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