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  }