github.com/MetalBlockchain/metalgo@v1.11.9/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/MetalBlockchain/metalgo/vms/rpcchainvm/ghttp/gresponsewriter" 11 "github.com/MetalBlockchain/metalgo/vms/rpcchainvm/grpcutils" 12 13 httppb "github.com/MetalBlockchain/metalgo/proto/pb/http" 14 responsewriterpb "github.com/MetalBlockchain/metalgo/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 }