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 }