parent
a500fe2f0d
commit
496edc6fb1
@ -0,0 +1,11 @@ |
|||||||
|
// Code generated by protoc-gen-go-errors. DO NOT EDIT.
|
||||||
|
|
||||||
|
package testproto |
||||||
|
|
||||||
|
import ( |
||||||
|
errors "github.com/go-kratos/kratos/v2/errors" |
||||||
|
) |
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the kratos package it is being compiled against.
|
||||||
|
const _ = errors.SupportPackageIsVersion1 |
@ -0,0 +1,91 @@ |
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.25.0
|
||||||
|
// protoc v3.13.0
|
||||||
|
// source: stream.proto
|
||||||
|
|
||||||
|
package testproto |
||||||
|
|
||||||
|
import ( |
||||||
|
proto "github.com/golang/protobuf/proto" |
||||||
|
empty "github.com/golang/protobuf/ptypes/empty" |
||||||
|
_ "google.golang.org/genproto/googleapis/api/annotations" |
||||||
|
httpbody "google.golang.org/genproto/googleapis/api/httpbody" |
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect" |
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl" |
||||||
|
reflect "reflect" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) |
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = 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 |
||||||
|
|
||||||
|
var File_stream_proto protoreflect.FileDescriptor |
||||||
|
|
||||||
|
var file_stream_proto_rawDesc = []byte{ |
||||||
|
0x0a, 0x0c, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, |
||||||
|
0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, |
||||||
|
0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, |
||||||
|
0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, |
||||||
|
0x61, 0x70, 0x69, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x62, 0x6f, 0x64, 0x79, 0x2e, 0x70, 0x72, 0x6f, |
||||||
|
0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, |
||||||
|
0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, |
||||||
|
0x69, 0x0a, 0x0d, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, |
||||||
|
0x12, 0x58, 0x0a, 0x08, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x16, 0x2e, 0x67, |
||||||
|
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, |
||||||
|
0x6d, 0x70, 0x74, 0x79, 0x1a, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, 0x70, |
||||||
|
0x69, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, |
||||||
|
0x02, 0x16, 0x12, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, |
||||||
|
0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x30, 0x01, 0x42, 0x51, 0x5a, 0x4f, 0x67, 0x69, |
||||||
|
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b, 0x72, 0x61, 0x74, |
||||||
|
0x6f, 0x73, 0x2f, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x70, 0x72, |
||||||
|
0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x2d, 0x68, 0x74, 0x74, 0x70, |
||||||
|
0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, |
||||||
|
0x6f, 0x74, 0x6f, 0x3b, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, |
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x33, |
||||||
|
} |
||||||
|
|
||||||
|
var file_stream_proto_goTypes = []interface{}{ |
||||||
|
(*empty.Empty)(nil), // 0: google.protobuf.Empty
|
||||||
|
(*httpbody.HttpBody)(nil), // 1: google.api.HttpBody
|
||||||
|
} |
||||||
|
var file_stream_proto_depIdxs = []int32{ |
||||||
|
0, // 0: testproto.StreamService.Download:input_type -> google.protobuf.Empty
|
||||||
|
1, // 1: testproto.StreamService.Download:output_type -> google.api.HttpBody
|
||||||
|
1, // [1:2] is the sub-list for method output_type
|
||||||
|
0, // [0:1] is the sub-list for method input_type
|
||||||
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
|
0, // [0:0] is the sub-list for field type_name
|
||||||
|
} |
||||||
|
|
||||||
|
func init() { file_stream_proto_init() } |
||||||
|
func file_stream_proto_init() { |
||||||
|
if File_stream_proto != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
type x struct{} |
||||||
|
out := protoimpl.TypeBuilder{ |
||||||
|
File: protoimpl.DescBuilder{ |
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), |
||||||
|
RawDescriptor: file_stream_proto_rawDesc, |
||||||
|
NumEnums: 0, |
||||||
|
NumMessages: 0, |
||||||
|
NumExtensions: 0, |
||||||
|
NumServices: 1, |
||||||
|
}, |
||||||
|
GoTypes: file_stream_proto_goTypes, |
||||||
|
DependencyIndexes: file_stream_proto_depIdxs, |
||||||
|
}.Build() |
||||||
|
File_stream_proto = out.File |
||||||
|
file_stream_proto_rawDesc = nil |
||||||
|
file_stream_proto_goTypes = nil |
||||||
|
file_stream_proto_depIdxs = nil |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
syntax = "proto3"; |
||||||
|
option go_package = "github.com/go-kratos/kratos/cmd/protoc-gen-go-http/internal/testproto;testproto"; |
||||||
|
package testproto; |
||||||
|
|
||||||
|
import "google/api/annotations.proto"; |
||||||
|
import "google/api/httpbody.proto"; |
||||||
|
import "google/protobuf/empty.proto"; |
||||||
|
|
||||||
|
service StreamService { |
||||||
|
rpc Download(google.protobuf.Empty) returns (stream google.api.HttpBody) { |
||||||
|
option (google.api.http) = { |
||||||
|
get : "/v1/example/download" |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,130 @@ |
|||||||
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
|
||||||
|
package testproto |
||||||
|
|
||||||
|
import ( |
||||||
|
context "context" |
||||||
|
empty "github.com/golang/protobuf/ptypes/empty" |
||||||
|
httpbody "google.golang.org/genproto/googleapis/api/httpbody" |
||||||
|
grpc "google.golang.org/grpc" |
||||||
|
codes "google.golang.org/grpc/codes" |
||||||
|
status "google.golang.org/grpc/status" |
||||||
|
) |
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
// Requires gRPC-Go v1.32.0 or later.
|
||||||
|
const _ = grpc.SupportPackageIsVersion7 |
||||||
|
|
||||||
|
// StreamServiceClient is the client API for StreamService service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
type StreamServiceClient interface { |
||||||
|
Download(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (StreamService_DownloadClient, error) |
||||||
|
} |
||||||
|
|
||||||
|
type streamServiceClient struct { |
||||||
|
cc grpc.ClientConnInterface |
||||||
|
} |
||||||
|
|
||||||
|
func NewStreamServiceClient(cc grpc.ClientConnInterface) StreamServiceClient { |
||||||
|
return &streamServiceClient{cc} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *streamServiceClient) Download(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (StreamService_DownloadClient, error) { |
||||||
|
stream, err := c.cc.NewStream(ctx, &StreamService_ServiceDesc.Streams[0], "/testproto.StreamService/Download", opts...) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
x := &streamServiceDownloadClient{stream} |
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if err := x.ClientStream.CloseSend(); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return x, nil |
||||||
|
} |
||||||
|
|
||||||
|
type StreamService_DownloadClient interface { |
||||||
|
Recv() (*httpbody.HttpBody, error) |
||||||
|
grpc.ClientStream |
||||||
|
} |
||||||
|
|
||||||
|
type streamServiceDownloadClient struct { |
||||||
|
grpc.ClientStream |
||||||
|
} |
||||||
|
|
||||||
|
func (x *streamServiceDownloadClient) Recv() (*httpbody.HttpBody, error) { |
||||||
|
m := new(httpbody.HttpBody) |
||||||
|
if err := x.ClientStream.RecvMsg(m); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return m, nil |
||||||
|
} |
||||||
|
|
||||||
|
// StreamServiceServer is the server API for StreamService service.
|
||||||
|
// All implementations must embed UnimplementedStreamServiceServer
|
||||||
|
// for forward compatibility
|
||||||
|
type StreamServiceServer interface { |
||||||
|
Download(*empty.Empty, StreamService_DownloadServer) error |
||||||
|
mustEmbedUnimplementedStreamServiceServer() |
||||||
|
} |
||||||
|
|
||||||
|
// UnimplementedStreamServiceServer must be embedded to have forward compatible implementations.
|
||||||
|
type UnimplementedStreamServiceServer struct { |
||||||
|
} |
||||||
|
|
||||||
|
func (UnimplementedStreamServiceServer) Download(*empty.Empty, StreamService_DownloadServer) error { |
||||||
|
return status.Errorf(codes.Unimplemented, "method Download not implemented") |
||||||
|
} |
||||||
|
func (UnimplementedStreamServiceServer) mustEmbedUnimplementedStreamServiceServer() {} |
||||||
|
|
||||||
|
// UnsafeStreamServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to StreamServiceServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeStreamServiceServer interface { |
||||||
|
mustEmbedUnimplementedStreamServiceServer() |
||||||
|
} |
||||||
|
|
||||||
|
func RegisterStreamServiceServer(s grpc.ServiceRegistrar, srv StreamServiceServer) { |
||||||
|
s.RegisterService(&StreamService_ServiceDesc, srv) |
||||||
|
} |
||||||
|
|
||||||
|
func _StreamService_Download_Handler(srv interface{}, stream grpc.ServerStream) error { |
||||||
|
m := new(empty.Empty) |
||||||
|
if err := stream.RecvMsg(m); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return srv.(StreamServiceServer).Download(m, &streamServiceDownloadServer{stream}) |
||||||
|
} |
||||||
|
|
||||||
|
type StreamService_DownloadServer interface { |
||||||
|
Send(*httpbody.HttpBody) error |
||||||
|
grpc.ServerStream |
||||||
|
} |
||||||
|
|
||||||
|
type streamServiceDownloadServer struct { |
||||||
|
grpc.ServerStream |
||||||
|
} |
||||||
|
|
||||||
|
func (x *streamServiceDownloadServer) Send(m *httpbody.HttpBody) error { |
||||||
|
return x.ServerStream.SendMsg(m) |
||||||
|
} |
||||||
|
|
||||||
|
// StreamService_ServiceDesc is the grpc.ServiceDesc for StreamService service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var StreamService_ServiceDesc = grpc.ServiceDesc{ |
||||||
|
ServiceName: "testproto.StreamService", |
||||||
|
HandlerType: (*StreamServiceServer)(nil), |
||||||
|
Methods: []grpc.MethodDesc{}, |
||||||
|
Streams: []grpc.StreamDesc{ |
||||||
|
{ |
||||||
|
StreamName: "Download", |
||||||
|
Handler: _StreamService_Download_Handler, |
||||||
|
ServerStreams: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
Metadata: "stream.proto", |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
// Code generated by protoc-gen-go-http. DO NOT EDIT.
|
||||||
|
|
||||||
|
package testproto |
||||||
|
|
||||||
|
import ( |
||||||
|
context "context" |
||||||
|
http1 "github.com/go-kratos/kratos/v2/transport/http" |
||||||
|
binding "github.com/go-kratos/kratos/v2/transport/http/binding" |
||||||
|
mux "github.com/gorilla/mux" |
||||||
|
http "net/http" |
||||||
|
) |
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the kratos package it is being compiled against.
|
||||||
|
var _ = new(http.Request) |
||||||
|
var _ = new(context.Context) |
||||||
|
var _ = binding.MapProto |
||||||
|
var _ = mux.NewRouter |
||||||
|
|
||||||
|
const _ = http1.SupportPackageIsVersion1 |
||||||
|
|
||||||
|
type StreamServiceHandler interface { |
||||||
|
} |
||||||
|
|
||||||
|
func NewStreamServiceHandler(srv StreamServiceHandler, opts ...http1.HandleOption) http.Handler { |
||||||
|
h := http1.DefaultHandleOptions() |
||||||
|
for _, o := range opts { |
||||||
|
o(&h) |
||||||
|
} |
||||||
|
r := mux.NewRouter() |
||||||
|
|
||||||
|
return r |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
package binding |
||||||
|
|
||||||
|
import ( |
||||||
|
"net/http" |
||||||
|
|
||||||
|
"google.golang.org/protobuf/proto" |
||||||
|
) |
||||||
|
|
||||||
|
// BindForm bind form parameters to target.
|
||||||
|
func BindForm(req *http.Request, target interface{}) error { |
||||||
|
if err := req.ParseForm(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if msg, ok := target.(proto.Message); ok { |
||||||
|
return mapProto(msg, req.Form) |
||||||
|
} |
||||||
|
return mapForm(target, req.Form) |
||||||
|
} |
@ -0,0 +1,385 @@ |
|||||||
|
package binding |
||||||
|
|
||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"reflect" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
errUnknownType = errors.New("unknown type") |
||||||
|
emptyField = reflect.StructField{} |
||||||
|
) |
||||||
|
|
||||||
|
func mapForm(ptr interface{}, form map[string][]string) error { |
||||||
|
return mapFormByTag(ptr, form, "json") |
||||||
|
} |
||||||
|
|
||||||
|
func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { |
||||||
|
ptrVal := reflect.ValueOf(ptr) |
||||||
|
var pointed interface{} |
||||||
|
if ptrVal.Kind() == reflect.Ptr { |
||||||
|
ptrVal = ptrVal.Elem() |
||||||
|
pointed = ptrVal.Interface() |
||||||
|
} |
||||||
|
if ptrVal.Kind() == reflect.Map && |
||||||
|
ptrVal.Type().Key().Kind() == reflect.String { |
||||||
|
if pointed != nil { |
||||||
|
ptr = pointed |
||||||
|
} |
||||||
|
return setFormMap(ptr, form) |
||||||
|
} |
||||||
|
return mappingByPtr(ptr, formSource(form), tag) |
||||||
|
} |
||||||
|
|
||||||
|
// setter tries to set value on a walking by fields of a struct
|
||||||
|
type setter interface { |
||||||
|
TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) |
||||||
|
} |
||||||
|
|
||||||
|
type formSource map[string][]string |
||||||
|
|
||||||
|
var _ setter = formSource(nil) |
||||||
|
|
||||||
|
// TrySet tries to set a value by request's form source (like map[string][]string)
|
||||||
|
func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) { |
||||||
|
return setByForm(value, field, form, tagValue, opt) |
||||||
|
} |
||||||
|
|
||||||
|
func mappingByPtr(ptr interface{}, setter setter, tag string) error { |
||||||
|
_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { |
||||||
|
if field.Tag.Get(tag) == "-" { // just ignoring this field
|
||||||
|
return false, nil |
||||||
|
} |
||||||
|
|
||||||
|
var vKind = value.Kind() |
||||||
|
|
||||||
|
if vKind == reflect.Ptr { |
||||||
|
var isNew bool |
||||||
|
vPtr := value |
||||||
|
if value.IsNil() { |
||||||
|
isNew = true |
||||||
|
vPtr = reflect.New(value.Type().Elem()) |
||||||
|
} |
||||||
|
isSetted, err := mapping(vPtr.Elem(), field, setter, tag) |
||||||
|
if err != nil { |
||||||
|
return false, err |
||||||
|
} |
||||||
|
if isNew && isSetted { |
||||||
|
value.Set(vPtr) |
||||||
|
} |
||||||
|
return isSetted, nil |
||||||
|
} |
||||||
|
|
||||||
|
if vKind != reflect.Struct || !field.Anonymous { |
||||||
|
ok, err := tryToSetValue(value, field, setter, tag) |
||||||
|
if err != nil { |
||||||
|
return false, err |
||||||
|
} |
||||||
|
if ok { |
||||||
|
return true, nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if vKind == reflect.Struct { |
||||||
|
tValue := value.Type() |
||||||
|
|
||||||
|
var isSetted bool |
||||||
|
for i := 0; i < value.NumField(); i++ { |
||||||
|
sf := tValue.Field(i) |
||||||
|
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||||
|
continue |
||||||
|
} |
||||||
|
ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag) |
||||||
|
if err != nil { |
||||||
|
return false, err |
||||||
|
} |
||||||
|
isSetted = isSetted || ok |
||||||
|
} |
||||||
|
return isSetted, nil |
||||||
|
} |
||||||
|
return false, nil |
||||||
|
} |
||||||
|
|
||||||
|
type setOptions struct { |
||||||
|
isDefaultExists bool |
||||||
|
defaultValue string |
||||||
|
} |
||||||
|
|
||||||
|
func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { |
||||||
|
var tagValue string |
||||||
|
var setOpt setOptions |
||||||
|
|
||||||
|
tagValue = field.Tag.Get(tag) |
||||||
|
tagValue, opts := head(tagValue, ",") |
||||||
|
|
||||||
|
if tagValue == "" { // default value is FieldName
|
||||||
|
tagValue = field.Name |
||||||
|
} |
||||||
|
if tagValue == "" { // when field is "emptyField" variable
|
||||||
|
return false, nil |
||||||
|
} |
||||||
|
|
||||||
|
var opt string |
||||||
|
for len(opts) > 0 { |
||||||
|
opt, opts = head(opts, ",") |
||||||
|
|
||||||
|
if k, v := head(opt, "="); k == "default" { |
||||||
|
setOpt.isDefaultExists = true |
||||||
|
setOpt.defaultValue = v |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return setter.TrySet(value, field, tagValue, setOpt) |
||||||
|
} |
||||||
|
|
||||||
|
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) { |
||||||
|
vs, ok := form[tagValue] |
||||||
|
if !ok && !opt.isDefaultExists { |
||||||
|
return false, nil |
||||||
|
} |
||||||
|
|
||||||
|
switch value.Kind() { |
||||||
|
case reflect.Slice: |
||||||
|
if !ok { |
||||||
|
vs = []string{opt.defaultValue} |
||||||
|
} |
||||||
|
return true, setSlice(vs, value, field) |
||||||
|
case reflect.Array: |
||||||
|
if !ok { |
||||||
|
vs = []string{opt.defaultValue} |
||||||
|
} |
||||||
|
if len(vs) != value.Len() { |
||||||
|
return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String()) |
||||||
|
} |
||||||
|
return true, setArray(vs, value, field) |
||||||
|
default: |
||||||
|
var val string |
||||||
|
if !ok { |
||||||
|
val = opt.defaultValue |
||||||
|
} |
||||||
|
|
||||||
|
if len(vs) > 0 { |
||||||
|
val = vs[0] |
||||||
|
} |
||||||
|
return true, setWithProperType(val, value, field) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func setWithProperType(val string, value reflect.Value, field reflect.StructField) error { |
||||||
|
switch value.Kind() { |
||||||
|
case reflect.Int: |
||||||
|
return setIntField(val, 0, value) |
||||||
|
case reflect.Int8: |
||||||
|
return setIntField(val, 8, value) |
||||||
|
case reflect.Int16: |
||||||
|
return setIntField(val, 16, value) |
||||||
|
case reflect.Int32: |
||||||
|
return setIntField(val, 32, value) |
||||||
|
case reflect.Int64: |
||||||
|
switch value.Interface().(type) { |
||||||
|
case time.Duration: |
||||||
|
return setTimeDuration(val, value, field) |
||||||
|
} |
||||||
|
return setIntField(val, 64, value) |
||||||
|
case reflect.Uint: |
||||||
|
return setUintField(val, 0, value) |
||||||
|
case reflect.Uint8: |
||||||
|
return setUintField(val, 8, value) |
||||||
|
case reflect.Uint16: |
||||||
|
return setUintField(val, 16, value) |
||||||
|
case reflect.Uint32: |
||||||
|
return setUintField(val, 32, value) |
||||||
|
case reflect.Uint64: |
||||||
|
return setUintField(val, 64, value) |
||||||
|
case reflect.Bool: |
||||||
|
return setBoolField(val, value) |
||||||
|
case reflect.Float32: |
||||||
|
return setFloatField(val, 32, value) |
||||||
|
case reflect.Float64: |
||||||
|
return setFloatField(val, 64, value) |
||||||
|
case reflect.String: |
||||||
|
value.SetString(val) |
||||||
|
case reflect.Struct: |
||||||
|
switch value.Interface().(type) { |
||||||
|
case time.Time: |
||||||
|
return setTimeField(val, field, value) |
||||||
|
} |
||||||
|
return json.Unmarshal([]byte(val), value.Addr().Interface()) |
||||||
|
case reflect.Map: |
||||||
|
return json.Unmarshal([]byte(val), value.Addr().Interface()) |
||||||
|
default: |
||||||
|
return errUnknownType |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func setIntField(val string, bitSize int, field reflect.Value) error { |
||||||
|
if val == "" { |
||||||
|
val = "0" |
||||||
|
} |
||||||
|
intVal, err := strconv.ParseInt(val, 10, bitSize) |
||||||
|
if err == nil { |
||||||
|
field.SetInt(intVal) |
||||||
|
} |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func setUintField(val string, bitSize int, field reflect.Value) error { |
||||||
|
if val == "" { |
||||||
|
val = "0" |
||||||
|
} |
||||||
|
uintVal, err := strconv.ParseUint(val, 10, bitSize) |
||||||
|
if err == nil { |
||||||
|
field.SetUint(uintVal) |
||||||
|
} |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func setBoolField(val string, field reflect.Value) error { |
||||||
|
if val == "" { |
||||||
|
val = "false" |
||||||
|
} |
||||||
|
boolVal, err := strconv.ParseBool(val) |
||||||
|
if err == nil { |
||||||
|
field.SetBool(boolVal) |
||||||
|
} |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func setFloatField(val string, bitSize int, field reflect.Value) error { |
||||||
|
if val == "" { |
||||||
|
val = "0.0" |
||||||
|
} |
||||||
|
floatVal, err := strconv.ParseFloat(val, bitSize) |
||||||
|
if err == nil { |
||||||
|
field.SetFloat(floatVal) |
||||||
|
} |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func setTimeField(val string, structField reflect.StructField, value reflect.Value) error { |
||||||
|
timeFormat := structField.Tag.Get("time_format") |
||||||
|
if timeFormat == "" { |
||||||
|
timeFormat = time.RFC3339 |
||||||
|
} |
||||||
|
|
||||||
|
switch tf := strings.ToLower(timeFormat); tf { |
||||||
|
case "unix", "unixnano": |
||||||
|
tv, err := strconv.ParseInt(val, 10, 64) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
d := time.Duration(1) |
||||||
|
if tf == "unixnano" { |
||||||
|
d = time.Second |
||||||
|
} |
||||||
|
|
||||||
|
t := time.Unix(tv/int64(d), tv%int64(d)) |
||||||
|
value.Set(reflect.ValueOf(t)) |
||||||
|
return nil |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
if val == "" { |
||||||
|
value.Set(reflect.ValueOf(time.Time{})) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
l := time.Local |
||||||
|
if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC { |
||||||
|
l = time.UTC |
||||||
|
} |
||||||
|
|
||||||
|
if locTag := structField.Tag.Get("time_location"); locTag != "" { |
||||||
|
loc, err := time.LoadLocation(locTag) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
l = loc |
||||||
|
} |
||||||
|
|
||||||
|
t, err := time.ParseInLocation(timeFormat, val, l) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
value.Set(reflect.ValueOf(t)) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func setArray(vals []string, value reflect.Value, field reflect.StructField) error { |
||||||
|
for i, s := range vals { |
||||||
|
err := setWithProperType(s, value.Index(i), field) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func setSlice(vals []string, value reflect.Value, field reflect.StructField) error { |
||||||
|
slice := reflect.MakeSlice(value.Type(), len(vals), len(vals)) |
||||||
|
err := setArray(vals, slice, field) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
value.Set(slice) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func setTimeDuration(val string, value reflect.Value, field reflect.StructField) error { |
||||||
|
d, err := time.ParseDuration(val) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
value.Set(reflect.ValueOf(d)) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func head(str, sep string) (head string, tail string) { |
||||||
|
idx := strings.Index(str, sep) |
||||||
|
if idx < 0 { |
||||||
|
return str, "" |
||||||
|
} |
||||||
|
return str[:idx], str[idx+len(sep):] |
||||||
|
} |
||||||
|
|
||||||
|
func setFormMap(ptr interface{}, form map[string][]string) error { |
||||||
|
el := reflect.TypeOf(ptr).Elem() |
||||||
|
|
||||||
|
if el.Kind() == reflect.Slice { |
||||||
|
ptrMap, ok := ptr.(map[string][]string) |
||||||
|
if !ok { |
||||||
|
return errors.New("cannot convert to map slices of strings") |
||||||
|
} |
||||||
|
for k, v := range form { |
||||||
|
ptrMap[k] = v |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
ptrMap, ok := ptr.(map[string]string) |
||||||
|
if !ok { |
||||||
|
return errors.New("cannot convert to map of strings") |
||||||
|
} |
||||||
|
for k, v := range form { |
||||||
|
ptrMap[k] = v[len(v)-1] // pick last
|
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
@ -1,81 +0,0 @@ |
|||||||
package http |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"io/ioutil" |
|
||||||
"net/http" |
|
||||||
"strings" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/encoding" |
|
||||||
) |
|
||||||
|
|
||||||
const baseContentType = "application" |
|
||||||
|
|
||||||
func contentType(subtype string) string { |
|
||||||
return strings.Join([]string{baseContentType, subtype}, "/") |
|
||||||
} |
|
||||||
|
|
||||||
func contentSubtype(contentType string) string { |
|
||||||
if contentType == baseContentType { |
|
||||||
return "" |
|
||||||
} |
|
||||||
if !strings.HasPrefix(contentType, baseContentType) { |
|
||||||
return "" |
|
||||||
} |
|
||||||
// guaranteed since != baseContentType and has baseContentType prefix
|
|
||||||
switch contentType[len(baseContentType)] { |
|
||||||
case '/', ';': |
|
||||||
if i := strings.Index(contentType, ";"); i != -1 { |
|
||||||
return contentType[len(baseContentType)+1 : i] |
|
||||||
} |
|
||||||
return contentType[len(baseContentType)+1:] |
|
||||||
default: |
|
||||||
return "" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func defaultRequestDecoder(req *http.Request, v interface{}) error { |
|
||||||
data, err := ioutil.ReadAll(req.Body) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
defer req.Body.Close() |
|
||||||
subtype := contentSubtype(req.Header.Get("content-type")) |
|
||||||
codec := encoding.GetCodec(subtype) |
|
||||||
if codec == nil { |
|
||||||
return fmt.Errorf("decoding request failed unknown content-type: %s", subtype) |
|
||||||
} |
|
||||||
return codec.Unmarshal(data, v) |
|
||||||
} |
|
||||||
|
|
||||||
func defaultResponseEncoder(res http.ResponseWriter, req *http.Request, v interface{}) error { |
|
||||||
subtype := contentSubtype(req.Header.Get("accept")) |
|
||||||
codec := encoding.GetCodec(subtype) |
|
||||||
if codec == nil { |
|
||||||
codec = encoding.GetCodec("json") |
|
||||||
} |
|
||||||
data, err := codec.Marshal(v) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
res.Header().Set("content-type", contentType(codec.Name())) |
|
||||||
res.Write(data) |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func defaultErrorEncoder(res http.ResponseWriter, req *http.Request, err error) { |
|
||||||
se, code := StatusError(err) |
|
||||||
subtype := contentSubtype(req.Header.Get("accept")) |
|
||||||
codec := encoding.GetCodec(subtype) |
|
||||||
if codec == nil { |
|
||||||
codec = encoding.GetCodec("json") |
|
||||||
} |
|
||||||
data, err := codec.Marshal(se) |
|
||||||
if err != nil { |
|
||||||
res.WriteHeader(http.StatusInternalServerError) |
|
||||||
return |
|
||||||
} |
|
||||||
res.Header().Set("content-type", contentType(codec.Name())) |
|
||||||
res.WriteHeader(code) |
|
||||||
res.Write(data) |
|
||||||
} |
|
@ -1,23 +0,0 @@ |
|||||||
package http |
|
||||||
|
|
||||||
import "testing" |
|
||||||
|
|
||||||
func TestSubtype(t *testing.T) { |
|
||||||
tests := []struct { |
|
||||||
input string |
|
||||||
expected string |
|
||||||
}{ |
|
||||||
{"application/json", "json"}, |
|
||||||
{"application/json;", "json"}, |
|
||||||
{"application/json; charset=utf-8", "json"}, |
|
||||||
{"application/", ""}, |
|
||||||
{"application", ""}, |
|
||||||
{"foo", ""}, |
|
||||||
{"", ""}, |
|
||||||
} |
|
||||||
for _, test := range tests { |
|
||||||
if contentSubtype(test.input) != test.expected { |
|
||||||
t.Errorf("expected %s got %s", test.expected, test.input) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,59 +0,0 @@ |
|||||||
package http |
|
||||||
|
|
||||||
import ( |
|
||||||
"net/http" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/errors" |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
// References: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
|
|
||||||
codesMapping = map[int32]int{ |
|
||||||
0: http.StatusOK, |
|
||||||
1: http.StatusInternalServerError, |
|
||||||
2: http.StatusInternalServerError, |
|
||||||
3: http.StatusBadRequest, |
|
||||||
4: http.StatusRequestTimeout, |
|
||||||
5: http.StatusNotFound, |
|
||||||
6: http.StatusConflict, |
|
||||||
7: http.StatusForbidden, |
|
||||||
8: http.StatusTooManyRequests, |
|
||||||
9: http.StatusPreconditionFailed, |
|
||||||
10: http.StatusConflict, |
|
||||||
11: http.StatusBadRequest, |
|
||||||
12: http.StatusNotImplemented, |
|
||||||
13: http.StatusInternalServerError, |
|
||||||
14: http.StatusServiceUnavailable, |
|
||||||
15: http.StatusInternalServerError, |
|
||||||
16: http.StatusUnauthorized, |
|
||||||
} |
|
||||||
statusMapping = map[int]int32{ |
|
||||||
http.StatusOK: 0, |
|
||||||
http.StatusBadRequest: 3, |
|
||||||
http.StatusRequestTimeout: 4, |
|
||||||
http.StatusNotFound: 5, |
|
||||||
http.StatusConflict: 6, |
|
||||||
http.StatusForbidden: 7, |
|
||||||
http.StatusUnauthorized: 16, |
|
||||||
http.StatusPreconditionFailed: 9, |
|
||||||
http.StatusNotImplemented: 12, |
|
||||||
http.StatusInternalServerError: 13, |
|
||||||
http.StatusServiceUnavailable: 14, |
|
||||||
} |
|
||||||
) |
|
||||||
|
|
||||||
// StatusError converts error to status error.
|
|
||||||
func StatusError(err error) (*errors.StatusError, int) { |
|
||||||
se, ok := errors.FromError(err) |
|
||||||
if !ok { |
|
||||||
se = &errors.StatusError{ |
|
||||||
Code: 2, |
|
||||||
Reason: "Unknown", |
|
||||||
Message: "Unknown: " + err.Error(), |
|
||||||
} |
|
||||||
} |
|
||||||
if status, ok := codesMapping[se.Code]; ok { |
|
||||||
return se, status |
|
||||||
} |
|
||||||
return se, http.StatusInternalServerError |
|
||||||
} |
|
@ -0,0 +1,147 @@ |
|||||||
|
package http |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"io/ioutil" |
||||||
|
"net/http" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/go-kratos/kratos/v2/encoding" |
||||||
|
"github.com/go-kratos/kratos/v2/errors" |
||||||
|
"github.com/go-kratos/kratos/v2/middleware" |
||||||
|
"github.com/go-kratos/kratos/v2/transport/http/binding" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
// SupportPackageIsVersion1 These constants should not be referenced from any other code.
|
||||||
|
SupportPackageIsVersion1 = true |
||||||
|
|
||||||
|
baseContentType = "application" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
acceptHeader = http.CanonicalHeaderKey("Accept") |
||||||
|
contentTypeHeader = http.CanonicalHeaderKey("Content-Type") |
||||||
|
) |
||||||
|
|
||||||
|
// DecodeRequestFunc is decode request func.
|
||||||
|
type DecodeRequestFunc func(*http.Request, interface{}) error |
||||||
|
|
||||||
|
// EncodeResponseFunc is encode response func.
|
||||||
|
type EncodeResponseFunc func(http.ResponseWriter, *http.Request, interface{}) error |
||||||
|
|
||||||
|
// EncodeErrorFunc is encode error func.
|
||||||
|
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, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// RequestDecoder with request decoder.
|
||||||
|
func RequestDecoder(dec DecodeRequestFunc) HandleOption { |
||||||
|
return func(o *HandleOptions) { |
||||||
|
o.Decode = dec |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ResponseEncoder with response encoder.
|
||||||
|
func ResponseEncoder(en EncodeResponseFunc) HandleOption { |
||||||
|
return func(o *HandleOptions) { |
||||||
|
o.Encode = en |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ErrorEncoder with error encoder.
|
||||||
|
func ErrorEncoder(en EncodeErrorFunc) HandleOption { |
||||||
|
return func(o *HandleOptions) { |
||||||
|
o.Error = en |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Middleware with middleware option.
|
||||||
|
func Middleware(m middleware.Middleware) HandleOption { |
||||||
|
return func(o *HandleOptions) { |
||||||
|
o.Middleware = m |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// decodeRequest decodes the request body to object.
|
||||||
|
func decodeRequest(req *http.Request, v interface{}) error { |
||||||
|
subtype := contentSubtype(req.Header.Get(contentTypeHeader)) |
||||||
|
if codec := encoding.GetCodec(subtype); codec != nil { |
||||||
|
data, err := ioutil.ReadAll(req.Body) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return codec.Unmarshal(data, v) |
||||||
|
} |
||||||
|
return binding.BindForm(req, v) |
||||||
|
} |
||||||
|
|
||||||
|
// encodeResponse encodes the object to the HTTP response.
|
||||||
|
func encodeResponse(w http.ResponseWriter, r *http.Request, v interface{}) error { |
||||||
|
for _, accept := range r.Header[acceptHeader] { |
||||||
|
if codec := encoding.GetCodec(contentSubtype(accept)); codec != nil { |
||||||
|
data, err := codec.Marshal(v) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
w.Header().Set(contentTypeHeader, contentType(codec.Name())) |
||||||
|
w.Write(data) |
||||||
|
return nil |
||||||
|
} |
||||||
|
} |
||||||
|
return json.NewEncoder(w).Encode(v) |
||||||
|
} |
||||||
|
|
||||||
|
// encodeError encodes the erorr to the HTTP response.
|
||||||
|
func encodeError(w http.ResponseWriter, r *http.Request, err error) { |
||||||
|
se, ok := errors.FromError(err) |
||||||
|
if !ok { |
||||||
|
se = &errors.StatusError{ |
||||||
|
Code: 2, |
||||||
|
Reason: "", |
||||||
|
Message: err.Error(), |
||||||
|
} |
||||||
|
} |
||||||
|
w.WriteHeader(se.HTTPStatus()) |
||||||
|
encodeResponse(w, r, se) |
||||||
|
} |
||||||
|
|
||||||
|
func contentType(subtype string) string { |
||||||
|
return strings.Join([]string{baseContentType, subtype}, "/") |
||||||
|
} |
||||||
|
|
||||||
|
func contentSubtype(contentType string) string { |
||||||
|
if contentType == baseContentType { |
||||||
|
return "" |
||||||
|
} |
||||||
|
if !strings.HasPrefix(contentType, baseContentType) { |
||||||
|
return "" |
||||||
|
} |
||||||
|
switch contentType[len(baseContentType)] { |
||||||
|
case '/', ';': |
||||||
|
if i := strings.Index(contentType, ";"); i != -1 { |
||||||
|
return contentType[len(baseContentType)+1 : i] |
||||||
|
} |
||||||
|
return contentType[len(baseContentType)+1:] |
||||||
|
default: |
||||||
|
return "" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
package http |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"net/http" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/gorilla/mux" |
||||||
|
) |
||||||
|
|
||||||
|
type HelloRequest struct { |
||||||
|
Name string `json:"name"` |
||||||
|
} |
||||||
|
type HelloReply struct { |
||||||
|
Message string `json:"message"` |
||||||
|
} |
||||||
|
type GreeterService struct { |
||||||
|
} |
||||||
|
|
||||||
|
func (s *GreeterService) SayHello(ctx context.Context, req *HelloRequest) (*HelloReply, error) { |
||||||
|
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) |
||||||
|
} |
@ -1,49 +0,0 @@ |
|||||||
package http |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"net/http" |
|
||||||
) |
|
||||||
|
|
||||||
// SupportPackageIsVersion1 These constants should not be referenced from any other code.
|
|
||||||
const SupportPackageIsVersion1 = true |
|
||||||
|
|
||||||
type methodHandler func(srv interface{}, ctx context.Context, req *http.Request, dec func(interface{}) error) (out interface{}, err error) |
|
||||||
|
|
||||||
// MethodDesc represents a Proto service's method specification.
|
|
||||||
type MethodDesc struct { |
|
||||||
Path string |
|
||||||
Method string |
|
||||||
Handler methodHandler |
|
||||||
} |
|
||||||
|
|
||||||
// ServiceDesc represents a Proto service's specification.
|
|
||||||
type ServiceDesc struct { |
|
||||||
ServiceName string |
|
||||||
Methods []MethodDesc |
|
||||||
Metadata interface{} |
|
||||||
} |
|
||||||
|
|
||||||
// ServiceRegistrar wraps a single method that supports service registration.
|
|
||||||
type ServiceRegistrar interface { |
|
||||||
RegisterService(desc *ServiceDesc, impl interface{}) |
|
||||||
} |
|
||||||
|
|
||||||
// RegisterService .
|
|
||||||
func (s *Server) RegisterService(desc *ServiceDesc, impl interface{}) { |
|
||||||
for _, m := range desc.Methods { |
|
||||||
h := m.Handler |
|
||||||
s.router.HandleFunc(m.Path, func(res http.ResponseWriter, req *http.Request) { |
|
||||||
out, err := h(impl, req.Context(), req, func(v interface{}) error { |
|
||||||
return s.requestDecoder(req, v) |
|
||||||
}) |
|
||||||
if err != nil { |
|
||||||
s.errorEncoder(res, req, err) |
|
||||||
return |
|
||||||
} |
|
||||||
if err := s.responseEncoder(res, req, out); err != nil { |
|
||||||
s.errorEncoder(res, req, err) |
|
||||||
} |
|
||||||
}).Methods(m.Method) |
|
||||||
} |
|
||||||
} |
|
@ -1,93 +0,0 @@ |
|||||||
package http |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"context" |
|
||||||
"encoding/json" |
|
||||||
"fmt" |
|
||||||
"net/http" |
|
||||||
"testing" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/internal/host" |
|
||||||
) |
|
||||||
|
|
||||||
type testRequest struct { |
|
||||||
Name string `json:"name"` |
|
||||||
} |
|
||||||
type testReply struct { |
|
||||||
Result string `json:"result"` |
|
||||||
} |
|
||||||
type testService struct{} |
|
||||||
|
|
||||||
func (s *testService) SayHello(ctx context.Context, req *testRequest) (*testReply, error) { |
|
||||||
return &testReply{Result: req.Name}, nil |
|
||||||
} |
|
||||||
|
|
||||||
func TestService(t *testing.T) { |
|
||||||
h := func(srv interface{}, ctx context.Context, req *http.Request, dec func(interface{}) error) (interface{}, error) { |
|
||||||
var in testRequest |
|
||||||
if err := dec(&in); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
out, err := srv.(*testService).SayHello(ctx, &in) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return out, nil |
|
||||||
} |
|
||||||
sd := &ServiceDesc{ |
|
||||||
ServiceName: "helloworld.Greeter", |
|
||||||
Methods: []MethodDesc{ |
|
||||||
{ |
|
||||||
Path: "/helloworld", |
|
||||||
Method: "POST", |
|
||||||
Handler: h, |
|
||||||
}, |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
svc := &testService{} |
|
||||||
srv := NewServer() |
|
||||||
srv.RegisterService(sd, svc) |
|
||||||
|
|
||||||
time.AfterFunc(time.Second, func() { |
|
||||||
defer srv.Stop() |
|
||||||
testServiceClient(t, srv) |
|
||||||
}) |
|
||||||
|
|
||||||
if err := srv.Start(); err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func testServiceClient(t *testing.T, srv *Server) { |
|
||||||
client, err := NewClient(context.Background()) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
port, ok := host.Port(srv.lis) |
|
||||||
if !ok { |
|
||||||
t.Fatalf("extract port error: %v", srv.lis) |
|
||||||
} |
|
||||||
var ( |
|
||||||
in = testRequest{Name: "hello"} |
|
||||||
out = testReply{} |
|
||||||
url = fmt.Sprintf("http://127.0.0.1:%d/helloworld", port) |
|
||||||
) |
|
||||||
data, err := json.Marshal(in) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
req, err := http.NewRequest("POST", url, bytes.NewReader(data)) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
req.Header.Set("content-type", "application/json") |
|
||||||
if err := Do(client, req, &out); err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
if out.Result != in.Name { |
|
||||||
t.Fatalf("expected %s got %s", in.Name, out.Result) |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue