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  }