github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/framework/http/gateway.go (about) 1 package ddhttp 2 3 import ( 4 "fmt" 5 lru "github.com/hashicorp/golang-lru" 6 "github.com/unionj-cloud/go-doudou/framework/cache" 7 "github.com/unionj-cloud/go-doudou/framework/internal/config" 8 "github.com/unionj-cloud/go-doudou/framework/registry" 9 "github.com/unionj-cloud/go-doudou/framework/registry/nacos" 10 "github.com/wubin1989/nacos-sdk-go/vo" 11 "net/http" 12 "net/http/httputil" 13 "net/url" 14 "os" 15 "regexp" 16 "strconv" 17 "strings" 18 ) 19 20 // Headers borrowed from labstack/echo 21 const ( 22 HeaderAccept = "Accept" 23 HeaderAcceptEncoding = "Accept-Encoding" 24 HeaderAllow = "Allow" 25 HeaderAuthorization = "Authorization" 26 HeaderContentDisposition = "Content-Disposition" 27 HeaderContentEncoding = "Content-Encoding" 28 HeaderContentLength = "Content-Length" 29 HeaderContentType = "Content-Type" 30 HeaderCookie = "Cookie" 31 HeaderSetCookie = "Set-Cookie" 32 HeaderIfModifiedSince = "If-Modified-Since" 33 HeaderLastModified = "Last-Modified" 34 HeaderLocation = "Location" 35 HeaderUpgrade = "Upgrade" 36 HeaderVary = "Vary" 37 HeaderWWWAuthenticate = "WWW-Authenticate" 38 HeaderXForwardedFor = "X-Forwarded-For" 39 HeaderXForwardedProto = "X-Forwarded-Proto" 40 HeaderXForwardedProtocol = "X-Forwarded-Protocol" 41 HeaderXForwardedSsl = "X-Forwarded-Ssl" 42 HeaderXUrlScheme = "X-Url-Scheme" 43 HeaderXHTTPMethodOverride = "X-HTTP-Method-Override" 44 HeaderXRealIP = "X-Real-IP" 45 HeaderXRequestID = "X-Request-ID" 46 HeaderXRequestedWith = "X-Requested-With" 47 HeaderServer = "Server" 48 HeaderOrigin = "Origin" 49 50 // Access control 51 HeaderAccessControlRequestMethod = "Access-Control-Request-Method" 52 HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers" 53 HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" 54 HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods" 55 HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" 56 HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" 57 HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" 58 HeaderAccessControlMaxAge = "Access-Control-Max-Age" 59 60 // Security 61 HeaderStrictTransportSecurity = "Strict-Transport-Security" 62 HeaderXContentTypeOptions = "X-Content-Type-Options" 63 HeaderXXSSProtection = "X-XSS-Protection" 64 HeaderXFrameOptions = "X-Frame-Options" 65 HeaderContentSecurityPolicy = "Content-Security-Policy" 66 HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" 67 HeaderXCSRFToken = "X-CSRF-Token" 68 HeaderReferrerPolicy = "Referrer-Policy" 69 ) 70 71 type ProxyTarget struct { 72 Name string 73 URL *url.URL 74 } 75 76 type ProxyConfig struct { 77 ProviderStore cache.IStore 78 // To customize the transport to remote. 79 // Examples: If custom TLS certificates are required. 80 Transport http.RoundTripper 81 82 // ModifyResponse defines function to modify response from ProxyTarget. 83 ModifyResponse func(*http.Response) error 84 } 85 86 func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer { 87 groups := pattern.FindAllStringSubmatch(input, -1) 88 if groups == nil { 89 return nil 90 } 91 values := groups[0][1:] 92 replace := make([]string, 2*len(values)) 93 for i, v := range values { 94 j := 2 * i 95 replace[j] = "$" + strconv.Itoa(i+1) 96 replace[j+1] = v 97 } 98 return strings.NewReplacer(replace...) 99 } 100 101 func getPath(r *http.Request) string { 102 path := r.URL.RawPath 103 if path == "" { 104 path = r.URL.Path 105 } 106 return path 107 } 108 109 func isWebSocket(r *http.Request) bool { 110 upgrade := r.Header.Get(HeaderUpgrade) 111 return strings.ToLower(upgrade) == "websocket" 112 } 113 114 func Proxy(proxyConfig ProxyConfig) func(inner http.Handler) http.Handler { 115 if proxyConfig.ProviderStore == nil { 116 arc, _ := lru.NewARC(128) 117 proxyConfig.ProviderStore = arc 118 } 119 if proxyConfig.Transport == nil { 120 proxyConfig.Transport = http.DefaultTransport 121 } 122 return func(inner http.Handler) http.Handler { 123 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 124 if isWebSocket(r) || r.Header.Get(HeaderAccept) == "text/event-stream" { 125 http.Error(w, fmt.Sprintf("not support"), http.StatusBadGateway) 126 return 127 } 128 parts := strings.Split(r.URL.Path, "/") 129 if len(parts) <= 1 { 130 http.Error(w, fmt.Sprintf("request url must be prefixed / + service name"), http.StatusBadGateway) 131 return 132 } 133 serviceName := parts[1] 134 modes := strings.Split(os.Getenv("GDD_SERVICE_DISCOVERY_MODE"), ",") 135 var provider registry.IServiceProvider 136 for _, mode := range modes { 137 switch mode { 138 case "nacos": 139 cluster := config.GddNacosClusterName.LoadOrDefault(config.DefaultGddNacosClusterName) 140 group := config.GddNacosGroupName.LoadOrDefault(config.DefaultGddNacosGroupName) 141 _, err := nacos.NamingClient.GetService(vo.GetServiceParam{ 142 Clusters: []string{cluster}, 143 ServiceName: serviceName, 144 GroupName: group, 145 }) 146 if err != nil { 147 continue 148 } 149 if value, ok := proxyConfig.ProviderStore.Get(serviceName); ok { 150 if provider, ok = value.(*NacosWRRServiceProvider); ok { 151 break 152 } 153 } 154 provider = NewNacosWRRServiceProvider(serviceName, WithNacosClusters([]string{cluster}), WithNacosGroupName(group)) 155 proxyConfig.ProviderStore.Add(serviceName, provider) 156 default: 157 nodes, err := registry.AllNodes() 158 if err != nil { 159 continue 160 } 161 exist := false 162 for _, node := range nodes { 163 if registry.SvcName(node) == serviceName { 164 exist = true 165 break 166 } 167 } 168 if !exist { 169 continue 170 } 171 if value, ok := proxyConfig.ProviderStore.Get(serviceName); ok { 172 if provider, ok = value.(*SmoothWeightedRoundRobinProvider); ok { 173 break 174 } 175 } 176 provider = NewSmoothWeightedRoundRobinProvider(serviceName) 177 proxyConfig.ProviderStore.Add(serviceName, provider) 178 } 179 if provider != nil { 180 break 181 } 182 } 183 if provider == nil { 184 http.Error(w, fmt.Sprintf("available server for service %s not found", serviceName), http.StatusBadGateway) 185 return 186 } 187 k := regexp.MustCompile(strings.Replace(fmt.Sprintf("/%s/*", serviceName), "*", "(\\S*)", -1)) 188 replacer := captureTokens(k, getPath(r)) 189 if replacer != nil { 190 r.URL.Path = replacer.Replace("/$1") 191 } 192 parsed, err := url.Parse(provider.SelectServer()) 193 if err != nil { 194 http.Error(w, fmt.Sprintf("available server for service %s not found with error: %s", serviceName, err), http.StatusBadGateway) 195 return 196 } 197 tgt := &ProxyTarget{ 198 Name: serviceName, 199 URL: parsed, 200 } 201 proxyHTTP(tgt, proxyConfig).ServeHTTP(w, r) 202 }) 203 } 204 } 205 206 func singleJoiningSlash(a, b string) string { 207 aslash := strings.HasSuffix(a, "/") 208 bslash := strings.HasPrefix(b, "/") 209 switch { 210 case aslash && bslash: 211 return a + b[1:] 212 case !aslash && !bslash: 213 return a + "/" + b 214 } 215 return a + b 216 } 217 218 func proxyHTTP(tgt *ProxyTarget, config ProxyConfig) http.Handler { 219 target := tgt.URL 220 targetQuery := target.RawQuery 221 director := func(req *http.Request) { 222 req.URL.Scheme = target.Scheme 223 req.URL.Host = target.Host 224 req.Host = target.Host 225 req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path) 226 if targetQuery == "" || req.URL.RawQuery == "" { 227 req.URL.RawQuery = targetQuery + req.URL.RawQuery 228 } else { 229 req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery 230 } 231 req.Header.Set("Host", target.Host) 232 if _, ok := req.Header["User-Agent"]; !ok { 233 // explicitly disable User-Agent so it's not set to default value 234 req.Header.Set("User-Agent", "") 235 } 236 } 237 proxy := &httputil.ReverseProxy{Director: director} 238 proxy.ErrorHandler = func(w http.ResponseWriter, req *http.Request, err error) { 239 desc := target.String() 240 if tgt.Name != "" { 241 desc = fmt.Sprintf("%s(%s)", tgt.Name, tgt.URL.String()) 242 } 243 http.Error(w, fmt.Sprintf("remote %s unreachable, could not forward: %v", desc, err), http.StatusBadGateway) 244 } 245 proxy.Transport = config.Transport 246 proxy.ModifyResponse = config.ModifyResponse 247 return proxy 248 }