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  }