github.com/btccom/go-micro/v2@v2.9.3/api/handler/web/web.go (about)

     1  // Package web contains the web handler including websocket support
     2  package web
     3  
     4  import (
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"net/http"
    10  	"net/http/httputil"
    11  	"net/url"
    12  	"strings"
    13  
    14  	"github.com/btccom/go-micro/v2/api"
    15  	"github.com/btccom/go-micro/v2/api/handler"
    16  	"github.com/btccom/go-micro/v2/client/selector"
    17  )
    18  
    19  const (
    20  	Handler = "web"
    21  )
    22  
    23  type webHandler struct {
    24  	opts handler.Options
    25  	s    *api.Service
    26  }
    27  
    28  func (wh *webHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    29  	service, err := wh.getService(r)
    30  	if err != nil {
    31  		w.WriteHeader(500)
    32  		return
    33  	}
    34  
    35  	if len(service) == 0 {
    36  		w.WriteHeader(404)
    37  		return
    38  	}
    39  
    40  	rp, err := url.Parse(service)
    41  	if err != nil {
    42  		w.WriteHeader(500)
    43  		return
    44  	}
    45  
    46  	if isWebSocket(r) {
    47  		wh.serveWebSocket(rp.Host, w, r)
    48  		return
    49  	}
    50  
    51  	httputil.NewSingleHostReverseProxy(rp).ServeHTTP(w, r)
    52  }
    53  
    54  // getService returns the service for this request from the selector
    55  func (wh *webHandler) getService(r *http.Request) (string, error) {
    56  	var service *api.Service
    57  
    58  	if wh.s != nil {
    59  		// we were given the service
    60  		service = wh.s
    61  	} else if wh.opts.Router != nil {
    62  		// try get service from router
    63  		s, err := wh.opts.Router.Route(r)
    64  		if err != nil {
    65  			return "", err
    66  		}
    67  		service = s
    68  	} else {
    69  		// we have no way of routing the request
    70  		return "", errors.New("no route found")
    71  	}
    72  
    73  	// create a random selector
    74  	next := selector.Random(service.Services)
    75  
    76  	// get the next node
    77  	s, err := next()
    78  	if err != nil {
    79  		return "", nil
    80  	}
    81  
    82  	return fmt.Sprintf("http://%s", s.Address), nil
    83  }
    84  
    85  // serveWebSocket used to serve a web socket proxied connection
    86  func (wh *webHandler) serveWebSocket(host string, w http.ResponseWriter, r *http.Request) {
    87  	req := new(http.Request)
    88  	*req = *r
    89  
    90  	if len(host) == 0 {
    91  		http.Error(w, "invalid host", 500)
    92  		return
    93  	}
    94  
    95  	// set x-forward-for
    96  	if clientIP, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
    97  		if ips, ok := req.Header["X-Forwarded-For"]; ok {
    98  			clientIP = strings.Join(ips, ", ") + ", " + clientIP
    99  		}
   100  		req.Header.Set("X-Forwarded-For", clientIP)
   101  	}
   102  
   103  	// connect to the backend host
   104  	conn, err := net.Dial("tcp", host)
   105  	if err != nil {
   106  		http.Error(w, err.Error(), 500)
   107  		return
   108  	}
   109  
   110  	// hijack the connection
   111  	hj, ok := w.(http.Hijacker)
   112  	if !ok {
   113  		http.Error(w, "failed to connect", 500)
   114  		return
   115  	}
   116  
   117  	nc, _, err := hj.Hijack()
   118  	if err != nil {
   119  		return
   120  	}
   121  
   122  	defer nc.Close()
   123  	defer conn.Close()
   124  
   125  	if err = req.Write(conn); err != nil {
   126  		return
   127  	}
   128  
   129  	errCh := make(chan error, 2)
   130  
   131  	cp := func(dst io.Writer, src io.Reader) {
   132  		_, err := io.Copy(dst, src)
   133  		errCh <- err
   134  	}
   135  
   136  	go cp(conn, nc)
   137  	go cp(nc, conn)
   138  
   139  	<-errCh
   140  }
   141  
   142  func isWebSocket(r *http.Request) bool {
   143  	contains := func(key, val string) bool {
   144  		vv := strings.Split(r.Header.Get(key), ",")
   145  		for _, v := range vv {
   146  			if val == strings.ToLower(strings.TrimSpace(v)) {
   147  				return true
   148  			}
   149  		}
   150  		return false
   151  	}
   152  
   153  	if contains("Connection", "upgrade") && contains("Upgrade", "websocket") {
   154  		return true
   155  	}
   156  
   157  	return false
   158  }
   159  
   160  func (wh *webHandler) String() string {
   161  	return "web"
   162  }
   163  
   164  func NewHandler(opts ...handler.Option) handler.Handler {
   165  	return &webHandler{
   166  		opts: handler.NewOptions(opts...),
   167  	}
   168  }
   169  
   170  func WithService(s *api.Service, opts ...handler.Option) handler.Handler {
   171  	options := handler.NewOptions(opts...)
   172  
   173  	return &webHandler{
   174  		opts: options,
   175  		s:    s,
   176  	}
   177  }