github.com/vcilabs/webrpc@v0.5.2-0.20201116131534-162e27b1b33b/gen/golang/templates/client.go.tmpl (about)

     1  {{define "client"}}
     2  {{if .Services}}
     3  //
     4  // Client
     5  //
     6  
     7  {{range .Services}}
     8  const {{.Name | constPathPrefix}} = "/rpc/{{.Name}}/"
     9  {{end}}
    10  
    11  {{range .Services}}
    12    {{ $serviceName := .Name | clientServiceName}}
    13    type {{$serviceName}} struct {
    14      client HTTPClient
    15      urls   [{{.Methods | countMethods}}]string
    16    }
    17  
    18    func {{.Name | newClientServiceName }}(addr string, client HTTPClient) {{.Name}} {
    19      prefix := urlBase(addr) + {{.Name | constPathPrefix}}
    20      urls := [{{.Methods | countMethods}}]string{
    21        {{- range .Methods}}
    22        prefix + "{{.Name}}",
    23        {{- end}}
    24      }
    25      return &{{$serviceName}}{
    26        client: client,
    27        urls:   urls,
    28      }
    29    }
    30  
    31    {{range $i, $method := .Methods}}
    32      func (c *{{$serviceName}}) {{.Name}}({{.Inputs | methodInputs}}) ({{.Outputs | methodOutputs }}) {
    33        {{- $inputVar := "nil" -}}
    34        {{- $outputVar := "nil" -}}
    35        {{- if .Inputs | len}}
    36        {{- $inputVar = "in"}}
    37        in := struct {
    38          {{- range $i, $input := .Inputs}}
    39            Arg{{$i}} {{$input | methodArgType}} `json:"{{$input.Name | downcaseName}}"`
    40          {{- end}}          
    41        }{ {{.Inputs | methodArgNames}} }
    42        {{- end}}
    43        {{- if .Outputs | len}}
    44        {{- $outputVar = "&out"}}
    45        out := struct {
    46          {{- range $i, $output := .Outputs}}
    47            Ret{{$i}} {{$output | methodArgType}} `json:"{{$output.Name | downcaseName}}"`
    48          {{- end}}          
    49        }{}
    50      {{- end}}
    51  
    52        err := doJSONRequest(ctx, c.client, c.urls[{{$i}}], {{$inputVar}}, {{$outputVar}})
    53        return {{argsList .Outputs "out.Ret"}}{{commaIfLen .Outputs}} err
    54      }
    55    {{end}}
    56  {{end}}
    57  
    58  // HTTPClient is the interface used by generated clients to send HTTP requests.
    59  // It is fulfilled by *(net/http).Client, which is sufficient for most users.
    60  // Users can provide their own implementation for special retry policies.
    61  type HTTPClient interface {
    62    Do(req *http.Request) (*http.Response, error)
    63  }
    64  
    65  // urlBase helps ensure that addr specifies a scheme. If it is unparsable
    66  // as a URL, it returns addr unchanged.
    67  func urlBase(addr string) string {
    68    // If the addr specifies a scheme, use it. If not, default to
    69    // http. If url.Parse fails on it, return it unchanged.
    70    url, err := url.Parse(addr)
    71    if err != nil {
    72      return addr
    73    }
    74    if url.Scheme == "" {
    75      url.Scheme = "http"
    76    }
    77    return url.String()
    78  }
    79  
    80  // newRequest makes an http.Request from a client, adding common headers.
    81  func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType string) (*http.Request, error) {
    82    req, err := http.NewRequest("POST", url, reqBody)
    83    if err != nil {
    84      return nil, err
    85    }
    86    req.Header.Set("Accept", contentType)
    87    req.Header.Set("Content-Type", contentType)
    88  	if headers, ok := HTTPRequestHeaders(ctx); ok {
    89  		for k := range headers {
    90  			for _, v := range headers[k] {
    91  				req.Header.Add(k, v)
    92  			}
    93  		}
    94  	}
    95    return req, nil
    96  }
    97  
    98  // doJSONRequest is common code to make a request to the remote service.
    99  func doJSONRequest(ctx context.Context, client HTTPClient, url string, in, out interface{}) error {
   100  	reqBody, err := json.Marshal(in)
   101  	if err != nil {
   102  		return clientError("failed to marshal json request", err)
   103  	}
   104  	if err = ctx.Err(); err != nil {
   105  		return clientError("aborted because context was done", err)
   106  	}
   107  
   108  	req, err := newRequest(ctx, url, bytes.NewBuffer(reqBody), "application/json")
   109  	if err != nil {
   110  		return clientError("could not build request", err)
   111  	}
   112  	resp, err := client.Do(req)
   113  	if err != nil {
   114  		return clientError("request failed", err)
   115  	}
   116  
   117  	defer func() {
   118  		cerr := resp.Body.Close()
   119  		if err == nil && cerr != nil {
   120  			err = clientError("failed to close response body", cerr)
   121  		}
   122  	}()
   123  
   124  	if err = ctx.Err(); err != nil {
   125  		return clientError("aborted because context was done", err)
   126  	}
   127  
   128  	if resp.StatusCode != 200 {
   129  		return errorFromResponse(resp)
   130  	}
   131  
   132  	if out != nil {
   133  		respBody, err := ioutil.ReadAll(resp.Body)
   134  		if err != nil {
   135  			return clientError("failed to read response body", err)
   136  		}
   137  
   138  		err = json.Unmarshal(respBody, &out)
   139  		if err != nil {
   140  			return clientError("failed to unmarshal json response body", err)
   141  		}
   142  		if err = ctx.Err(); err != nil {
   143  			return clientError("aborted because context was done", err)
   144  		}
   145  	}
   146  
   147  	return nil
   148  }
   149  
   150  // errorFromResponse builds a webrpc Error from a non-200 HTTP response.
   151  func errorFromResponse(resp *http.Response) Error {
   152  	respBody, err := ioutil.ReadAll(resp.Body)
   153  	if err != nil {
   154  		return clientError("failed to read server error response body", err)
   155  	}
   156  
   157  	var respErr ErrorPayload
   158  	if err := json.Unmarshal(respBody, &respErr); err != nil {
   159  		return clientError("failed unmarshal error response", err)
   160  	}
   161  
   162  	errCode := ErrorCode(respErr.Code)
   163  
   164  	if HTTPStatusFromErrorCode(errCode) == 0 {
   165  		return ErrorInternal("invalid code returned from server error response: %s", respErr.Code)
   166  	}
   167  
   168  	return &rpcErr{
   169  		code:  errCode,
   170  		msg:   respErr.Msg,
   171  		cause: errors.New(respErr.Cause),
   172  	}
   173  }
   174  
   175  func clientError(desc string, err error) Error {
   176  	return WrapError(ErrInternal, err, desc)
   177  }
   178  
   179  func WithHTTPRequestHeaders(ctx context.Context, h http.Header) (context.Context, error) {
   180  	if _, ok := h["Accept"]; ok {
   181  		return nil, errors.New("provided header cannot set Accept")
   182  	}
   183  	if _, ok := h["Content-Type"]; ok {
   184  		return nil, errors.New("provided header cannot set Content-Type")
   185  	}
   186  
   187  	copied := make(http.Header, len(h))
   188  	for k, vv := range h {
   189  		if vv == nil {
   190  			copied[k] = nil
   191  			continue
   192  		}
   193  		copied[k] = make([]string, len(vv))
   194  		copy(copied[k], vv)
   195  	}
   196  
   197  	return context.WithValue(ctx, HTTPClientRequestHeadersCtxKey, copied), nil
   198  }
   199  
   200  func HTTPRequestHeaders(ctx context.Context) (http.Header, bool) {
   201  	h, ok := ctx.Value(HTTPClientRequestHeadersCtxKey).(http.Header)
   202  	return h, ok
   203  }
   204  {{end}}
   205  {{end}}