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}}