Merge pull request #75 from hasAdamr/context-to-transport-to-transport-to-context
Context to transport to transport to context
This commit is contained in:
Коммит
8c37fb79e0
|
@ -325,7 +325,6 @@ func applyTemplate(templBytes []byte, templName string, executor *templateExecut
|
|||
templateString := string(templBytes)
|
||||
|
||||
codeTemplate, err := template.New(templName).Funcs(executor.funcMap).Parse(templateString)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create template")
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ import (
|
|||
type Helper struct {
|
||||
Methods []*Method
|
||||
PathParamsBuilder string
|
||||
ServerTemplate func(interface{}) (string, error)
|
||||
ClientTemplate func(interface{}) (string, error)
|
||||
}
|
||||
|
||||
// NewHelper builds a helper struct from a service declaration. The other
|
||||
|
@ -35,6 +37,8 @@ func NewHelper(svc *deftree.ProtoService) *Helper {
|
|||
pp := FormatCode(HTTPAssistFuncs)
|
||||
rv := Helper{
|
||||
PathParamsBuilder: pp,
|
||||
ServerTemplate: GenServerTemplate,
|
||||
ClientTemplate: GenClientTemplate,
|
||||
}
|
||||
for _, meth := range svc.Methods {
|
||||
if len(meth.HttpBindings) > 0 {
|
||||
|
@ -148,6 +152,24 @@ func NewBinding(i int, meth *deftree.ServiceMethod) *Binding {
|
|||
return &nBinding
|
||||
}
|
||||
|
||||
func GenServerTemplate(exec interface{}) (string, error) {
|
||||
code, err := ApplyTemplate("ServerTemplate", serverTemplate, exec, TemplateFuncs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
code = FormatCode(code)
|
||||
return code, nil
|
||||
}
|
||||
|
||||
func GenClientTemplate(exec interface{}) (string, error) {
|
||||
code, err := ApplyTemplate("ClientTemplate", clientTemplate, exec, TemplateFuncs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
code = FormatCode(code)
|
||||
return code, nil
|
||||
}
|
||||
|
||||
// GenServerDecode returns the generated code for the server-side decoding of
|
||||
// an http request into its request struct.
|
||||
func (b *Binding) GenServerDecode() (string, error) {
|
||||
|
|
|
@ -3,102 +3,102 @@ package httptransport
|
|||
// ServerDecodeTemplate is the template for generating the server-side decoding
|
||||
// function for a particular Binding.
|
||||
var ServerDecodeTemplate = `
|
||||
{{ with $binding := .}}
|
||||
// DecodeHTTP{{$binding.Label}}Request is a transport/http.DecodeRequestFunc that
|
||||
// decodes a JSON-encoded {{ToLower $binding.Parent.Name}} request from the HTTP request
|
||||
// body. Primarily useful in a server.
|
||||
func DecodeHTTP{{$binding.Label}}Request(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
var req pb.{{GoName $binding.Parent.RequestType}}
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
// err = io.EOF if r.Body was empty
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, errors.Wrap(err, "decoding body of http request")
|
||||
}
|
||||
{{- with $binding := . -}}
|
||||
// DecodeHTTP{{$binding.Label}}Request is a transport/http.DecodeRequestFunc that
|
||||
// decodes a JSON-encoded {{ToLower $binding.Parent.Name}} request from the HTTP request
|
||||
// body. Primarily useful in a server.
|
||||
func DecodeHTTP{{$binding.Label}}Request(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
var req pb.{{GoName $binding.Parent.RequestType}}
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
// err = io.EOF if r.Body was empty
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, errors.Wrap(err, "decoding body of http request")
|
||||
}
|
||||
|
||||
pathParams, err := PathParams(r.URL.Path, "{{$binding.PathTemplate}}")
|
||||
_ = pathParams
|
||||
if err != nil {
|
||||
fmt.Printf("Error while reading path params: %v\n", err)
|
||||
return nil, errors.Wrap(err, "couldn't unmarshal path parameters")
|
||||
pathParams, err := PathParams(r.URL.Path, "{{$binding.PathTemplate}}")
|
||||
_ = pathParams
|
||||
if err != nil {
|
||||
fmt.Printf("Error while reading path params: %v\n", err)
|
||||
return nil, errors.Wrap(err, "couldn't unmarshal path parameters")
|
||||
}
|
||||
queryParams, err := QueryParams(r.URL.Query())
|
||||
_ = queryParams
|
||||
if err != nil {
|
||||
fmt.Printf("Error while reading query params: %v\n", err)
|
||||
return nil, errors.Wrapf(err, "Error while reading query params: %v", r.URL.Query())
|
||||
}
|
||||
{{range $field := $binding.Fields}}
|
||||
{{if ne $field.Location "body"}}
|
||||
{{$field.GenQueryUnmarshaler}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
return &req, err
|
||||
}
|
||||
queryParams, err := QueryParams(r.URL.Query())
|
||||
_ = queryParams
|
||||
if err != nil {
|
||||
fmt.Printf("Error while reading query params: %v\n", err)
|
||||
return nil, errors.Wrapf(err, "Error while reading query params: %v", r.URL.Query())
|
||||
}
|
||||
{{range $field := $binding.Fields}}
|
||||
{{if ne $field.Location "body"}}
|
||||
{{$field.GenQueryUnmarshaler}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
return &req, err
|
||||
}
|
||||
{{end}}
|
||||
{{- end -}}
|
||||
`
|
||||
|
||||
// ClientEncodeTemplate is the template for generating the client-side encoding
|
||||
// function for a particular Binding.
|
||||
var ClientEncodeTemplate = `
|
||||
{{ with $binding := .}}
|
||||
// EncodeHTTP{{$binding.Label}}Request is a transport/http.EncodeRequestFunc
|
||||
// that encodes a {{ToLower $binding.Parent.Name}} request into the various portions of
|
||||
// the http request (path, query, and body).
|
||||
func EncodeHTTP{{$binding.Label}}Request(_ context.Context, r *http.Request, request interface{}) error {
|
||||
fmt.Printf("Encoding request %v\n", request)
|
||||
req := request.(*pb.{{GoName $binding.Parent.RequestType}})
|
||||
_ = req
|
||||
{{- with $binding := . -}}
|
||||
// EncodeHTTP{{$binding.Label}}Request is a transport/http.EncodeRequestFunc
|
||||
// that encodes a {{ToLower $binding.Parent.Name}} request into the various portions of
|
||||
// the http request (path, query, and body).
|
||||
func EncodeHTTP{{$binding.Label}}Request(_ context.Context, r *http.Request, request interface{}) error {
|
||||
fmt.Printf("Encoding request %v\n", request)
|
||||
req := request.(*pb.{{GoName $binding.Parent.RequestType}})
|
||||
_ = req
|
||||
|
||||
// Set the path parameters
|
||||
path := strings.Join([]string{
|
||||
{{- range $section := $binding.PathSections}}
|
||||
{{$section}},
|
||||
{{- end}}
|
||||
}, "/")
|
||||
u, err := url.Parse(path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "couldn't unmarshal path %q", path)
|
||||
}
|
||||
r.URL.RawPath = u.RawPath
|
||||
r.URL.Path = u.Path
|
||||
|
||||
// Set the query parameters
|
||||
values := r.URL.Query()
|
||||
var tmp []byte
|
||||
_ = tmp
|
||||
{{- range $field := $binding.Fields }}
|
||||
{{- if eq $field.Location "query"}}
|
||||
{{if or (not $field.IsBaseType) $field.Repeated}}
|
||||
tmp, err = json.Marshal(req.{{$field.CamelName}})
|
||||
// Set the path parameters
|
||||
path := strings.Join([]string{
|
||||
{{- range $section := $binding.PathSections}}
|
||||
{{$section}},
|
||||
{{- end}}
|
||||
}, "/")
|
||||
u, err := url.Parse(path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal req.{{$field.CamelName}}")
|
||||
return errors.Wrapf(err, "couldn't unmarshal path %q", path)
|
||||
}
|
||||
values.Add("{{$field.Name}}", string(tmp))
|
||||
{{else}}
|
||||
values.Add("{{$field.Name}}", fmt.Sprint(req.{{$field.CamelName}}))
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end}}
|
||||
r.URL.RawPath = u.RawPath
|
||||
r.URL.Path = u.Path
|
||||
|
||||
r.URL.RawQuery = values.Encode()
|
||||
// Set the query parameters
|
||||
values := r.URL.Query()
|
||||
var tmp []byte
|
||||
_ = tmp
|
||||
{{- range $field := $binding.Fields }}
|
||||
{{- if eq $field.Location "query"}}
|
||||
{{if or (not $field.IsBaseType) $field.Repeated}}
|
||||
tmp, err = json.Marshal(req.{{$field.CamelName}})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal req.{{$field.CamelName}}")
|
||||
}
|
||||
values.Add("{{$field.Name}}", string(tmp))
|
||||
{{else}}
|
||||
values.Add("{{$field.Name}}", fmt.Sprint(req.{{$field.CamelName}}))
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end}}
|
||||
|
||||
// Set the body parameters
|
||||
var buf bytes.Buffer
|
||||
toRet := map[string]interface{}{
|
||||
{{- range $field := $binding.Fields -}}
|
||||
{{if eq $field.Location "body"}}
|
||||
"{{$field.CamelName}}" : req.{{$field.CamelName}},
|
||||
{{end}}
|
||||
r.URL.RawQuery = values.Encode()
|
||||
|
||||
// Set the body parameters
|
||||
var buf bytes.Buffer
|
||||
toRet := map[string]interface{}{
|
||||
{{- range $field := $binding.Fields -}}
|
||||
{{if eq $field.Location "body"}}
|
||||
"{{$field.CamelName}}" : req.{{$field.CamelName}},
|
||||
{{end}}
|
||||
{{- end -}}
|
||||
}
|
||||
if err := json.NewEncoder(&buf).Encode(toRet); err != nil {
|
||||
return errors.Wrapf(err, "couldn't encode body as json %v", toRet)
|
||||
}
|
||||
r.Body = ioutil.NopCloser(&buf)
|
||||
fmt.Printf("URL: %v\n", r.URL)
|
||||
return nil
|
||||
}
|
||||
{{- end -}}
|
||||
}
|
||||
if err := json.NewEncoder(&buf).Encode(toRet); err != nil {
|
||||
return errors.Wrapf(err, "couldn't encode body as json %v", toRet)
|
||||
}
|
||||
r.Body = ioutil.NopCloser(&buf)
|
||||
fmt.Printf("URL: %v\n", r.URL)
|
||||
return nil
|
||||
}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
// WARNING: Changing the contents of these strings, even a little bit, will cause tests
|
||||
|
@ -187,3 +187,255 @@ func QueryParams(vals url.Values) (map[string]string, error) {
|
|||
// for encoding and decoding http request to and from generated protobuf
|
||||
// structs, and is used within the generated code of each microservice.
|
||||
var HTTPAssistFuncs = PathParamsTemplate + BuildParamMapTemplate + RemoveBracesTemplate + QueryParamsTemplate
|
||||
|
||||
var serverTemplate = `
|
||||
package svc
|
||||
|
||||
// This file provides server-side bindings for the HTTP transport.
|
||||
// It utilizes the transport/http.Server.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"io"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/pkg/errors"
|
||||
httptransport "github.com/go-kit/kit/transport/http"
|
||||
|
||||
// This service
|
||||
pb "{{.PBImportPath -}}"
|
||||
)
|
||||
|
||||
var (
|
||||
_ = fmt.Sprint
|
||||
_ = bytes.Compare
|
||||
_ = strconv.Atoi
|
||||
_ = httptransport.NewServer
|
||||
_ = ioutil.NopCloser
|
||||
_ = pb.Register{{.Service.GetName}}Server
|
||||
_ = io.Copy
|
||||
)
|
||||
|
||||
// MakeHTTPHandler returns a handler that makes a set of endpoints available
|
||||
// on predefined paths.
|
||||
func MakeHTTPHandler(ctx context.Context, endpoints Endpoints, logger log.Logger) http.Handler {
|
||||
serverOptions := []httptransport.ServerOption{
|
||||
httptransport.ServerBefore(headersToContext),
|
||||
}
|
||||
m := http.NewServeMux()
|
||||
|
||||
{{range $method := .HTTPHelper.Methods}}
|
||||
{{range $binding := $method.Bindings}}
|
||||
m.Handle("{{ToLower $binding.BasePath}}", httptransport.NewServer(
|
||||
ctx,
|
||||
endpoints.{{$method.Name}}Endpoint,
|
||||
HttpDecodeLogger(DecodeHTTP{{$binding.Label}}Request, logger),
|
||||
EncodeHTTPGenericResponse,
|
||||
serverOptions...,
|
||||
))
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
return m
|
||||
}
|
||||
|
||||
func HttpDecodeLogger(next httptransport.DecodeRequestFunc, logger log.Logger) httptransport.DecodeRequestFunc {
|
||||
return func(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
logger.Log("method", r.Method, "url", r.URL.String())
|
||||
rv, err := next(ctx, r)
|
||||
if err != nil {
|
||||
logger.Log("method", r.Method, "url", r.URL.String(), "Error", err)
|
||||
}
|
||||
return rv, err
|
||||
}
|
||||
}
|
||||
|
||||
func errorEncoder(_ context.Context, err error, w http.ResponseWriter) {
|
||||
code := http.StatusInternalServerError
|
||||
msg := err.Error()
|
||||
|
||||
w.WriteHeader(code)
|
||||
json.NewEncoder(w).Encode(errorWrapper{Error: msg})
|
||||
}
|
||||
|
||||
func errorDecoder(r *http.Response) error {
|
||||
var w errorWrapper
|
||||
if err := json.NewDecoder(r.Body).Decode(&w); err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New(w.Error)
|
||||
}
|
||||
|
||||
type errorWrapper struct {
|
||||
Error string ` + "`json:\"error\"`\n" + `
|
||||
}
|
||||
|
||||
// Server Decode
|
||||
{{range $method := .HTTPHelper.Methods}}
|
||||
{{range $binding := $method.Bindings}}
|
||||
{{$binding.GenServerDecode}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
// Client Decode
|
||||
{{range $method := .HTTPHelper.Methods}}
|
||||
// DecodeHTTP{{$method.Name}} is a transport/http.DecodeResponseFunc that decodes
|
||||
// a JSON-encoded {{GoName $method.ResponseType}} response from the HTTP response body.
|
||||
// If the response has a non-200 status code, we will interpret that as an
|
||||
// error and attempt to decode the specific error message from the response
|
||||
// body. Primarily useful in a client.
|
||||
func DecodeHTTP{{$method.Name}}Response(_ context.Context, r *http.Response) (interface{}, error) {
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return nil, errorDecoder(r)
|
||||
}
|
||||
var resp pb.{{GoName $method.ResponseType}}
|
||||
err := json.NewDecoder(r.Body).Decode(&resp)
|
||||
return &resp, err
|
||||
}
|
||||
{{end}}
|
||||
|
||||
// Client Encode
|
||||
{{range $method := .HTTPHelper.Methods}}
|
||||
{{range $binding := $method.Bindings}}
|
||||
{{$binding.GenClientEncode}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
// EncodeHTTPGenericResponse is a transport/http.EncodeResponseFunc that encodes
|
||||
// the response as JSON to the response writer. Primarily useful in a server.
|
||||
func EncodeHTTPGenericResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
{{.HTTPHelper.PathParamsBuilder}}
|
||||
|
||||
func headersToContext(ctx context.Context, r *http.Request) context.Context {
|
||||
for k, _ := range r.Header {
|
||||
ctx = context.WithValue(ctx, k, r.Header.Get(k))
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
`
|
||||
|
||||
var clientTemplate = `
|
||||
// Package http provides an HTTP client for the {{.Service.GetName}} service.
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
httptransport "github.com/go-kit/kit/transport/http"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
// This Service
|
||||
handler "{{.ImportPath -}} /handlers/server"
|
||||
svc "{{.ImportPath -}} /generated"
|
||||
)
|
||||
|
||||
var (
|
||||
_ = endpoint.Chain
|
||||
_ = httptransport.NewClient
|
||||
)
|
||||
|
||||
// New returns a service backed by an HTTP server living at the remote
|
||||
// instance. We expect instance to come from a service discovery system, so
|
||||
// likely of the form "host:port".
|
||||
func New(instance string, options ...ClientOption) (handler.Service, error) {
|
||||
var cc clientConfig
|
||||
|
||||
for _, f := range options {
|
||||
err := f(&cc)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot apply option")
|
||||
}
|
||||
}
|
||||
|
||||
clientOptions := []httptransport.ClientOption{
|
||||
httptransport.ClientBefore(
|
||||
contextValuesToHttpHeaders(cc.headers)),
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(instance, "http") {
|
||||
instance = "http://" + instance
|
||||
}
|
||||
u, err := url.Parse(instance)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = u
|
||||
|
||||
{{range $method := .HTTPHelper.Methods}}
|
||||
{{range $binding := $method.Bindings}}
|
||||
var {{$binding.Label}}Endpoint endpoint.Endpoint
|
||||
{
|
||||
{{$binding.Label}}Endpoint = httptransport.NewClient(
|
||||
"{{$binding.Verb}}",
|
||||
copyURL(u, "{{ToLower $binding.BasePath}}"),
|
||||
svc.EncodeHTTP{{$binding.Label}}Request,
|
||||
svc.DecodeHTTP{{$method.Name}}Response,
|
||||
clientOptions...,
|
||||
).Endpoint()
|
||||
}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
|
||||
return svc.Endpoints{
|
||||
{{range $method := .HTTPHelper.Methods -}}
|
||||
{{range $binding := $method.Bindings -}}
|
||||
{{$method.Name}}Endpoint: {{$binding.Label}}Endpoint,
|
||||
{{end}}
|
||||
{{- end}}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func copyURL(base *url.URL, path string) *url.URL {
|
||||
next := *base
|
||||
next.Path = path
|
||||
return &next
|
||||
}
|
||||
|
||||
type clientConfig struct {
|
||||
headers []string
|
||||
}
|
||||
|
||||
// ClientOption is a function that modifies the client config
|
||||
type ClientOption func(*clientConfig) error
|
||||
|
||||
// CtxValuesToSend configures the http client to pull the specified keys out of
|
||||
// the context and add them to the http request as headers. Note that keys
|
||||
// will have net/http.CanonicalHeaderKey called on them before being send over
|
||||
// the wire and that is the form they will be available in the server context.
|
||||
func CtxValuesToSend(keys ...string) ClientOption {
|
||||
return func(o *clientConfig) error {
|
||||
o.headers = keys
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func contextValuesToHttpHeaders(keys []string) httptransport.RequestFunc {
|
||||
return func(ctx context.Context, r *http.Request) context.Context {
|
||||
for _, k := range keys {
|
||||
if v, ok := ctx.Value(k).(string); ok {
|
||||
r.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package httptransport
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/TuneLab/go-truss/gengokit/gentesthelper"
|
||||
|
@ -97,7 +98,7 @@ func EncodeHTTPSumZeroRequest(_ context.Context, r *http.Request, request interf
|
|||
}
|
||||
|
||||
`
|
||||
if got, want := str, desired; got != want {
|
||||
if got, want := strings.TrimSpace(str), strings.TrimSpace(desired); got != want {
|
||||
t.Errorf("Generated code differs from result.\ngot = %s\nwant = %s", got, want)
|
||||
t.Log(gentesthelper.DiffStrings(got, want))
|
||||
}
|
||||
|
@ -202,7 +203,7 @@ func DecodeHTTPSumZeroRequest(_ context.Context, r *http.Request) (interface{},
|
|||
}
|
||||
|
||||
`
|
||||
if got, want := str, desired; got != want {
|
||||
if got, want := strings.TrimSpace(str), strings.TrimSpace(desired); got != want {
|
||||
t.Errorf("Generated code differs from result.\ngot = %s\nwant = %s", got, want)
|
||||
t.Log(gentesthelper.DiffStrings(got, want))
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@ import (
|
|||
// New returns an service backed by a gRPC client connection. It is the
|
||||
// responsibility of the caller to dial, and later close, the connection.
|
||||
func New(conn *grpc.ClientConn) handler.Service {
|
||||
//options := []grpctransport.ServerOption{
|
||||
//grpctransport.ServerBefore(),
|
||||
//options := []grpctransport.ClientOptions{
|
||||
//grpctransport.ClientBefore(),
|
||||
//}
|
||||
|
||||
{{- with $tE := .}}
|
||||
|
|
|
@ -1,68 +1,2 @@
|
|||
// Package http provides an HTTP client for the {{.Service.GetName}} service.
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
httptransport "github.com/go-kit/kit/transport/http"
|
||||
|
||||
// This Service
|
||||
handler "{{.ImportPath -}} /handlers/server"
|
||||
svc "{{.ImportPath -}} /generated"
|
||||
)
|
||||
|
||||
var (
|
||||
_ = endpoint.Chain
|
||||
_ = httptransport.NewClient
|
||||
)
|
||||
|
||||
// New returns a service backed by an HTTP server living at the remote
|
||||
// instance. We expect instance to come from a service discovery system, so
|
||||
// likely of the form "host:port".
|
||||
func New(instance string) (handler.Service, error) {
|
||||
//options := []httptransport.ServerOption{
|
||||
//httptransport.ServerBefore(),
|
||||
//}
|
||||
|
||||
if !strings.HasPrefix(instance, "http") {
|
||||
instance = "http://" + instance
|
||||
}
|
||||
u, err := url.Parse(instance)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = u
|
||||
|
||||
{{range $method := .HTTPHelper.Methods}}
|
||||
{{range $binding := $method.Bindings}}
|
||||
var {{$binding.Label}}Endpoint endpoint.Endpoint
|
||||
{
|
||||
{{$binding.Label}}Endpoint = httptransport.NewClient(
|
||||
"{{$binding.Verb}}",
|
||||
copyURL(u, "{{ToLower $binding.BasePath}}"),
|
||||
svc.EncodeHTTP{{$binding.Label}}Request,
|
||||
svc.DecodeHTTP{{$method.Name}}Response,
|
||||
//options...,
|
||||
).Endpoint()
|
||||
}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
|
||||
return svc.Endpoints{
|
||||
{{range $method := .HTTPHelper.Methods -}}
|
||||
{{range $binding := $method.Bindings -}}
|
||||
{{$method.Name}}Endpoint: {{$binding.Label}}Endpoint,
|
||||
{{end}}
|
||||
{{- end}}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func copyURL(base *url.URL, path string) *url.URL {
|
||||
next := *base
|
||||
next.Path = path
|
||||
return &next
|
||||
}
|
||||
|
||||
{{/* See go-truss/gengokit/httptransport/template.go for code */}}
|
||||
{{call .HTTPHelper.ClientTemplate .}}
|
||||
|
|
|
@ -1,134 +1,2 @@
|
|||
package svc
|
||||
|
||||
// This file provides server-side bindings for the HTTP transport.
|
||||
// It utilizes the transport/http.Server.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"io"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/pkg/errors"
|
||||
httptransport "github.com/go-kit/kit/transport/http"
|
||||
|
||||
// This service
|
||||
pb "{{.PBImportPath -}}"
|
||||
)
|
||||
|
||||
var (
|
||||
_ = fmt.Sprint
|
||||
_ = bytes.Compare
|
||||
_ = strconv.Atoi
|
||||
_ = httptransport.NewServer
|
||||
_ = ioutil.NopCloser
|
||||
_ = pb.Register{{.Service.GetName}}Server
|
||||
_ = io.Copy
|
||||
)
|
||||
|
||||
// MakeHTTPHandler returns a handler that makes a set of endpoints available
|
||||
// on predefined paths.
|
||||
func MakeHTTPHandler(ctx context.Context, endpoints Endpoints, logger log.Logger) http.Handler {
|
||||
/*options := []httptransport.ServerOption{
|
||||
httptransport.ServerErrorEncoder(errorEncoder),
|
||||
httptransport.ServerErrorLogger(logger),
|
||||
}*/
|
||||
m := http.NewServeMux()
|
||||
|
||||
{{range $method := .HTTPHelper.Methods}}
|
||||
{{range $binding := $method.Bindings}}
|
||||
m.Handle("{{ToLower $binding.BasePath}}", httptransport.NewServer(
|
||||
ctx,
|
||||
endpoints.{{$method.Name}}Endpoint,
|
||||
HttpDecodeLogger(DecodeHTTP{{$binding.Label}}Request, logger),
|
||||
EncodeHTTPGenericResponse,
|
||||
//options...,
|
||||
))
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
return m
|
||||
}
|
||||
|
||||
func HttpDecodeLogger(next httptransport.DecodeRequestFunc, logger log.Logger) httptransport.DecodeRequestFunc {
|
||||
return func(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
logger.Log("method", r.Method, "url", r.URL.String())
|
||||
rv, err := next(ctx, r)
|
||||
if err != nil {
|
||||
logger.Log("method", r.Method, "url", r.URL.String(), "Error", err)
|
||||
}
|
||||
return rv, err
|
||||
}
|
||||
}
|
||||
|
||||
func errorEncoder(_ context.Context, err error, w http.ResponseWriter) {
|
||||
code := http.StatusInternalServerError
|
||||
msg := err.Error()
|
||||
|
||||
w.WriteHeader(code)
|
||||
json.NewEncoder(w).Encode(errorWrapper{Error: msg})
|
||||
}
|
||||
|
||||
func errorDecoder(r *http.Response) error {
|
||||
var w errorWrapper
|
||||
if err := json.NewDecoder(r.Body).Decode(&w); err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New(w.Error)
|
||||
}
|
||||
|
||||
type errorWrapper struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// Server Decode
|
||||
{{range $method := .HTTPHelper.Methods}}
|
||||
{{range $binding := $method.Bindings}}
|
||||
|
||||
{{$binding.GenServerDecode}}
|
||||
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
// Client Decode
|
||||
{{range $method := .HTTPHelper.Methods}}
|
||||
// DecodeHTTP{{$method.Name}} is a transport/http.DecodeResponseFunc that decodes
|
||||
// a JSON-encoded {{GoName $method.ResponseType}} response from the HTTP response body.
|
||||
// If the response has a non-200 status code, we will interpret that as an
|
||||
// error and attempt to decode the specific error message from the response
|
||||
// body. Primarily useful in a client.
|
||||
func DecodeHTTP{{$method.Name}}Response(_ context.Context, r *http.Response) (interface{}, error) {
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return nil, errorDecoder(r)
|
||||
}
|
||||
var resp pb.{{GoName $method.ResponseType}}
|
||||
err := json.NewDecoder(r.Body).Decode(&resp)
|
||||
return &resp, err
|
||||
}
|
||||
{{end}}
|
||||
|
||||
// Client Encode
|
||||
{{range $method := .HTTPHelper.Methods}}
|
||||
{{range $binding := $method.Bindings}}
|
||||
|
||||
{{$binding.GenClientEncode}}
|
||||
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
// EncodeHTTPGenericResponse is a transport/http.EncodeResponseFunc that encodes
|
||||
// the response as JSON to the response writer. Primarily useful in a server.
|
||||
func EncodeHTTPGenericResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
{{.HTTPHelper.PathParamsBuilder}}
|
||||
{{/* See go-truss/gengokit/httptransport/template.go for code */}}
|
||||
{{call .HTTPHelper.ServerTemplate .}}
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -24,7 +24,6 @@ type httptestService struct{}
|
|||
|
||||
// GetWithQuery implements Service.
|
||||
func (s httptestService) GetWithQuery(ctx context.Context, in *pb.GetWithQueryRequest) (*pb.GetWithQueryResponse, error) {
|
||||
|
||||
response := pb.GetWithQueryResponse{
|
||||
V: in.A + in.B,
|
||||
}
|
||||
|
@ -49,16 +48,31 @@ func (s httptestService) GetWithRepeatedQuery(ctx context.Context, in *pb.GetWit
|
|||
|
||||
// PostWithNestedMessageBody implements Service.
|
||||
func (s httptestService) PostWithNestedMessageBody(ctx context.Context, in *pb.PostWithNestedMessageBodyRequest) (*pb.PostWithNestedMessageBodyResponse, error) {
|
||||
_ = ctx
|
||||
_ = in
|
||||
response := pb.PostWithNestedMessageBodyResponse{
|
||||
V: in.NM.A + in.NM.B,
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// CtxtToCtxtViaHTTPHeader implements Service.
|
||||
func (s httptestService) CtxToCtxViaHTTPHeader(ctx context.Context, in *pb.HeaderRequest) (*pb.HeaderResponse, error) {
|
||||
var resp pb.HeaderResponse
|
||||
val := ctx.Value(in.HeaderKey)
|
||||
|
||||
if v, ok := val.(string); ok {
|
||||
resp.V = v
|
||||
} else if val == nil {
|
||||
resp.V = "CONTEXT VALUE FOR KEY IS NILL"
|
||||
} else {
|
||||
resp.V = "CONTEXT VALUE FOR KEY IS NON STRING"
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
GetWithQuery(ctx context.Context, in *pb.GetWithQueryRequest) (*pb.GetWithQueryResponse, error)
|
||||
GetWithRepeatedQuery(ctx context.Context, in *pb.GetWithRepeatedQueryRequest) (*pb.GetWithRepeatedQueryResponse, error)
|
||||
PostWithNestedMessageBody(ctx context.Context, in *pb.PostWithNestedMessageBodyRequest) (*pb.PostWithNestedMessageBodyResponse, error)
|
||||
CtxToCtxViaHTTPHeader(ctx context.Context, in *pb.HeaderRequest) (*pb.HeaderResponse, error)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
|
||||
// 3d Party
|
||||
"golang.org/x/net/context"
|
||||
|
@ -43,11 +44,13 @@ func init() {
|
|||
getWithQueryE := svc.MakeGetWithQueryEndpoint(service)
|
||||
getWithRepeatedQueryE := svc.MakeGetWithRepeatedQueryEndpoint(service)
|
||||
postWithNestedMessageBodyE := svc.MakePostWithNestedMessageBodyEndpoint(service)
|
||||
ctxToCtxViaHTTPHeaderE := svc.MakeCtxToCtxViaHTTPHeaderEndpoint(service)
|
||||
|
||||
endpoints := svc.Endpoints{
|
||||
GetWithQueryEndpoint: getWithQueryE,
|
||||
GetWithRepeatedQueryEndpoint: getWithRepeatedQueryE,
|
||||
PostWithNestedMessageBodyEndpoint: postWithNestedMessageBodyE,
|
||||
CtxToCtxViaHTTPHeaderEndpoint: ctxToCtxViaHTTPHeaderE,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
@ -91,6 +94,9 @@ func TestGetWithQueryRequest(t *testing.T) {
|
|||
route: fmt.Sprintf(routeFormat, routeFields...),
|
||||
body: bodyBytes,
|
||||
}.Test(t)
|
||||
if err != nil {
|
||||
t.Fatal(errors.Wrap(err, "cannot make http request"))
|
||||
}
|
||||
|
||||
err = json.Unmarshal(respBytes, &resp)
|
||||
if err != nil {
|
||||
|
@ -138,6 +144,9 @@ func TestGetWithRepeatedQueryRequest(t *testing.T) {
|
|||
route: fmt.Sprintf(routeFormat, routeFields...),
|
||||
body: bodyBytes,
|
||||
}.Test(t)
|
||||
if err != nil {
|
||||
t.Fatal(errors.Wrap(err, "cannot make http request"))
|
||||
}
|
||||
|
||||
err = json.Unmarshal(respBytes, &resp)
|
||||
if err != nil {
|
||||
|
@ -194,6 +203,9 @@ func TestPostWithNestedMessageBodyRequest(t *testing.T) {
|
|||
route: fmt.Sprintf(routeFormat, routeFields...),
|
||||
body: bodyBytes,
|
||||
}.Test(t)
|
||||
if err != nil {
|
||||
t.Fatal(errors.Wrap(err, "cannot make http request"))
|
||||
}
|
||||
|
||||
err = json.Unmarshal(respBytes, &resp)
|
||||
if err != nil {
|
||||
|
@ -210,6 +222,64 @@ func TestPostWithNestedMessageBodyRequest(t *testing.T) {
|
|||
testHTTP([]byte(jsonStr), "POST", "postwithnestedmessagebody")
|
||||
}
|
||||
|
||||
func TestCtxToCtxViaHTTPHeaderClient(t *testing.T) {
|
||||
var req pb.HeaderRequest
|
||||
var key, value = "Truss-Auth-Header", "SECRET"
|
||||
req.HeaderKey = key
|
||||
|
||||
// Create a new client telling it to send "Truss-Auth-Header" as a header
|
||||
svchttp, err := httpclient.New(httpserver.URL,
|
||||
httpclient.CtxValuesToSend(key))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create httpclient: %q", err)
|
||||
}
|
||||
|
||||
// Create a context with the header key and value
|
||||
ctx := context.WithValue(context.Background(), key, value)
|
||||
|
||||
// send the context
|
||||
resp, err := svchttp.CtxToCtxViaHTTPHeader(ctx, &req)
|
||||
if err != nil {
|
||||
t.Fatalf("httpclient returned error: %q", err)
|
||||
}
|
||||
|
||||
if resp.V != value {
|
||||
t.Fatalf("Expect: %q, got %q", value, resp.V)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCtxToCtxViaHTTPHeaderRequest(t *testing.T) {
|
||||
var resp pb.HeaderResponse
|
||||
var key, value = "Truss-Auth-Header", "SECRET"
|
||||
|
||||
jsonStr := fmt.Sprintf(`{ "HeaderKey": %q }`, key)
|
||||
fmt.Println(jsonStr)
|
||||
|
||||
req, err := http.NewRequest("POST", httpserver.URL+"/"+"ctxtoctx", strings.NewReader(jsonStr))
|
||||
if err != nil {
|
||||
t.Fatal(errors.Wrap(err, "cannot construct http request"))
|
||||
}
|
||||
|
||||
req.Header.Set(key, value)
|
||||
|
||||
respBytes, err := testHTTPRequest(req)
|
||||
if err != nil {
|
||||
t.Fatal(errors.Wrap(err, "cannot make http request"))
|
||||
}
|
||||
|
||||
err = json.Unmarshal(respBytes, &resp)
|
||||
if err != nil {
|
||||
t.Fatal(errors.Wrapf(err, "json error, got json: %q", string(respBytes)))
|
||||
}
|
||||
|
||||
if resp.V != value {
|
||||
t.Fatalf("Expect: %q, got %q", value, resp.V)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
type httpRequestBuilder struct {
|
||||
method string
|
||||
route string
|
||||
|
|
|
@ -21,6 +21,12 @@ service HTTPPermutations {
|
|||
body: "*"
|
||||
};
|
||||
}
|
||||
rpc CtxToCtxViaHTTPHeader (HeaderRequest) returns (HeaderResponse){
|
||||
option (google.api.http) = {
|
||||
post: "/ctxtoctx"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
message GetWithQueryRequest {
|
||||
|
@ -53,3 +59,10 @@ message PostWithNestedMessageBodyResponse {
|
|||
int64 V = 1;
|
||||
}
|
||||
|
||||
message HeaderRequest{
|
||||
string HeaderKey = 1;
|
||||
}
|
||||
|
||||
message HeaderResponse {
|
||||
string V = 1;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче