dubbo.apache.org/dubbo-go/v3@v3.1.1/protocol/jsonrpc/http.go (about)

     1  /*
     2   * Licensed to the Apache Software Foundation (ASF) under one or more
     3   * contributor license agreements.  See the NOTICE file distributed with
     4   * this work for additional information regarding copyright ownership.
     5   * The ASF licenses this file to You under the Apache License, Version 2.0
     6   * (the "License"); you may not use this file except in compliance with
     7   * the License.  You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  package jsonrpc
    19  
    20  import (
    21  	"bufio"
    22  	"bytes"
    23  	"context"
    24  	"fmt"
    25  	"io"
    26  	"net"
    27  	"net/http"
    28  	"net/url"
    29  	"os"
    30  	"strings"
    31  	"sync/atomic"
    32  	"time"
    33  
    34  	"github.com/dubbogo/gost/log/logger"
    35  	"github.com/opentracing/opentracing-go"
    36  
    37  	"dubbo.apache.org/dubbo-go/v3/common"
    38  	"dubbo.apache.org/dubbo-go/v3/common/constant"
    39  	perrors "github.com/pkg/errors"
    40  )
    41  
    42  // Request is HTTP protocol request
    43  type Request struct {
    44  	ID       int64
    45  	group    string
    46  	protocol string
    47  	version  string
    48  	service  string
    49  	method   string
    50  	args     interface{}
    51  }
    52  
    53  // HTTPOptions is a HTTP option include HandshakeTimeout and HTTPTimeout.
    54  type HTTPOptions struct {
    55  	HandshakeTimeout time.Duration
    56  	HTTPTimeout      time.Duration
    57  }
    58  
    59  var defaultHTTPOptions = HTTPOptions{
    60  	HandshakeTimeout: 3 * time.Second,
    61  	HTTPTimeout:      3 * time.Second,
    62  }
    63  
    64  // HTTPClient is a HTTP client ,include ID and options.
    65  type HTTPClient struct {
    66  	ID      int64
    67  	options HTTPOptions
    68  }
    69  
    70  // NewHTTPClient creates a new HTTP client with HTTPOptions.
    71  func NewHTTPClient(opt *HTTPOptions) *HTTPClient {
    72  	if opt == nil {
    73  		opt = &defaultHTTPOptions
    74  	}
    75  
    76  	switch {
    77  	case opt.HandshakeTimeout == 0:
    78  		opt.HandshakeTimeout = defaultHTTPOptions.HandshakeTimeout
    79  	case opt.HTTPTimeout == 0:
    80  		opt.HTTPTimeout = defaultHTTPOptions.HTTPTimeout
    81  	}
    82  
    83  	t := time.Now()
    84  	return &HTTPClient{
    85  		ID:      int64(uint32(os.Getpid() * t.Second() * t.Nanosecond())),
    86  		options: *opt,
    87  	}
    88  }
    89  
    90  // NewRequest creates a new HTTP request with @service ,@method and @arguments.
    91  func (c *HTTPClient) NewRequest(service *common.URL, method string, args interface{}) *Request {
    92  	return &Request{
    93  		ID:       atomic.AddInt64(&c.ID, 1),
    94  		group:    service.GetParam(constant.GroupKey, ""),
    95  		protocol: service.Protocol,
    96  		version:  service.GetParam(constant.VersionKey, ""),
    97  		service:  service.Path,
    98  		method:   method,
    99  		args:     args,
   100  	}
   101  }
   102  
   103  // Call makes a HTTP call with @ctx , @service ,@req and @rsp
   104  func (c *HTTPClient) Call(ctx context.Context, service *common.URL, req *Request, rsp interface{}) error {
   105  	// header
   106  	httpHeader := http.Header{}
   107  	httpHeader.Set("Content-Type", "application/json")
   108  	httpHeader.Set("Accept", "application/json")
   109  
   110  	reqTimeout := c.options.HTTPTimeout
   111  	if reqTimeout <= 0 {
   112  		reqTimeout = 100 * time.Millisecond
   113  	}
   114  	httpHeader.Set("Timeout", reqTimeout.String())
   115  	if md, ok := ctx.Value(constant.DubboGoCtxKey).(map[string]string); ok {
   116  		for k := range md {
   117  			httpHeader.Set(k, md[k])
   118  		}
   119  	}
   120  
   121  	if span := opentracing.SpanFromContext(ctx); span != nil {
   122  		err := opentracing.GlobalTracer().Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(httpHeader))
   123  		if err != nil {
   124  			logger.Error("Could not inject the Context into http header.")
   125  		}
   126  	}
   127  
   128  	// body
   129  	codec := newJsonClientCodec()
   130  	codecData := CodecData{
   131  		ID:     req.ID,
   132  		Method: req.method,
   133  		Args:   req.args,
   134  	}
   135  	reqBody, err := codec.Write(&codecData)
   136  	if err != nil {
   137  		return perrors.WithStack(err)
   138  	}
   139  
   140  	rspBody, err := c.Do(service.Location, service.Path, httpHeader, reqBody)
   141  	if err != nil {
   142  		return perrors.WithStack(err)
   143  	}
   144  
   145  	return perrors.WithStack(codec.Read(rspBody, rsp))
   146  }
   147  
   148  // Do is the high level of complexity and the likelihood that the fasthttp client has not been extensively used
   149  // in production means that you would need to expect a very large benefit to justify the adoption of fasthttp today.
   150  func (c *HTTPClient) Do(addr, path string, httpHeader http.Header, body []byte) ([]byte, error) {
   151  	u := url.URL{Host: strings.TrimSuffix(addr, ":"), Path: path}
   152  	httpReq, err := http.NewRequest("POST", u.String(), bytes.NewBuffer(body))
   153  	if err != nil {
   154  		return nil, perrors.WithStack(err)
   155  	}
   156  	httpReq.Header = httpHeader
   157  	httpReq.Close = true
   158  
   159  	reqBuf := bytes.NewBuffer(make([]byte, 0))
   160  	if err = httpReq.Write(reqBuf); err != nil {
   161  		return nil, perrors.WithStack(err)
   162  	}
   163  
   164  	tcpConn, err := net.DialTimeout("tcp", addr, c.options.HandshakeTimeout)
   165  	if err != nil {
   166  		return nil, perrors.WithStack(err)
   167  	}
   168  	defer tcpConn.Close()
   169  	setNetConnTimeout := func(conn net.Conn, timeout time.Duration) error {
   170  		t := time.Time{}
   171  		if timeout > time.Duration(0) {
   172  			t = time.Now().Add(timeout)
   173  		}
   174  
   175  		return conn.SetDeadline(t)
   176  	}
   177  	if err = setNetConnTimeout(tcpConn, c.options.HTTPTimeout); err != nil {
   178  		return nil, err
   179  	}
   180  
   181  	if _, err = reqBuf.WriteTo(tcpConn); err != nil {
   182  		return nil, perrors.WithStack(err)
   183  	}
   184  
   185  	httpRsp, err := http.ReadResponse(bufio.NewReader(tcpConn), httpReq)
   186  	if err != nil {
   187  		return nil, perrors.WithStack(err)
   188  	}
   189  	defer httpRsp.Body.Close()
   190  
   191  	b, err := io.ReadAll(httpRsp.Body)
   192  	if err != nil {
   193  		return nil, perrors.WithStack(err)
   194  	}
   195  
   196  	if httpRsp.StatusCode != http.StatusOK {
   197  		return nil, perrors.New(fmt.Sprintf("http status:%q, error string:%q", httpRsp.Status, string(b)))
   198  	}
   199  
   200  	return b, nil
   201  }