trpc.group/trpc-go/trpc-go@v1.0.3/http/client.go (about) 1 // 2 // 3 // Tencent is pleased to support the open source community by making tRPC available. 4 // 5 // Copyright (C) 2023 THL A29 Limited, a Tencent company. 6 // All rights reserved. 7 // 8 // If you have downloaded a copy of the tRPC source code from Tencent, 9 // please note that tRPC source code is licensed under the Apache 2.0 License, 10 // A copy of the Apache 2.0 License is included in this file. 11 // 12 // 13 14 package http 15 16 import ( 17 "context" 18 "net/http" 19 "strings" 20 21 "trpc.group/trpc-go/trpc-go/client" 22 "trpc.group/trpc-go/trpc-go/codec" 23 "trpc.group/trpc-go/trpc-go/errs" 24 ) 25 26 // Client provides the HTTP client interface. 27 // The primary use of this interface is to request standard HTTP services, 28 // if you wish to request HTTP RPC services use the client provided by the 29 // stub code (simply specify the protocol as "http"). 30 type Client interface { 31 Get(ctx context.Context, path string, rspBody interface{}, opts ...client.Option) error 32 Post(ctx context.Context, path string, reqBody interface{}, rspBody interface{}, opts ...client.Option) error 33 Put(ctx context.Context, path string, reqBody interface{}, rspBody interface{}, opts ...client.Option) error 34 Patch(ctx context.Context, path string, reqBody interface{}, rspBody interface{}, opts ...client.Option) error 35 Delete(ctx context.Context, path string, reqBody interface{}, rspBody interface{}, opts ...client.Option) error 36 } 37 38 // cli is the struct of backend request. 39 type cli struct { 40 serviceName string 41 client client.Client 42 opts []client.Option 43 } 44 45 // NewClientProxy creates a new http backend request proxy. 46 // Parameter name means the name of backend http service (e.g. trpc.http.xxx.xxx), 47 // mainly used for metrics, can be freely defined but 48 // format needs to follow "trpc.app.server.service". 49 var NewClientProxy = func(name string, opts ...client.Option) Client { 50 c := &cli{ 51 serviceName: name, 52 client: client.DefaultClient, 53 } 54 c.opts = make([]client.Option, 0, len(opts)+1) 55 c.opts = append(c.opts, client.WithProtocol("http")) 56 c.opts = append(c.opts, opts...) 57 return c 58 } 59 60 // NewStdHTTPClient returns http.Client of the go sdk, which is convenient for 61 // third-party clients to use, and can report monitoring metrics. 62 func NewStdHTTPClient(name string, opts ...client.Option) *http.Client { 63 c := &cli{ 64 serviceName: name, 65 client: client.DefaultClient, 66 } 67 c.opts = make([]client.Option, 0, len(opts)+1) 68 c.opts = append(c.opts, client.WithProtocol("http")) 69 c.opts = append(c.opts, opts...) 70 return &http.Client{Transport: c} 71 } 72 73 // RoundTrip implements the http.RoundTripper interface of http.Client of go sdk. 74 func (c *cli) RoundTrip(request *http.Request) (*http.Response, error) { 75 ctx, msg := codec.WithCloneMessage(request.Context()) 76 defer codec.PutBackMessage(msg) 77 c.setDefaultCallOption(msg, request.Method, request.URL.Path) 78 79 header := &ClientReqHeader{ 80 Schema: request.URL.Scheme, 81 Method: request.Method, 82 Host: request.URL.Host, 83 Request: request, 84 Header: request.Header, 85 } 86 87 opts := append([]client.Option{ 88 client.WithReqHead(header), 89 client.WithCurrentCompressType(0), // no compression 90 client.WithCurrentSerializationType(-1), // no serialization 91 }, c.opts...) 92 93 err := c.client.Invoke(ctx, nil, nil, opts...) 94 var rsp *http.Response 95 if h, ok := msg.ClientRspHead().(*ClientRspHeader); ok && h.Response != nil { 96 rsp = h.Response 97 } 98 99 if err != nil { 100 // If the error is caused by the status code, ignore it and return the response normally. 101 if rsp != nil && rsp.StatusCode == int(errs.Code(err)) { 102 return rsp, nil 103 } 104 return nil, err 105 } 106 return rsp, nil 107 } 108 109 // Post uses trpc client to send http POST request. 110 // Param path represents the url segments that follow domain, e.g. /cgi-bin/add_xxx 111 // Param rspBody and rspBody are passed in with specific type, 112 // corresponding serialization should be specified, or json by default. 113 // client.WithClientReqHead will be called within this method to ensure that httpMethod is POST. 114 func (c *cli) Post(ctx context.Context, path string, reqBody interface{}, rspBody interface{}, 115 opts ...client.Option) error { 116 ctx, msg := codec.WithCloneMessage(ctx) 117 defer codec.PutBackMessage(msg) 118 c.setDefaultCallOption(msg, http.MethodPost, path) 119 return c.send(ctx, reqBody, rspBody, opts...) 120 } 121 122 // Put uses trpc client to send http PUT request. 123 // Param path represents the url segments that follow domain, e.g. /cgi-bin/update_xxx 124 // Param rspBody and rspBody are passed in with specific type, 125 // corresponding serialization should be specified, or json by default. 126 // client.WithClientReqHead will be called within this method to ensure that httpMethod is PUT. 127 func (c *cli) Put(ctx context.Context, path string, reqBody interface{}, rspBody interface{}, 128 opts ...client.Option) error { 129 ctx, msg := codec.WithCloneMessage(ctx) 130 defer codec.PutBackMessage(msg) 131 c.setDefaultCallOption(msg, http.MethodPut, path) 132 return c.send(ctx, reqBody, rspBody, opts...) 133 } 134 135 // Patch uses trpc client to send http PATCH request. 136 // Param path represents the url segments that follow domain, e.g. /cgi-bin/update_xxx 137 // Param rspBody and rspBody are passed in with specific type, 138 // corresponding serialization should be specified, or json by default. 139 // client.WithClientReqHead will be called within this method to ensure that httpMethod is PATCH. 140 func (c *cli) Patch(ctx context.Context, path string, reqBody interface{}, rspBody interface{}, 141 opts ...client.Option) error { 142 ctx, msg := codec.WithCloneMessage(ctx) 143 defer codec.PutBackMessage(msg) 144 c.setDefaultCallOption(msg, http.MethodPatch, path) 145 return c.send(ctx, reqBody, rspBody, opts...) 146 } 147 148 // Delete uses trpc client to send http DELETE request. 149 // Param path represents the url segments that follow domain, e.g. /cgi-bin/delete_xxx 150 // Param reqBody and rspBody are passed in with specific type, 151 // corresponding serialization should be specified, or json by default. 152 // client.WithClientReqHead will be called within this method to ensure that httpMethod is DELETE. 153 // 154 // Delete may have body, if it is empty, set reqBody and rspBody with nil. 155 func (c *cli) Delete(ctx context.Context, path string, reqBody interface{}, rspBody interface{}, 156 opts ...client.Option) error { 157 ctx, msg := codec.WithCloneMessage(ctx) 158 defer codec.PutBackMessage(msg) 159 c.setDefaultCallOption(msg, http.MethodDelete, path) 160 return c.send(ctx, reqBody, rspBody, opts...) 161 } 162 163 // Get uses trpc client to send http GET request. 164 // Param path represents the url segments that follow domain, e.g. /cgi-bin/get_xxx?k1=v1&k2=v2 165 // Param reqBody and rspBody are passed in with specific type, 166 // corresponding serialization should be specified, or json by default. 167 // client.WithClientReqHead will be called within this method to ensure that httpMethod is GET. 168 func (c *cli) Get(ctx context.Context, path string, rspBody interface{}, opts ...client.Option) error { 169 ctx, msg := codec.WithCloneMessage(ctx) 170 defer codec.PutBackMessage(msg) 171 c.setDefaultCallOption(msg, http.MethodGet, path) 172 return c.send(ctx, nil, rspBody, opts...) 173 } 174 175 // send uses trpc client to send http request. 176 func (c *cli) send(ctx context.Context, reqBody, rspBody interface{}, opts ...client.Option) error { 177 return c.client.Invoke(ctx, reqBody, rspBody, append(c.opts, opts...)...) 178 } 179 180 // setDefaultCallOption sets default call option. 181 func (c *cli) setDefaultCallOption(msg codec.Msg, method, path string) { 182 msg.WithClientRPCName(path) 183 msg.WithCalleeServiceName(c.serviceName) 184 msg.WithSerializationType(codec.SerializationTypeJSON) 185 186 // Use ClientReqHeader to specify HTTP method. 187 msg.WithClientReqHead(&ClientReqHeader{ 188 Method: method, 189 }) 190 msg.WithClientRspHead(&ClientRspHeader{}) 191 192 // Callee method is mainly for metrics. 193 // If you have special requirements, you can copy this part of code and modify it yourself. 194 if s := strings.Split(path, "?"); len(s) > 0 { 195 msg.WithCalleeMethod(s[0]) 196 } 197 }