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 }