github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/api/handler/web/web.go (about)

     1  // Copyright 2020 Asim Aslam
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // Original source: github.com/micro/go-micro/v3/api/handler/web.go
    16  
    17  // Package web contains the web handler including websocket support
    18  package web
    19  
    20  import (
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"math/rand"
    25  	"net"
    26  	"net/http"
    27  	"net/http/httputil"
    28  	"net/url"
    29  	"strings"
    30  
    31  	"github.com/tickoalcantara12/micro/v3/service/api"
    32  	"github.com/tickoalcantara12/micro/v3/service/api/handler"
    33  	"github.com/tickoalcantara12/micro/v3/service/registry"
    34  )
    35  
    36  const (
    37  	Handler = "web"
    38  )
    39  
    40  type webHandler struct {
    41  	opts handler.Options
    42  	s    *api.Service
    43  }
    44  
    45  func (wh *webHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    46  	service, err := wh.getService(r)
    47  	if err != nil {
    48  		w.WriteHeader(500)
    49  		return
    50  	}
    51  
    52  	if len(service) == 0 {
    53  		w.WriteHeader(404)
    54  		return
    55  	}
    56  
    57  	rp, err := url.Parse(service)
    58  	if err != nil {
    59  		w.WriteHeader(500)
    60  		return
    61  	}
    62  
    63  	if isWebSocket(r) {
    64  		wh.serveWebSocket(rp.Host, w, r)
    65  		return
    66  	}
    67  
    68  	httputil.NewSingleHostReverseProxy(rp).ServeHTTP(w, r)
    69  }
    70  
    71  // getService returns the service for this request from the selector
    72  func (wh *webHandler) getService(r *http.Request) (string, error) {
    73  	var service *api.Service
    74  
    75  	if v, ok := r.Context().(handler.Context); ok {
    76  		// we were given the service
    77  		service = v.Service()
    78  	} else if wh.opts.Router != nil {
    79  		// try get service from router
    80  		s, err := wh.opts.Router.Route(r)
    81  		if err != nil {
    82  			return "", err
    83  		}
    84  		service = s
    85  	} else {
    86  		// we have no way of routing the request
    87  		return "", errors.New("no route found")
    88  	}
    89  
    90  	// get the nodes
    91  	var nodes []*registry.Node
    92  	for _, srv := range service.Services {
    93  		nodes = append(nodes, srv.Nodes...)
    94  	}
    95  	if len(nodes) == 0 {
    96  		return "", errors.New("no route found")
    97  	}
    98  
    99  	// select a random node
   100  	node := nodes[rand.Int()%len(nodes)]
   101  
   102  	return fmt.Sprintf("http://%s", node.Address), nil
   103  }
   104  
   105  // serveWebSocket used to serve a web socket proxied connection
   106  func (wh *webHandler) serveWebSocket(host string, w http.ResponseWriter, r *http.Request) {
   107  	req := new(http.Request)
   108  	*req = *r
   109  
   110  	if len(host) == 0 {
   111  		http.Error(w, "invalid host", 500)
   112  		return
   113  	}
   114  
   115  	// set x-forward-for
   116  	if clientIP, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
   117  		if ips, ok := req.Header["X-Forwarded-For"]; ok {
   118  			clientIP = strings.Join(ips, ", ") + ", " + clientIP
   119  		}
   120  		req.Header.Set("X-Forwarded-For", clientIP)
   121  	}
   122  
   123  	// connect to the backend host
   124  	conn, err := net.Dial("tcp", host)
   125  	if err != nil {
   126  		http.Error(w, err.Error(), 500)
   127  		return
   128  	}
   129  
   130  	// hijack the connection
   131  	hj, ok := w.(http.Hijacker)
   132  	if !ok {
   133  		http.Error(w, "failed to connect", 500)
   134  		return
   135  	}
   136  
   137  	nc, _, err := hj.Hijack()
   138  	if err != nil {
   139  		return
   140  	}
   141  
   142  	defer nc.Close()
   143  	defer conn.Close()
   144  
   145  	if err = req.Write(conn); err != nil {
   146  		return
   147  	}
   148  
   149  	errCh := make(chan error, 2)
   150  
   151  	cp := func(dst io.Writer, src io.Reader) {
   152  		_, err := io.Copy(dst, src)
   153  		errCh <- err
   154  	}
   155  
   156  	go cp(conn, nc)
   157  	go cp(nc, conn)
   158  
   159  	<-errCh
   160  }
   161  
   162  func isWebSocket(r *http.Request) bool {
   163  	contains := func(key, val string) bool {
   164  		vv := strings.Split(r.Header.Get(key), ",")
   165  		for _, v := range vv {
   166  			if val == strings.ToLower(strings.TrimSpace(v)) {
   167  				return true
   168  			}
   169  		}
   170  		return false
   171  	}
   172  
   173  	if contains("Connection", "upgrade") && contains("Upgrade", "websocket") {
   174  		return true
   175  	}
   176  
   177  	return false
   178  }
   179  
   180  func (wh *webHandler) String() string {
   181  	return "web"
   182  }
   183  
   184  func NewHandler(opts ...handler.Option) handler.Handler {
   185  	return &webHandler{
   186  		opts: handler.NewOptions(opts...),
   187  	}
   188  }