github.com/ava-labs/avalanchego@v1.11.11/vms/rpcchainvm/ghttp/http_client.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package ghttp
     5  
     6  import (
     7  	"io"
     8  	"net/http"
     9  
    10  	"github.com/ava-labs/avalanchego/vms/rpcchainvm/ghttp/gresponsewriter"
    11  	"github.com/ava-labs/avalanchego/vms/rpcchainvm/grpcutils"
    12  
    13  	httppb "github.com/ava-labs/avalanchego/proto/pb/http"
    14  	responsewriterpb "github.com/ava-labs/avalanchego/proto/pb/http/responsewriter"
    15  )
    16  
    17  var _ http.Handler = (*Client)(nil)
    18  
    19  // Client is an http.Handler that talks over RPC.
    20  type Client struct {
    21  	client httppb.HTTPClient
    22  }
    23  
    24  // NewClient returns an HTTP handler database instance connected to a remote
    25  // HTTP handler instance
    26  func NewClient(client httppb.HTTPClient) *Client {
    27  	return &Client{
    28  		client: client,
    29  	}
    30  }
    31  
    32  func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    33  	// rfc2616#section-14.42: The Upgrade general-header allows the client
    34  	// to specify a communication protocols it supports and would like to
    35  	// use. Upgrade (e.g. websockets) is a more expensive transaction and
    36  	// if not required use the less expensive HTTPSimple.
    37  	if !isUpgradeRequest(r) {
    38  		c.serveHTTPSimple(w, r)
    39  		return
    40  	}
    41  
    42  	closer := grpcutils.ServerCloser{}
    43  	defer closer.GracefulStop()
    44  
    45  	// Wrap [w] with a lock to ensure that it is accessed in a thread-safe manner.
    46  	w = gresponsewriter.NewLockedWriter(w)
    47  
    48  	serverListener, err := grpcutils.NewListener()
    49  	if err != nil {
    50  		http.Error(w, err.Error(), http.StatusInternalServerError)
    51  		return
    52  	}
    53  
    54  	server := grpcutils.NewServer()
    55  	closer.Add(server)
    56  	responsewriterpb.RegisterWriterServer(server, gresponsewriter.NewServer(w))
    57  
    58  	// Start responsewriter gRPC service.
    59  	go grpcutils.Serve(serverListener, server)
    60  
    61  	body, err := io.ReadAll(r.Body)
    62  	if err != nil {
    63  		http.Error(w, err.Error(), http.StatusBadRequest)
    64  		return
    65  	}
    66  
    67  	req := &httppb.HTTPRequest{
    68  		ResponseWriter: &httppb.ResponseWriter{
    69  			ServerAddr: serverListener.Addr().String(),
    70  			Header:     make([]*httppb.Element, 0, len(r.Header)),
    71  		},
    72  		Request: &httppb.Request{
    73  			Method:           r.Method,
    74  			Proto:            r.Proto,
    75  			ProtoMajor:       int32(r.ProtoMajor),
    76  			ProtoMinor:       int32(r.ProtoMinor),
    77  			Header:           make([]*httppb.Element, 0, len(r.Header)),
    78  			Body:             body,
    79  			ContentLength:    r.ContentLength,
    80  			TransferEncoding: r.TransferEncoding,
    81  			Host:             r.Host,
    82  			Form:             make([]*httppb.Element, 0, len(r.Form)),
    83  			PostForm:         make([]*httppb.Element, 0, len(r.PostForm)),
    84  			RemoteAddr:       r.RemoteAddr,
    85  			RequestUri:       r.RequestURI,
    86  		},
    87  	}
    88  	for key, values := range w.Header() {
    89  		req.ResponseWriter.Header = append(req.ResponseWriter.Header, &httppb.Element{
    90  			Key:    key,
    91  			Values: values,
    92  		})
    93  	}
    94  	for key, values := range r.Header {
    95  		req.Request.Header = append(req.Request.Header, &httppb.Element{
    96  			Key:    key,
    97  			Values: values,
    98  		})
    99  	}
   100  	for key, values := range r.Form {
   101  		req.Request.Form = append(req.Request.Form, &httppb.Element{
   102  			Key:    key,
   103  			Values: values,
   104  		})
   105  	}
   106  	for key, values := range r.PostForm {
   107  		req.Request.PostForm = append(req.Request.PostForm, &httppb.Element{
   108  			Key:    key,
   109  			Values: values,
   110  		})
   111  	}
   112  
   113  	if r.URL != nil {
   114  		req.Request.Url = &httppb.URL{
   115  			Scheme:     r.URL.Scheme,
   116  			Opaque:     r.URL.Opaque,
   117  			Host:       r.URL.Host,
   118  			Path:       r.URL.Path,
   119  			RawPath:    r.URL.RawPath,
   120  			ForceQuery: r.URL.ForceQuery,
   121  			RawQuery:   r.URL.RawQuery,
   122  			Fragment:   r.URL.Fragment,
   123  		}
   124  
   125  		if r.URL.User != nil {
   126  			pwd, set := r.URL.User.Password()
   127  			req.Request.Url.User = &httppb.Userinfo{
   128  				Username:    r.URL.User.Username(),
   129  				Password:    pwd,
   130  				PasswordSet: set,
   131  			}
   132  		}
   133  	}
   134  
   135  	if r.TLS != nil {
   136  		req.Request.Tls = &httppb.ConnectionState{
   137  			Version:            uint32(r.TLS.Version),
   138  			HandshakeComplete:  r.TLS.HandshakeComplete,
   139  			DidResume:          r.TLS.DidResume,
   140  			CipherSuite:        uint32(r.TLS.CipherSuite),
   141  			NegotiatedProtocol: r.TLS.NegotiatedProtocol,
   142  			ServerName:         r.TLS.ServerName,
   143  			PeerCertificates: &httppb.Certificates{
   144  				Cert: make([][]byte, len(r.TLS.PeerCertificates)),
   145  			},
   146  			VerifiedChains:              make([]*httppb.Certificates, len(r.TLS.VerifiedChains)),
   147  			SignedCertificateTimestamps: r.TLS.SignedCertificateTimestamps,
   148  			OcspResponse:                r.TLS.OCSPResponse,
   149  		}
   150  		for i, cert := range r.TLS.PeerCertificates {
   151  			req.Request.Tls.PeerCertificates.Cert[i] = cert.Raw
   152  		}
   153  		for i, chain := range r.TLS.VerifiedChains {
   154  			req.Request.Tls.VerifiedChains[i] = &httppb.Certificates{
   155  				Cert: make([][]byte, len(chain)),
   156  			}
   157  			for j, cert := range chain {
   158  				req.Request.Tls.VerifiedChains[i].Cert[j] = cert.Raw
   159  			}
   160  		}
   161  	}
   162  
   163  	_, err = c.client.Handle(r.Context(), req)
   164  	if err != nil {
   165  		http.Error(w, err.Error(), http.StatusInternalServerError)
   166  	}
   167  }
   168  
   169  // serveHTTPSimple converts an http request to a gRPC HTTPRequest and returns the
   170  // response to the client. Protocol upgrade requests (websockets) are not supported
   171  // and should use ServeHTTP. Based on https://www.weave.works/blog/turtles-way-http-grpc.
   172  func (c *Client) serveHTTPSimple(w http.ResponseWriter, r *http.Request) {
   173  	req, err := getHTTPSimpleRequest(r)
   174  	if err != nil {
   175  		http.Error(w, err.Error(), http.StatusBadRequest)
   176  		return
   177  	}
   178  
   179  	resp, err := c.client.HandleSimple(r.Context(), req)
   180  	if err != nil {
   181  		// Some errors will actually contain a valid resp, just need to unpack it
   182  		var ok bool
   183  		resp, ok = grpcutils.GetHTTPResponseFromError(err)
   184  		if !ok {
   185  			http.Error(w, err.Error(), http.StatusInternalServerError)
   186  			return
   187  		}
   188  	}
   189  
   190  	if err := convertWriteResponse(w, resp); err != nil {
   191  		http.Error(w, err.Error(), http.StatusInternalServerError)
   192  		return
   193  	}
   194  }
   195  
   196  // getHTTPSimpleRequest takes an http request as input and returns a gRPC HandleSimpleHTTPRequest.
   197  func getHTTPSimpleRequest(r *http.Request) (*httppb.HandleSimpleHTTPRequest, error) {
   198  	body, err := io.ReadAll(r.Body)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  	return &httppb.HandleSimpleHTTPRequest{
   203  		Method:  r.Method,
   204  		Url:     r.RequestURI,
   205  		Body:    body,
   206  		Headers: grpcutils.GetHTTPHeader(r.Header),
   207  	}, nil
   208  }
   209  
   210  // convertWriteResponse converts a gRPC HandleSimpleHTTPResponse to an HTTP response.
   211  func convertWriteResponse(w http.ResponseWriter, resp *httppb.HandleSimpleHTTPResponse) error {
   212  	grpcutils.MergeHTTPHeader(resp.Headers, w.Header())
   213  	w.WriteHeader(grpcutils.EnsureValidResponseCode(int(resp.Code)))
   214  	_, err := w.Write(resp.Body)
   215  	return err
   216  }
   217  
   218  // isUpgradeRequest returns true if the upgrade key exists in header and value is non empty.
   219  func isUpgradeRequest(req *http.Request) bool {
   220  	return req.Header.Get("Upgrade") != ""
   221  }