github.com/weaveworks/common@v0.0.0-20230728070032-dd9e68f319d5/httpgrpc/server/server.go (about) 1 package server 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "net" 8 "net/http" 9 "net/http/httptest" 10 "net/url" 11 "strings" 12 13 otgrpc "github.com/opentracing-contrib/go-grpc" 14 "github.com/opentracing/opentracing-go" 15 "github.com/sercand/kuberesolver/v4" 16 "golang.org/x/net/context" 17 "google.golang.org/grpc" 18 "google.golang.org/grpc/credentials/insecure" 19 20 "github.com/weaveworks/common/httpgrpc" 21 "github.com/weaveworks/common/logging" 22 "github.com/weaveworks/common/middleware" 23 ) 24 25 // Server implements HTTPServer. HTTPServer is a generated interface that gRPC 26 // servers must implement. 27 type Server struct { 28 handler http.Handler 29 } 30 31 // NewServer makes a new Server. 32 func NewServer(handler http.Handler) *Server { 33 return &Server{ 34 handler: handler, 35 } 36 } 37 38 type nopCloser struct { 39 *bytes.Buffer 40 } 41 42 func (nopCloser) Close() error { return nil } 43 44 // BytesBuffer returns the underlaying `bytes.buffer` used to build this io.ReadCloser. 45 func (n nopCloser) BytesBuffer() *bytes.Buffer { return n.Buffer } 46 47 // Handle implements HTTPServer. 48 func (s Server) Handle(ctx context.Context, r *httpgrpc.HTTPRequest) (*httpgrpc.HTTPResponse, error) { 49 req, err := http.NewRequest(r.Method, r.Url, nopCloser{Buffer: bytes.NewBuffer(r.Body)}) 50 if err != nil { 51 return nil, err 52 } 53 toHeader(r.Headers, req.Header) 54 req = req.WithContext(ctx) 55 req.RequestURI = r.Url 56 req.ContentLength = int64(len(r.Body)) 57 58 recorder := httptest.NewRecorder() 59 s.handler.ServeHTTP(recorder, req) 60 resp := &httpgrpc.HTTPResponse{ 61 Code: int32(recorder.Code), 62 Headers: fromHeader(recorder.Header()), 63 Body: recorder.Body.Bytes(), 64 } 65 if recorder.Code/100 == 5 { 66 return nil, httpgrpc.ErrorFromHTTPResponse(resp) 67 } 68 return resp, nil 69 } 70 71 // Client is a http.Handler that forwards the request over gRPC. 72 type Client struct { 73 client httpgrpc.HTTPClient 74 conn *grpc.ClientConn 75 } 76 77 // ParseURL deals with direct:// style URLs, as well as kubernetes:// urls. 78 // For backwards compatibility it treats URLs without schems as kubernetes://. 79 func ParseURL(unparsed string) (string, error) { 80 // if it has :///, this is the kuberesolver v2 URL. Return it as it is. 81 if strings.Contains(unparsed, ":///") { 82 return unparsed, nil 83 } 84 85 parsed, err := url.Parse(unparsed) 86 if err != nil { 87 return "", err 88 } 89 90 scheme, host := parsed.Scheme, parsed.Host 91 if !strings.Contains(unparsed, "://") { 92 scheme, host = "kubernetes", unparsed 93 } 94 95 switch scheme { 96 case "direct": 97 return host, err 98 99 case "kubernetes": 100 host, port, err := net.SplitHostPort(host) 101 if err != nil { 102 return "", err 103 } 104 parts := strings.SplitN(host, ".", 3) 105 service, namespace, domain := parts[0], "default", "" 106 if len(parts) > 1 { 107 namespace = parts[1] 108 domain = "." + namespace 109 } 110 if len(parts) > 2 { 111 domain = domain + "." + parts[2] 112 } 113 address := fmt.Sprintf("kubernetes:///%s%s:%s", service, domain, port) 114 return address, nil 115 116 default: 117 return "", fmt.Errorf("unrecognised scheme: %s", parsed.Scheme) 118 } 119 } 120 121 // NewClient makes a new Client, given a kubernetes service address. 122 func NewClient(address string) (*Client, error) { 123 kuberesolver.RegisterInCluster() 124 125 address, err := ParseURL(address) 126 if err != nil { 127 return nil, err 128 } 129 const grpcServiceConfig = `{"loadBalancingPolicy":"round_robin"}` 130 131 dialOptions := []grpc.DialOption{ 132 grpc.WithDefaultServiceConfig(grpcServiceConfig), 133 grpc.WithTransportCredentials(insecure.NewCredentials()), 134 grpc.WithChainUnaryInterceptor( 135 otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer()), 136 middleware.ClientUserHeaderInterceptor, 137 ), 138 } 139 140 conn, err := grpc.Dial(address, dialOptions...) 141 if err != nil { 142 return nil, err 143 } 144 145 return &Client{ 146 client: httpgrpc.NewHTTPClient(conn), 147 conn: conn, 148 }, nil 149 } 150 151 // HTTPRequest wraps an ordinary HTTPRequest with a gRPC one 152 func HTTPRequest(r *http.Request) (*httpgrpc.HTTPRequest, error) { 153 body, err := ioutil.ReadAll(r.Body) 154 if err != nil { 155 return nil, err 156 } 157 return &httpgrpc.HTTPRequest{ 158 Method: r.Method, 159 Url: r.RequestURI, 160 Body: body, 161 Headers: fromHeader(r.Header), 162 }, nil 163 } 164 165 // WriteResponse converts an httpgrpc response to an HTTP one 166 func WriteResponse(w http.ResponseWriter, resp *httpgrpc.HTTPResponse) error { 167 toHeader(resp.Headers, w.Header()) 168 w.WriteHeader(int(resp.Code)) 169 _, err := w.Write(resp.Body) 170 return err 171 } 172 173 // WriteError converts an httpgrpc error to an HTTP one 174 func WriteError(w http.ResponseWriter, err error) { 175 resp, ok := httpgrpc.HTTPResponseFromError(err) 176 if ok { 177 WriteResponse(w, resp) 178 } else { 179 http.Error(w, err.Error(), http.StatusInternalServerError) 180 } 181 } 182 183 // ServeHTTP implements http.Handler 184 func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) { 185 if tracer := opentracing.GlobalTracer(); tracer != nil { 186 if span := opentracing.SpanFromContext(r.Context()); span != nil { 187 if err := tracer.Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header)); err != nil { 188 logging.Global().Warnf("Failed to inject tracing headers into request: %v", err) 189 } 190 } 191 } 192 193 req, err := HTTPRequest(r) 194 if err != nil { 195 http.Error(w, err.Error(), http.StatusInternalServerError) 196 return 197 } 198 resp, err := c.client.Handle(r.Context(), req) 199 if err != nil { 200 // Some errors will actually contain a valid resp, just need to unpack it 201 var ok bool 202 resp, ok = httpgrpc.HTTPResponseFromError(err) 203 204 if !ok { 205 http.Error(w, err.Error(), http.StatusInternalServerError) 206 return 207 } 208 } 209 210 if err := WriteResponse(w, resp); err != nil { 211 http.Error(w, err.Error(), http.StatusInternalServerError) 212 return 213 } 214 } 215 216 func toHeader(hs []*httpgrpc.Header, header http.Header) { 217 for _, h := range hs { 218 header[h.Key] = h.Values 219 } 220 } 221 222 func fromHeader(hs http.Header) []*httpgrpc.Header { 223 result := make([]*httpgrpc.Header, 0, len(hs)) 224 for k, vs := range hs { 225 result = append(result, &httpgrpc.Header{ 226 Key: k, 227 Values: vs, 228 }) 229 } 230 return result 231 }