transport/http: add http handler (#937)

* add http handler

* fix request error
pull/939/head
Tony Chen 4 years ago committed by GitHub
parent ef6e52d1ba
commit b9f821c29f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .github/workflows/go.yml
  2. 57
      api/metadata/metadata_http.pb.go
  3. 58
      cmd/protoc-gen-go-errors/errors.go
  4. 8
      cmd/protoc-gen-go-errors/go.mod
  5. 95
      cmd/protoc-gen-go-errors/go.sum
  6. 35
      cmd/protoc-gen-go-errors/main.go
  7. 40
      cmd/protoc-gen-go-errors/template.go
  8. 33
      cmd/protoc-gen-go-http/template.go
  9. 139
      examples/blog/api/blog/v1/blog_http.pb.go
  10. 9
      examples/helloworld/helloworld/helloworld.pb.go
  11. 32
      examples/helloworld/helloworld/helloworld_http.pb.go
  12. 52
      examples/http/handler/main.go
  13. 12
      transport/http/binding/bind.go
  14. 1
      transport/http/binding/proto.go
  15. 134
      transport/http/handle.go
  16. 35
      transport/http/handle_test.go

@ -36,12 +36,6 @@ jobs:
go build ./...
go test ./...
- name: Errors
run: |
cd cmd/protoc-gen-go-errors
go build ./...
go test ./...
- name: Examples
run: |
cd examples

@ -26,64 +26,11 @@ type MetadataHandler interface {
}
func NewMetadataHandler(srv MetadataHandler, opts ...http1.HandleOption) http.Handler {
h := http1.DefaultHandleOptions()
for _, o := range opts {
o(&h)
}
r := mux.NewRouter()
r.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) {
var in ListServicesRequest
if err := h.Decode(r, &in); err != nil {
h.Error(w, r, err)
return
}
next := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.ListServices(ctx, req.(*ListServicesRequest))
}
if h.Middleware != nil {
next = h.Middleware(next)
}
out, err := next(r.Context(), &in)
if err != nil {
h.Error(w, r, err)
return
}
reply := out.(*ListServicesReply)
if err := h.Encode(w, r, reply); err != nil {
h.Error(w, r, err)
}
}).Methods("GET")
r.Handle("/services", http1.NewHandler(srv.ListServices, opts...)).Methods("GET")
r.HandleFunc("/services/{name}", func(w http.ResponseWriter, r *http.Request) {
var in GetServiceDescRequest
if err := h.Decode(r, &in); err != nil {
h.Error(w, r, err)
return
}
if err := binding.MapProto(&in, mux.Vars(r)); err != nil {
h.Error(w, r, err)
return
}
next := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.GetServiceDesc(ctx, req.(*GetServiceDescRequest))
}
if h.Middleware != nil {
next = h.Middleware(next)
}
out, err := next(r.Context(), &in)
if err != nil {
h.Error(w, r, err)
return
}
reply := out.(*GetServiceDescReply)
if err := h.Encode(w, r, reply); err != nil {
h.Error(w, r, err)
}
}).Methods("GET")
r.Handle("/services/{name}", http1.NewHandler(srv.GetServiceDesc, opts...)).Methods("GET")
return r
}

@ -1,58 +0,0 @@
package main
import (
pb "github.com/go-kratos/kratos/v2/api/kratos/api"
"google.golang.org/protobuf/compiler/protogen"
"google.golang.org/protobuf/proto"
)
const (
errorsPackage = protogen.GoImportPath("github.com/go-kratos/kratos/v2/errors")
)
// generateFile generates a _http.pb.go file containing kratos errors definitions.
func generateFile(gen *protogen.Plugin, file *protogen.File) *protogen.GeneratedFile {
if len(file.Enums) == 0 {
return nil
}
filename := file.GeneratedFilenamePrefix + "_errors.pb.go"
g := gen.NewGeneratedFile(filename, file.GoImportPath)
g.P("// Code generated by protoc-gen-go-errors. DO NOT EDIT.")
g.P()
g.P("package ", file.GoPackageName)
g.P()
generateFileContent(gen, file, g)
return g
}
// generateFileContent generates the kratos errors definitions, excluding the package statement.
func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile) {
if len(file.Enums) == 0 {
return
}
g.P("// This is a compile-time assertion to ensure that this generated file")
g.P("// is compatible with the kratos package it is being compiled against.")
g.P("const _ = ", errorsPackage.Ident("SupportPackageIsVersion1"))
g.P()
for _, enum := range file.Enums {
genErrorsReason(gen, file, g, enum)
}
}
func genErrorsReason(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile, enum *protogen.Enum) {
err := proto.GetExtension(enum.Desc.Options(), pb.E_Errors)
if ok := err.(bool); !ok {
return
}
var ew errorWrapper
for _, v := range enum.Values {
err := &errorInfo{
Name: string(enum.Desc.Name()),
Value: string(v.Desc.Name()),
}
ew.Errors = append(ew.Errors, err)
}
g.P(ew.execute())
}

@ -1,8 +0,0 @@
module github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2
go 1.15
require (
github.com/go-kratos/kratos/v2 v2.0.0-beta2
google.golang.org/protobuf v1.25.0
)

@ -1,95 +0,0 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-kratos/kratos/v2 v2.0.0-beta2 h1:KfYlxfXsjRiccGS1TVRKvXVuEnPJDwrrTSx5HtaOBlc=
github.com/go-kratos/kratos/v2 v2.0.0-beta2/go.mod h1:hwEYWw8GFuJ8IoNt3T/3k+7kUfYt+h2fHDcyFlR1jBA=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.opentelemetry.io/otel v0.18.0/go.mod h1:PT5zQj4lTsR1YeARt8YNKcFb88/c2IKoSABK9mX0r78=
go.opentelemetry.io/otel/metric v0.18.0/go.mod h1:kEH2QtzAyBy3xDVQfGZKIcok4ZZFvd5xyKPfPcuK6pE=
go.opentelemetry.io/otel/oteltest v0.18.0/go.mod h1:NyierCU3/G8DLTva7KRzGii2fdxdR89zXKH1bNWY7Bo=
go.opentelemetry.io/otel/trace v0.18.0/go.mod h1:FzdUu3BPwZSZebfQ1vl5/tAa8LyMLXSJN57AXIt/iDk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

@ -1,35 +0,0 @@
package main
import (
"flag"
"fmt"
"google.golang.org/protobuf/compiler/protogen"
"google.golang.org/protobuf/types/pluginpb"
)
const version = "v2.0.0-beta4"
func main() {
showVersion := flag.Bool("version", false, "print the version and exit")
flag.Parse()
if *showVersion {
fmt.Printf("protoc-gen-go-errors %v\n", version)
return
}
var flags flag.FlagSet
protogen.Options{
ParamFunc: flags.Set,
}.Run(func(gen *protogen.Plugin) error {
gen.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
for _, f := range gen.Files {
if !f.Generate {
continue
}
generateFile(gen, f)
}
return nil
})
}

@ -1,40 +0,0 @@
package main
import (
"bytes"
"text/template"
)
var errorsTemplate = `const (
{{ range .Errors }}
Errors_{{.Value}} = "{{.Name}}_{{.Value}}"
{{- end }}
)
{{ range .Errors }}
func Is{{.Value}}(err error) bool {
return errors.Reason(err) == Errors_{{.Value}}
}
{{- end }}
`
type errorInfo struct {
Name string
Value string
}
type errorWrapper struct {
Errors []*errorInfo
}
func (e *errorWrapper) execute() string {
buf := new(bytes.Buffer)
tmpl, err := template.New("errors").Parse(errorsTemplate)
if err != nil {
panic(err)
}
if err := tmpl.Execute(buf, e); err != nil {
panic(err)
}
return string(buf.Bytes())
}

@ -14,40 +14,9 @@ type {{.ServiceType}}Handler interface {
}
func New{{.ServiceType}}Handler(srv {{.ServiceType}}Handler, opts ...http1.HandleOption) http.Handler {
h := http1.DefaultHandleOptions()
for _, o := range opts {
o(&h)
}
r := mux.NewRouter()
{{range .Methods}}
r.HandleFunc("{{.Path}}", func(w http.ResponseWriter, r *http.Request) {
var in {{.Request}}
if err := h.Decode(r, &in{{.Body}}); err != nil {
h.Error(w, r, err)
return
}
{{if ne (len .Vars) 0}}
if err := binding.MapProto(&in, mux.Vars(r)); err != nil {
h.Error(w, r, err)
return
}
{{end}}
next := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.{{.Name}}(ctx, req.(*{{.Request}}))
}
if h.Middleware != nil {
next = h.Middleware(next)
}
out, err := next(r.Context(), &in)
if err != nil {
h.Error(w, r, err)
return
}
reply := out.(*{{.Reply}})
if err := h.Encode(w, r, reply{{.ResponseBody}}); err != nil {
h.Error(w, r, err)
}
}).Methods("{{.Method}}")
r.Handle("{{.Path}}", http1.NewHandler(srv.{{.Name}}, opts...)).Methods("{{.Method}}")
{{end}}
return r
}

@ -32,146 +32,17 @@ type BlogServiceHandler interface {
}
func NewBlogServiceHandler(srv BlogServiceHandler, opts ...http1.HandleOption) http.Handler {
h := http1.DefaultHandleOptions()
for _, o := range opts {
o(&h)
}
r := mux.NewRouter()
r.HandleFunc("/v1/article/", func(w http.ResponseWriter, r *http.Request) {
var in CreateArticleRequest
if err := h.Decode(r, &in); err != nil {
h.Error(w, r, err)
return
}
r.Handle("/v1/article/", http1.NewHandler(srv.CreateArticle, opts...)).Methods("POST")
next := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.CreateArticle(ctx, req.(*CreateArticleRequest))
}
if h.Middleware != nil {
next = h.Middleware(next)
}
out, err := next(r.Context(), &in)
if err != nil {
h.Error(w, r, err)
return
}
reply := out.(*CreateArticleReply)
if err := h.Encode(w, r, reply); err != nil {
h.Error(w, r, err)
}
}).Methods("POST")
r.Handle("/v1/article/{id}", http1.NewHandler(srv.UpdateArticle, opts...)).Methods("PUT")
r.HandleFunc("/v1/article/{id}", func(w http.ResponseWriter, r *http.Request) {
var in UpdateArticleRequest
if err := h.Decode(r, &in); err != nil {
h.Error(w, r, err)
return
}
r.Handle("/v1/article/{id}", http1.NewHandler(srv.DeleteArticle, opts...)).Methods("DELETE")
if err := binding.MapProto(&in, mux.Vars(r)); err != nil {
h.Error(w, r, err)
return
}
r.Handle("/v1/article/{id}", http1.NewHandler(srv.GetArticle, opts...)).Methods("GET")
next := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.UpdateArticle(ctx, req.(*UpdateArticleRequest))
}
if h.Middleware != nil {
next = h.Middleware(next)
}
out, err := next(r.Context(), &in)
if err != nil {
h.Error(w, r, err)
return
}
reply := out.(*UpdateArticleReply)
if err := h.Encode(w, r, reply); err != nil {
h.Error(w, r, err)
}
}).Methods("PUT")
r.HandleFunc("/v1/article/{id}", func(w http.ResponseWriter, r *http.Request) {
var in DeleteArticleRequest
if err := h.Decode(r, &in); err != nil {
h.Error(w, r, err)
return
}
if err := binding.MapProto(&in, mux.Vars(r)); err != nil {
h.Error(w, r, err)
return
}
next := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.DeleteArticle(ctx, req.(*DeleteArticleRequest))
}
if h.Middleware != nil {
next = h.Middleware(next)
}
out, err := next(r.Context(), &in)
if err != nil {
h.Error(w, r, err)
return
}
reply := out.(*DeleteArticleReply)
if err := h.Encode(w, r, reply); err != nil {
h.Error(w, r, err)
}
}).Methods("DELETE")
r.HandleFunc("/v1/article/{id}", func(w http.ResponseWriter, r *http.Request) {
var in GetArticleRequest
if err := h.Decode(r, &in); err != nil {
h.Error(w, r, err)
return
}
if err := binding.MapProto(&in, mux.Vars(r)); err != nil {
h.Error(w, r, err)
return
}
next := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.GetArticle(ctx, req.(*GetArticleRequest))
}
if h.Middleware != nil {
next = h.Middleware(next)
}
out, err := next(r.Context(), &in)
if err != nil {
h.Error(w, r, err)
return
}
reply := out.(*GetArticleReply)
if err := h.Encode(w, r, reply); err != nil {
h.Error(w, r, err)
}
}).Methods("GET")
r.HandleFunc("/v1/article/", func(w http.ResponseWriter, r *http.Request) {
var in ListArticleRequest
if err := h.Decode(r, &in); err != nil {
h.Error(w, r, err)
return
}
next := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.ListArticle(ctx, req.(*ListArticleRequest))
}
if h.Middleware != nil {
next = h.Middleware(next)
}
out, err := next(r.Context(), &in)
if err != nil {
h.Error(w, r, err)
return
}
reply := out.(*ListArticleReply)
if err := h.Encode(w, r, reply); err != nil {
h.Error(w, r, err)
}
}).Methods("GET")
r.Handle("/v1/article/", http1.NewHandler(srv.ListArticle, opts...)).Methods("GET")
return r
}

@ -1,13 +1,12 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.13.0
// protoc-gen-go v1.26.0
// protoc v3.14.0
// source: helloworld.proto
package helloworld
import (
proto "github.com/golang/protobuf/proto"
_ "google.golang.org/genproto/googleapis/api/annotations"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
@ -22,10 +21,6 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is a compile-time assertion that a sufficiently up-to-date version
// of the legacy proto package is being used.
const _ = proto.ProtoPackageIsVersion4
// The request message containing the user's name.
type HelloRequest struct {
state protoimpl.MessageState

@ -24,39 +24,9 @@ type GreeterHandler interface {
}
func NewGreeterHandler(srv GreeterHandler, opts ...http1.HandleOption) http.Handler {
h := http1.DefaultHandleOptions()
for _, o := range opts {
o(&h)
}
r := mux.NewRouter()
r.HandleFunc("/helloworld/{name}", func(w http.ResponseWriter, r *http.Request) {
var in HelloRequest
if err := binding.MapProto(&in, mux.Vars(r)); err != nil {
h.Error(w, r, err)
return
}
if err := h.Decode(r, &in); err != nil {
h.Error(w, r, err)
return
}
next := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.SayHello(ctx, req.(*HelloRequest))
}
if h.Middleware != nil {
next = h.Middleware(next)
}
out, err := next(r.Context(), &in)
if err != nil {
h.Error(w, r, err)
return
}
if err := h.Encode(w, r, out); err != nil {
h.Error(w, r, err)
}
}).Methods("GET")
r.Handle("/helloworld/{name}", http1.NewHandler(srv.SayHello, opts...)).Methods("GET")
return r
}

@ -0,0 +1,52 @@
package main
import (
"context"
"log"
"github.com/go-kratos/kratos/v2"
"github.com/go-kratos/kratos/v2/transport/http"
"github.com/gorilla/mux"
)
// User is a user model.
type User struct {
ID string `json:"id"`
Name string `json:"name"`
}
// UserService is a user service.
type UserService struct{ users map[string]*User }
// Get get a user from memory.
func (u *UserService) Get(ctx context.Context, user *User) (*User, error) {
return u.users[user.ID], nil
}
// Add add a user to memory.
func (u *UserService) Add(ctx context.Context, user *User) (*User, error) {
u.users[user.ID] = user
return user, nil
}
func main() {
us := &UserService{
users: make(map[string]*User),
}
router := mux.NewRouter()
router.Handle("/users", http.NewHandler(us.Add)).Methods("POST")
router.Handle("/users/{id}", http.NewHandler(us.Get)).Methods("GET")
httpSrv := http.NewServer(http.Address(":8000"))
httpSrv.HandlePrefix("/", router)
app := kratos.New(
kratos.Name("handler"),
kratos.Server(
httpSrv,
),
)
if err := app.Run(); err != nil {
log.Println(err)
}
}

@ -16,3 +16,15 @@ func BindForm(req *http.Request, target interface{}) error {
}
return mapForm(target, req.Form)
}
// BindValue bind map parameters to target.
func BindValue(vars map[string]string, target interface{}) error {
values := make(map[string][]string, len(vars))
for k, v := range vars {
values[k] = []string{v}
}
if msg, ok := target.(proto.Message); ok {
return mapProto(msg, values)
}
return mapForm(target, values)
}

@ -18,6 +18,7 @@ import (
)
// MapProto sets a value in a nested Protobuf structure.
// Deprecated: use BindValue instead.
func MapProto(msg proto.Message, values map[string]string) error {
for key, value := range values {
if err := populateFieldValues(msg.ProtoReflect(), strings.Split(key, "."), []string{value}); err != nil {

@ -1,14 +1,20 @@
package http
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"reflect"
"github.com/go-kratos/kratos/v2/encoding"
"github.com/go-kratos/kratos/v2/encoding/json"
xhttp "github.com/go-kratos/kratos/v2/internal/http"
"github.com/go-kratos/kratos/v2/middleware"
"github.com/go-kratos/kratos/v2/middleware/recovery"
"github.com/go-kratos/kratos/v2/transport/http/binding"
"github.com/gorilla/mux"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/protojson"
)
@ -26,51 +32,112 @@ type EncodeResponseFunc func(http.ResponseWriter, *http.Request, interface{}) er
type EncodeErrorFunc func(http.ResponseWriter, *http.Request, error)
// HandleOption is handle option.
type HandleOption func(*HandleOptions)
// HandleOptions is handle options.
type HandleOptions struct {
Decode DecodeRequestFunc
Encode EncodeResponseFunc
Error EncodeErrorFunc
Middleware middleware.Middleware
}
// DefaultHandleOptions returns a default handle options.
func DefaultHandleOptions() HandleOptions {
return HandleOptions{
Decode: decodeRequest,
Encode: encodeResponse,
Error: encodeError,
}
}
type HandleOption func(*Handler)
// RequestDecoder with request decoder.
func RequestDecoder(dec DecodeRequestFunc) HandleOption {
return func(o *HandleOptions) {
o.Decode = dec
return func(o *Handler) {
o.dec = dec
}
}
// ResponseEncoder with response encoder.
func ResponseEncoder(en EncodeResponseFunc) HandleOption {
return func(o *HandleOptions) {
o.Encode = en
return func(o *Handler) {
o.enc = en
}
}
// ErrorEncoder with error encoder.
func ErrorEncoder(en EncodeErrorFunc) HandleOption {
return func(o *HandleOptions) {
o.Error = en
return func(o *Handler) {
o.err = en
}
}
// Middleware with middleware option.
func Middleware(m ...middleware.Middleware) HandleOption {
return func(o *HandleOptions) {
o.Middleware = middleware.Chain(m...)
return func(o *Handler) {
o.next = middleware.Chain(m...)
}
}
// Handler is handle options.
type Handler struct {
method reflect.Value
in reflect.Type
out reflect.Type
dec DecodeRequestFunc
enc EncodeResponseFunc
err EncodeErrorFunc
next middleware.Middleware
}
// NewHandler new a HTTP handler.
func NewHandler(handler interface{}, opts ...HandleOption) http.Handler {
if err := validateHandler(handler); err != nil {
panic(err)
}
typ := reflect.TypeOf(handler)
h := &Handler{
method: reflect.ValueOf(handler),
in: typ.In(1).Elem(),
out: typ.Out(0).Elem(),
dec: decodeRequest,
enc: encodeResponse,
err: encodeError,
next: recovery.Recovery(),
}
for _, o := range opts {
o(h)
}
return h
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
in := reflect.New(h.in).Interface()
if err := h.dec(req, in); err != nil {
h.err(w, req, err)
return
}
invoke := func(ctx context.Context, in interface{}) (interface{}, error) {
ret := h.method.Call([]reflect.Value{
reflect.ValueOf(ctx),
reflect.ValueOf(in),
})
if ret[1].IsNil() {
return ret[0].Interface(), nil
}
return nil, ret[1].Interface().(error)
}
if h.next != nil {
invoke = h.next(invoke)
}
out, err := invoke(req.Context(), in)
if err != nil {
h.err(w, req, err)
return
}
if err := h.enc(w, req, out); err != nil {
h.err(w, req, err)
}
}
func validateHandler(handler interface{}) error {
typ := reflect.TypeOf(handler)
if typ.NumIn() != 2 || typ.NumOut() != 2 {
return fmt.Errorf("invalid types, in: %d out: %d", typ.NumIn(), typ.NumOut())
}
if typ.In(1).Kind() != reflect.Ptr || typ.Out(0).Kind() != reflect.Ptr {
return fmt.Errorf("invalid types is not a pointer")
}
if !typ.In(0).Implements(reflect.TypeOf((*context.Context)(nil)).Elem()) {
return fmt.Errorf("input does not implement the context")
}
if !typ.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) {
return fmt.Errorf("input does not implement the error")
}
return nil
}
// decodeRequest decodes the request body to object.
@ -79,11 +146,20 @@ func decodeRequest(req *http.Request, v interface{}) error {
if codec := encoding.GetCodec(subtype); codec != nil {
data, err := ioutil.ReadAll(req.Body)
if err != nil {
return err
return status.Error(codes.InvalidArgument, err.Error())
}
return codec.Unmarshal(data, v)
if err := codec.Unmarshal(data, v); err != nil {
return status.Error(codes.InvalidArgument, err.Error())
}
return binding.BindForm(req, v)
} else {
if err := binding.BindForm(req, v); err != nil {
return status.Error(codes.InvalidArgument, err.Error())
}
}
if err := binding.BindValue(mux.Vars(req), v); err != nil {
return status.Error(codes.InvalidArgument, err.Error())
}
return nil
}
// encodeResponse encodes the object to the HTTP response.

@ -2,10 +2,7 @@ package http
import (
"context"
"net/http"
"testing"
"github.com/gorilla/mux"
)
type HelloRequest struct {
@ -21,37 +18,7 @@ func (s *GreeterService) SayHello(ctx context.Context, req *HelloRequest) (*Hell
return &HelloReply{Message: "hello " + req.Name}, nil
}
func newGreeterHandler(srv *GreeterService, opts ...HandleOption) http.Handler {
h := DefaultHandleOptions()
for _, o := range opts {
o(&h)
}
r := mux.NewRouter()
r.HandleFunc("/helloworld", func(w http.ResponseWriter, r *http.Request) {
var in HelloRequest
if err := h.Decode(r, &in); err != nil {
h.Error(w, r, err)
return
}
next := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.SayHello(ctx, &in)
}
if h.Middleware != nil {
next = h.Middleware(next)
}
out, err := next(r.Context(), &in)
if err != nil {
h.Error(w, r, err)
return
}
if err := h.Encode(w, r, out); err != nil {
h.Error(w, r, err)
}
}).Methods("POST")
return r
}
func TestHandler(t *testing.T) {
s := &GreeterService{}
_ = newGreeterHandler(s)
_ = NewHandler(s.SayHello)
}

Loading…
Cancel
Save