mogfee 2 years ago
parent 75c9c1c909
commit 434d858718
  1. 11
      app.go
  2. 8
      cmd/kit/main.go
  3. 2
      cmd/ts/main.go
  4. 224
      encoding/form/proto_decode.go
  5. 333
      encoding/form/proto_encode.go
  6. 94
      encoding/form/well_known_types.go
  7. 67
      encoding/json/json.go
  8. 101
      errors/errors.go
  9. 226
      errors/errors.pb.go
  10. 16
      errors/errors.proto
  11. 16
      errors/warp.go
  12. 34
      internal/endpoint/endpoint.go
  13. 92
      internal/host/host.go
  14. 2
      middleware/jwt.go
  15. 3
      middleware/logger.go
  16. 2
      options.go
  17. 28
      test/main.go
  18. 16
      third_party/errors/errors.proto
  19. 0
      third_party/google/api/annotations.proto
  20. 0
      third_party/google/api/client.proto
  21. 0
      third_party/google/api/field_behavior.proto
  22. 0
      third_party/google/api/http.proto
  23. 0
      third_party/google/api/httpbody.proto
  24. 0
      third_party/google/protobuf/any.proto
  25. 0
      third_party/google/protobuf/api.proto
  26. 0
      third_party/google/protobuf/compiler/plugin.proto
  27. 0
      third_party/google/protobuf/descriptor.proto
  28. 0
      third_party/google/protobuf/duration.proto
  29. 0
      third_party/google/protobuf/empty.proto
  30. 0
      third_party/google/protobuf/field_mask.proto
  31. 0
      third_party/google/protobuf/source_context.proto
  32. 0
      third_party/google/protobuf/struct.proto
  33. 0
      third_party/google/protobuf/timestamp.proto
  34. 0
      third_party/google/protobuf/type.proto
  35. 0
      third_party/google/protobuf/wrappers.proto
  36. 0
      third_party/validate/validate.proto
  37. 109
      transport/http/calloption.go
  38. 2
      transport/http/codec.go
  39. 2
      transport/http/router.go
  40. 89
      transport/http/server.go
  41. 96
      transport/http/status/status.go
  42. 18
      transport/http/transport.go

@ -1,13 +1,16 @@
package protoc_gen_kit package kit
import ( import (
"context" "context"
"errors" "errors"
"git.diulo.com/mogfee/protoc-gen-kit/log" "git.diulo.com/mogfee/kit/log"
"git.diulo.com/mogfee/protoc-gen-kit/registry" "git.diulo.com/mogfee/kit/registry"
"git.diulo.com/mogfee/protoc-gen-kit/transport" "git.diulo.com/mogfee/kit/transport"
"github.com/google/uuid" "github.com/google/uuid"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
_ "git.diulo.com/mogfee/kit/encoding/form"
_ "git.diulo.com/mogfee/kit/encoding/json"
"os" "os"
"os/signal" "os/signal"
"sync" "sync"

@ -1,7 +1,7 @@
package main package main
import ( import (
protogen2 "git.diulo.com/mogfee/protoc-gen-kit/protogen" protogen2 "git.diulo.com/mogfee/kit/protogen"
"google.golang.org/protobuf/compiler/protogen" "google.golang.org/protobuf/compiler/protogen"
) )
@ -24,9 +24,9 @@ func (u *Kit) Generate(plugin *protogen.Plugin) error {
return nil return nil
} }
u.addImports("context") u.addImports("context")
u.addImports("git.diulo.com/mogfee/protoc-gen-kit/middleware") u.addImports("git.diulo.com/mogfee/kit/middleware")
u.addImports("git.diulo.com/mogfee/protoc-gen-kit/pkg/response") u.addImports("git.diulo.com/mogfee/kit/pkg/response")
u.addImports("git.diulo.com/mogfee/protoc-gen-kit/pkg/errors") u.addImports("git.diulo.com/mogfee/kit/pkg/errors")
u.addImports("github.com/gin-gonic/gin") u.addImports("github.com/gin-gonic/gin")
for _, f := range plugin.Files { for _, f := range plugin.Files {
if len(f.Services) == 0 { if len(f.Services) == 0 {

@ -2,7 +2,7 @@ package main
import ( import (
"fmt" "fmt"
protogen2 "git.diulo.com/mogfee/protoc-gen-kit/protogen" protogen2 "git.diulo.com/mogfee/kit/protogen"
"google.golang.org/protobuf/compiler/protogen" "google.golang.org/protobuf/compiler/protogen"
"strings" "strings"
) )

@ -0,0 +1,224 @@
package form
import (
"encoding/base64"
"fmt"
"net/url"
"reflect"
"strconv"
"strings"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/fieldmaskpb"
)
// EncodeValues encode a message into url values.
func EncodeValues(msg interface{}) (url.Values, error) {
if msg == nil || (reflect.ValueOf(msg).Kind() == reflect.Ptr && reflect.ValueOf(msg).IsNil()) {
return url.Values{}, nil
}
if v, ok := msg.(proto.Message); ok {
u := make(url.Values)
err := encodeByField(u, "", v.ProtoReflect())
if err != nil {
return nil, err
}
return u, nil
}
return encoder.Encode(msg)
}
func encodeByField(u url.Values, path string, m protoreflect.Message) (finalErr error) {
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
var (
key string
newPath string
)
if fd.HasJSONName() {
key = fd.JSONName()
} else {
key = fd.TextName()
}
if path == "" {
newPath = key
} else {
newPath = path + "." + key
}
if of := fd.ContainingOneof(); of != nil {
if f := m.WhichOneof(of); f != nil && f != fd {
return true
}
}
switch {
case fd.IsList():
if v.List().Len() > 0 {
list, err := encodeRepeatedField(fd, v.List())
if err != nil {
finalErr = err
return false
}
for _, item := range list {
u.Add(newPath, item)
}
}
case fd.IsMap():
if v.Map().Len() > 0 {
m, err := encodeMapField(fd, v.Map())
if err != nil {
finalErr = err
return false
}
for k, value := range m {
u.Set(fmt.Sprintf("%s[%s]", newPath, k), value)
}
}
case (fd.Kind() == protoreflect.MessageKind) || (fd.Kind() == protoreflect.GroupKind):
value, err := encodeMessage(fd.Message(), v)
if err == nil {
u.Set(newPath, value)
return true
}
if err = encodeByField(u, newPath, v.Message()); err != nil {
finalErr = err
return false
}
default:
value, err := EncodeField(fd, v)
if err != nil {
finalErr = err
return false
}
u.Set(newPath, value)
}
return true
})
return
}
func encodeRepeatedField(fieldDescriptor protoreflect.FieldDescriptor, list protoreflect.List) ([]string, error) {
var values []string
for i := 0; i < list.Len(); i++ {
value, err := EncodeField(fieldDescriptor, list.Get(i))
if err != nil {
return nil, err
}
values = append(values, value)
}
return values, nil
}
func encodeMapField(fieldDescriptor protoreflect.FieldDescriptor, mp protoreflect.Map) (map[string]string, error) {
m := make(map[string]string)
mp.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool {
key, err := EncodeField(fieldDescriptor.MapValue(), k.Value())
if err != nil {
return false
}
value, err := EncodeField(fieldDescriptor.MapValue(), v)
if err != nil {
return false
}
m[key] = value
return true
})
return m, nil
}
// EncodeField encode proto message filed
func EncodeField(fieldDescriptor protoreflect.FieldDescriptor, value protoreflect.Value) (string, error) {
switch fieldDescriptor.Kind() {
case protoreflect.BoolKind:
return strconv.FormatBool(value.Bool()), nil
case protoreflect.EnumKind:
if fieldDescriptor.Enum().FullName() == "google.protobuf.NullValue" {
return nullStr, nil
}
desc := fieldDescriptor.Enum().Values().ByNumber(value.Enum())
return string(desc.Name()), nil
case protoreflect.StringKind:
return value.String(), nil
case protoreflect.BytesKind:
return base64.URLEncoding.EncodeToString(value.Bytes()), nil
case protoreflect.MessageKind, protoreflect.GroupKind:
return encodeMessage(fieldDescriptor.Message(), value)
default:
return fmt.Sprint(value.Interface()), nil
}
}
// encodeMessage marshals the fields in the given protoreflect.Message.
// If the typeURL is non-empty, then a synthetic "@type" field is injected
// containing the URL as the value.
func encodeMessage(msgDescriptor protoreflect.MessageDescriptor, value protoreflect.Value) (string, error) {
switch msgDescriptor.FullName() {
case timestampMessageFullname:
return marshalTimestamp(value.Message())
case durationMessageFullname:
return marshalDuration(value.Message())
case bytesMessageFullname:
return marshalBytes(value.Message())
case "google.protobuf.DoubleValue", "google.protobuf.FloatValue", "google.protobuf.Int64Value", "google.protobuf.Int32Value",
"google.protobuf.UInt64Value", "google.protobuf.UInt32Value", "google.protobuf.BoolValue", "google.protobuf.StringValue":
fd := msgDescriptor.Fields()
v := value.Message().Get(fd.ByName("value"))
return fmt.Sprint(v.Interface()), nil
case fieldMaskFullName:
m, ok := value.Message().Interface().(*fieldmaskpb.FieldMask)
if !ok || m == nil {
return "", nil
}
for i, v := range m.Paths {
m.Paths[i] = jsonCamelCase(v)
}
return strings.Join(m.Paths, ","), nil
default:
return "", fmt.Errorf("unsupported message type: %q", string(msgDescriptor.FullName()))
}
}
// EncodeFieldMask return field mask name=paths
func EncodeFieldMask(m protoreflect.Message) (query string) {
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
if fd.Kind() == protoreflect.MessageKind {
if msg := fd.Message(); msg.FullName() == fieldMaskFullName {
value, err := encodeMessage(msg, v)
if err != nil {
return false
}
if fd.HasJSONName() {
query = fd.JSONName() + "=" + value
} else {
query = fd.TextName() + "=" + value
}
return false
}
}
return true
})
return
}
// jsonCamelCase converts a snake_case identifier to a camelCase identifier,
// according to the protobuf JSON specification.
// references: https://github.com/protocolbuffers/protobuf-go/blob/master/encoding/protojson/well_known_types.go#L842
func jsonCamelCase(s string) string {
var b []byte
var wasUnderscore bool
for i := 0; i < len(s); i++ { // proto identifiers are always ASCII
c := s[i]
if c != '_' {
if wasUnderscore && isASCIILower(c) {
c -= 'a' - 'A' // convert to uppercase
}
b = append(b, c)
}
wasUnderscore = c == '_'
}
return string(b)
}
func isASCIILower(c byte) bool {
return 'a' <= c && c <= 'z'
}

@ -0,0 +1,333 @@
package form
import (
"encoding/base64"
"errors"
"fmt"
"net/url"
"strconv"
"strings"
"time"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/fieldmaskpb"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
// DecodeValues decode url value into proto message.
func DecodeValues(msg proto.Message, values url.Values) error {
for key, values := range values {
if err := populateFieldValues(msg.ProtoReflect(), strings.Split(key, "."), values); err != nil {
return err
}
}
return nil
}
func populateFieldValues(v protoreflect.Message, fieldPath []string, values []string) error {
if len(fieldPath) < 1 {
return errors.New("no field path")
}
if len(values) < 1 {
return errors.New("no value provided")
}
var fd protoreflect.FieldDescriptor
for i, fieldName := range fieldPath {
if fd = getFieldDescriptor(v, fieldName); fd == nil {
// ignore unexpected field.
return nil
}
if i == len(fieldPath)-1 {
break
}
if fd.Message() == nil || fd.Cardinality() == protoreflect.Repeated {
if fd.IsMap() && len(fieldPath) > 1 {
// post subfield
return populateMapField(fd, v.Mutable(fd).Map(), []string{fieldPath[1]}, values)
}
return fmt.Errorf("invalid path: %q is not a message", fieldName)
}
v = v.Mutable(fd).Message()
}
if of := fd.ContainingOneof(); of != nil {
if f := v.WhichOneof(of); f != nil {
return fmt.Errorf("field already set for oneof %q", of.FullName().Name())
}
}
switch {
case fd.IsList():
return populateRepeatedField(fd, v.Mutable(fd).List(), values)
case fd.IsMap():
return populateMapField(fd, v.Mutable(fd).Map(), fieldPath, values)
}
if len(values) > 1 {
return fmt.Errorf("too many values for field %q: %s", fd.FullName().Name(), strings.Join(values, ", "))
}
return populateField(fd, v, values[0])
}
func getFieldDescriptor(v protoreflect.Message, fieldName string) protoreflect.FieldDescriptor {
fields := v.Descriptor().Fields()
var fd protoreflect.FieldDescriptor
if fd = getDescriptorByFieldAndName(fields, fieldName); fd == nil {
if v.Descriptor().FullName() == structMessageFullname {
fd = fields.ByNumber(structFieldsFieldNumber)
} else if len(fieldName) > 2 && strings.HasSuffix(fieldName, "[]") {
fd = getDescriptorByFieldAndName(fields, strings.TrimSuffix(fieldName, "[]"))
}
}
return fd
}
func getDescriptorByFieldAndName(fields protoreflect.FieldDescriptors, fieldName string) protoreflect.FieldDescriptor {
var fd protoreflect.FieldDescriptor
if fd = fields.ByName(protoreflect.Name(fieldName)); fd == nil {
fd = fields.ByJSONName(fieldName)
}
return fd
}
func populateField(fd protoreflect.FieldDescriptor, v protoreflect.Message, value string) error {
if value == "" {
return nil
}
val, err := parseField(fd, value)
if err != nil {
return fmt.Errorf("parsing field %q: %w", fd.FullName().Name(), err)
}
v.Set(fd, val)
return nil
}
func populateRepeatedField(fd protoreflect.FieldDescriptor, list protoreflect.List, values []string) error {
for _, value := range values {
v, err := parseField(fd, value)
if err != nil {
return fmt.Errorf("parsing list %q: %w", fd.FullName().Name(), err)
}
list.Append(v)
}
return nil
}
func populateMapField(fd protoreflect.FieldDescriptor, mp protoreflect.Map, fieldPath []string, values []string) error {
// post sub key.
nkey := len(fieldPath) - 1
key, err := parseField(fd.MapKey(), fieldPath[nkey])
if err != nil {
return fmt.Errorf("parsing map key %q: %w", fd.FullName().Name(), err)
}
vkey := len(values) - 1
value, err := parseField(fd.MapValue(), values[vkey])
if err != nil {
return fmt.Errorf("parsing map value %q: %w", fd.FullName().Name(), err)
}
mp.Set(key.MapKey(), value)
return nil
}
func parseField(fd protoreflect.FieldDescriptor, value string) (protoreflect.Value, error) {
switch fd.Kind() {
case protoreflect.BoolKind:
v, err := strconv.ParseBool(value)
if err != nil {
return protoreflect.Value{}, err
}
return protoreflect.ValueOfBool(v), nil
case protoreflect.EnumKind:
enum, err := protoregistry.GlobalTypes.FindEnumByName(fd.Enum().FullName())
switch {
case errors.Is(err, protoregistry.NotFound):
return protoreflect.Value{}, fmt.Errorf("enum %q is not registered", fd.Enum().FullName())
case err != nil:
return protoreflect.Value{}, fmt.Errorf("failed to look up enum: %w", err)
}
v := enum.Descriptor().Values().ByName(protoreflect.Name(value))
if v == nil {
i, err := strconv.ParseInt(value, 10, 32) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, fmt.Errorf("%q is not a valid value", value)
}
v = enum.Descriptor().Values().ByNumber(protoreflect.EnumNumber(i))
if v == nil {
return protoreflect.Value{}, fmt.Errorf("%q is not a valid value", value)
}
}
return protoreflect.ValueOfEnum(v.Number()), nil
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
v, err := strconv.ParseInt(value, 10, 32) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
return protoreflect.ValueOfInt32(int32(v)), nil
case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
v, err := strconv.ParseInt(value, 10, 64) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
return protoreflect.ValueOfInt64(v), nil
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
v, err := strconv.ParseUint(value, 10, 32) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
return protoreflect.ValueOfUint32(uint32(v)), nil
case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
v, err := strconv.ParseUint(value, 10, 64) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
return protoreflect.ValueOfUint64(v), nil
case protoreflect.FloatKind:
v, err := strconv.ParseFloat(value, 32) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
return protoreflect.ValueOfFloat32(float32(v)), nil
case protoreflect.DoubleKind:
v, err := strconv.ParseFloat(value, 64) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
return protoreflect.ValueOfFloat64(v), nil
case protoreflect.StringKind:
return protoreflect.ValueOfString(value), nil
case protoreflect.BytesKind:
v, err := base64.StdEncoding.DecodeString(value)
if err != nil {
return protoreflect.Value{}, err
}
return protoreflect.ValueOfBytes(v), nil
case protoreflect.MessageKind, protoreflect.GroupKind:
return parseMessage(fd.Message(), value)
default:
panic(fmt.Sprintf("unknown field kind: %v", fd.Kind()))
}
}
func parseMessage(md protoreflect.MessageDescriptor, value string) (protoreflect.Value, error) {
var msg proto.Message
switch md.FullName() {
case "google.protobuf.Timestamp":
if value == nullStr {
break
}
t, err := time.Parse(time.RFC3339Nano, value)
if err != nil {
return protoreflect.Value{}, err
}
msg = timestamppb.New(t)
case "google.protobuf.Duration":
if value == nullStr {
break
}
d, err := time.ParseDuration(value)
if err != nil {
return protoreflect.Value{}, err
}
msg = durationpb.New(d)
case "google.protobuf.DoubleValue":
v, err := strconv.ParseFloat(value, 64) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
msg = wrapperspb.Double(v)
case "google.protobuf.FloatValue":
v, err := strconv.ParseFloat(value, 32) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
msg = wrapperspb.Float(float32(v))
case "google.protobuf.Int64Value":
v, err := strconv.ParseInt(value, 10, 64) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
msg = wrapperspb.Int64(v)
case "google.protobuf.Int32Value":
v, err := strconv.ParseInt(value, 10, 32) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
msg = wrapperspb.Int32(int32(v))
case "google.protobuf.UInt64Value":
v, err := strconv.ParseUint(value, 10, 64) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
msg = wrapperspb.UInt64(v)
case "google.protobuf.UInt32Value":
v, err := strconv.ParseUint(value, 10, 32) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
msg = wrapperspb.UInt32(uint32(v))
case "google.protobuf.BoolValue":
v, err := strconv.ParseBool(value)
if err != nil {
return protoreflect.Value{}, err
}
msg = wrapperspb.Bool(v)
case "google.protobuf.StringValue":
msg = wrapperspb.String(value)
case "google.protobuf.BytesValue":
v, err := base64.StdEncoding.DecodeString(value)
if err != nil {
if v, err = base64.URLEncoding.DecodeString(value); err != nil {
return protoreflect.Value{}, err
}
}
msg = wrapperspb.Bytes(v)
case "google.protobuf.FieldMask":
fm := &fieldmaskpb.FieldMask{}
for _, fv := range strings.Split(value, ",") {
fm.Paths = append(fm.Paths, jsonSnakeCase(fv))
}
msg = fm
case "google.protobuf.Value":
fm, err := structpb.NewValue(value)
if err != nil {
return protoreflect.Value{}, err
}
msg = fm
case "google.protobuf.Struct":
var v structpb.Struct
if err := protojson.Unmarshal([]byte(value), &v); err != nil {
return protoreflect.Value{}, err
}
msg = &v
default:
return protoreflect.Value{}, fmt.Errorf("unsupported message type: %q", string(md.FullName()))
}
return protoreflect.ValueOfMessage(msg.ProtoReflect()), nil
}
// jsonSnakeCase converts a camelCase identifier to a snake_case identifier,
// according to the protobuf JSON specification.
// references: https://github.com/protocolbuffers/protobuf-go/blob/master/encoding/protojson/well_known_types.go#L864
func jsonSnakeCase(s string) string {
var b []byte
for i := 0; i < len(s); i++ { // proto identifiers are always ASCII
c := s[i]
if isASCIIUpper(c) {
b = append(b, '_')
c += 'a' - 'A' // convert to lowercase
}
b = append(b, c)
}
return string(b)
}
func isASCIIUpper(c byte) bool {
return 'A' <= c && c <= 'Z'
}

@ -0,0 +1,94 @@
package form
import (
"encoding/base64"
"fmt"
"math"
"strings"
"time"
"google.golang.org/protobuf/reflect/protoreflect"
)
const (
// timestamp
timestampMessageFullname protoreflect.FullName = "google.protobuf.Timestamp"
maxTimestampSeconds = 253402300799
minTimestampSeconds = -6213559680013
timestampSecondsFieldNumber protoreflect.FieldNumber = 1
timestampNanosFieldNumber protoreflect.FieldNumber = 2
// duration
durationMessageFullname protoreflect.FullName = "google.protobuf.Duration"
secondsInNanos = 999999999
durationSecondsFieldNumber protoreflect.FieldNumber = 1
durationNanosFieldNumber protoreflect.FieldNumber = 2
// bytes
bytesMessageFullname protoreflect.FullName = "google.protobuf.BytesValue"
bytesValueFieldNumber protoreflect.FieldNumber = 1
// google.protobuf.Struct.
structMessageFullname protoreflect.FullName = "google.protobuf.Struct"
structFieldsFieldNumber protoreflect.FieldNumber = 1
fieldMaskFullName protoreflect.FullName = "google.protobuf.FieldMask"
)
func marshalTimestamp(m protoreflect.Message) (string, error) {
fds := m.Descriptor().Fields()
fdSeconds := fds.ByNumber(timestampSecondsFieldNumber)
fdNanos := fds.ByNumber(timestampNanosFieldNumber)
secsVal := m.Get(fdSeconds)
nanosVal := m.Get(fdNanos)
secs := secsVal.Int()
nanos := nanosVal.Int()
if secs < minTimestampSeconds || secs > maxTimestampSeconds {
return "", fmt.Errorf("%s: seconds out of range %v", timestampMessageFullname, secs)
}
if nanos < 0 || nanos > secondsInNanos {
return "", fmt.Errorf("%s: nanos out of range %v", timestampMessageFullname, nanos)
}
// Uses RFC 3339, where generated output will be Z-normalized and uses 0, 3,
// 6 or 9 fractional digits.
t := time.Unix(secs, nanos).UTC()
x := t.Format("2006-01-02T15:04:05.000000000")
x = strings.TrimSuffix(x, "000")
x = strings.TrimSuffix(x, "000")
x = strings.TrimSuffix(x, ".000")
return x + "Z", nil
}
func marshalDuration(m protoreflect.Message) (string, error) {
fds := m.Descriptor().Fields()
fdSeconds := fds.ByNumber(durationSecondsFieldNumber)
fdNanos := fds.ByNumber(durationNanosFieldNumber)
secsVal := m.Get(fdSeconds)
nanosVal := m.Get(fdNanos)
secs := secsVal.Int()
nanos := nanosVal.Int()
d := time.Duration(secs) * time.Second
overflow := d/time.Second != time.Duration(secs)
d += time.Duration(nanos) * time.Nanosecond
overflow = overflow || (secs < 0 && nanos < 0 && d > 0)
overflow = overflow || (secs > 0 && nanos > 0 && d < 0)
if overflow {
switch {
case secs < 0:
return time.Duration(math.MinInt64).String(), nil
case secs > 0:
return time.Duration(math.MaxInt64).String(), nil
}
}
return d.String(), nil
}
func marshalBytes(m protoreflect.Message) (string, error) {
fds := m.Descriptor().Fields()
fdBytes := fds.ByNumber(bytesValueFieldNumber)
bytesVal := m.Get(fdBytes)
val := bytesVal.Bytes()
return base64.StdEncoding.EncodeToString(val), nil
}

@ -0,0 +1,67 @@
package json
import (
"encoding/json"
"git.diulo.com/mogfee/kit/encoding"
"reflect"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
// Name is the name registered for the json codec.
const Name = "json"
var (
// MarshalOptions is a configurable JSON format marshaller.
MarshalOptions = protojson.MarshalOptions{
EmitUnpopulated: true,
}
// UnmarshalOptions is a configurable JSON format parser.
UnmarshalOptions = protojson.UnmarshalOptions{
DiscardUnknown: true,
}
)
func init() {
encoding.RegisterCodec(codec{})
}
// codec is a Codec implementation with json.
type codec struct{}
func (codec) Marshal(v interface{}) ([]byte, error) {
switch m := v.(type) {
case json.Marshaler:
return m.MarshalJSON()
case proto.Message:
return MarshalOptions.Marshal(m)
default:
return json.Marshal(m)
}
}
func (codec) Unmarshal(data []byte, v interface{}) error {
switch m := v.(type) {
case json.Unmarshaler:
return m.UnmarshalJSON(data)
case proto.Message:
return UnmarshalOptions.Unmarshal(data, m)
default:
rv := reflect.ValueOf(v)
for rv := rv; rv.Kind() == reflect.Ptr; {
if rv.IsNil() {
rv.Set(reflect.New(rv.Type().Elem()))
}
rv = rv.Elem()
}
if m, ok := reflect.Indirect(rv).Interface().(proto.Message); ok {
return UnmarshalOptions.Unmarshal(data, m)
}
return json.Unmarshal(data, m)
}
}
func (codec) Name() string {
return Name
}

@ -2,8 +2,9 @@ package errors
import ( import (
"errors" "errors"
"fmt"
httpstatus "git.diulo.com/mogfee/kit/transport/http/status"
"google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )
@ -15,36 +16,69 @@ const (
) )
type Error struct { type Error struct {
Status int Status
Message string cause error
Reason string
Metadata map[string]string
} }
func (e *Error) WithMetadata(data map[string]string) *Error { func (e *Error) Error() string {
err := Clone(e) return fmt.Sprintf("error: code = %d reason = %s message = %s metadata = %v cause = %v", e.Code, e.Reason, e.Message, e.Metadata, e.cause)
err.Metadata = data
return err
} }
func (s *Error) Error() string { func (e *Error) Unwarp() error {
return s.Message return e.cause
} }
func New(code int, reason, message string) *Error { func (e *Error) Is(err error) bool {
return &Error{ if se := new(Error); errors.As(err, &se) {
Status: code, return se.Code == e.Code && se.Reason == e.Reason
Message: message,
Reason: reason,
Metadata: map[string]string{},
} }
return false
} }
func (e *Error) WithCause(cause error) *Error {
err := Clone(e)
err.cause = cause
return err
}
func (e *Error) WithMetadata(md map[string]string) *Error {
err := Clone(e)
err.Metadata = md
return err
}
func (e *Error) GRPCStatus() *status.Status { func (e *Error) GRPCStatus() *status.Status {
s, _ := status.New(codes.Code(e.Status), e.Message). s, _ := status.New(httpstatus.ToGRPCCode(int(e.Code)), e.Message).
WithDetails(&errdetails.ErrorInfo{ WithDetails(&errdetails.ErrorInfo{
Reason: e.Reason, Reason: e.Reason,
Metadata: e.Metadata, Metadata: e.Metadata,
}) })
return s return s
} }
func New(code int, reason, message string) *Error {
return &Error{
Status: Status{
Code: int32(code),
Reason: reason,
Message: message,
},
}
}
func Newf(code int, reason string, format string, a ...any) *Error {
return New(code, reason, fmt.Sprintf(format, a...))
}
func Errorf(code int, reason string, format string, a ...any) *Error {
return New(code, reason, fmt.Sprintf(format, a...))
}
func Code(err error) int {
if err == nil {
return 200
}
return int(FromError(err).Code)
}
func Reason(err error) string {
if err == nil {
return UnknownReason
}
return FromError(err).Reason
}
func Clone(err *Error) *Error { func Clone(err *Error) *Error {
if err == nil { if err == nil {
return nil return nil
@ -54,17 +88,14 @@ func Clone(err *Error) *Error {
metadata[k] = v metadata[k] = v
} }
return &Error{ return &Error{
Status: err.Status, cause: err.cause,
Reason: err.Reason, Status: Status{
Message: err.Message, Code: err.Code,
Metadata: metadata, Reason: err.Reason,
} Message: err.Message,
} Metadata: metadata,
func Code(err error) int { },
if err == nil {
return 200
} }
return FromError(err).Status
} }
func FromError(err error) *Error { func FromError(err error) *Error {
@ -74,5 +105,17 @@ func FromError(err error) *Error {
if se := new(Error); errors.As(err, &se) { if se := new(Error); errors.As(err, &se) {
return se return se
} }
return New(UnknownCode, UnknownReason, err.Error()) gs, ok := status.FromError(err)
if !ok {
return New(UnknownCode, UnknownReason, err.Error())
}
ret := New(httpstatus.FromGRPCCode(gs.Code()), UnknownReason, gs.Message())
for _, detail := range gs.Details() {
switch d := detail.(type) {
case *errdetails.ErrorInfo:
ret.Reason = d.Reason
return ret.WithMetadata(d.Metadata)
}
}
return ret
} }

@ -0,0 +1,226 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v4.22.0
// source: errors.proto
package errors
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
descriptorpb "google.golang.org/protobuf/types/descriptorpb"
reflect "reflect"
sync "sync"
)
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)
)
type Status struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"`
Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *Status) Reset() {
*x = Status{}
if protoimpl.UnsafeEnabled {
mi := &file_errors_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Status) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Status) ProtoMessage() {}
func (x *Status) ProtoReflect() protoreflect.Message {
mi := &file_errors_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Status.ProtoReflect.Descriptor instead.
func (*Status) Descriptor() ([]byte, []int) {
return file_errors_proto_rawDescGZIP(), []int{0}
}
func (x *Status) GetCode() int32 {
if x != nil {
return x.Code
}
return 0
}
func (x *Status) GetReason() string {
if x != nil {
return x.Reason
}
return ""
}
func (x *Status) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
func (x *Status) GetMetadata() map[string]string {
if x != nil {
return x.Metadata
}
return nil
}
var file_errors_proto_extTypes = []protoimpl.ExtensionInfo{
{
ExtendedType: (*descriptorpb.EnumOptions)(nil),
ExtensionType: (*int32)(nil),
Field: 1108,
Name: "errors.default_code",
Tag: "varint,1108,opt,name=default_code",
Filename: "errors.proto",
},
{
ExtendedType: (*descriptorpb.EnumValueOptions)(nil),
ExtensionType: (*int32)(nil),
Field: 1109,
Name: "errors.code",
Tag: "varint,1109,opt,name=code",
Filename: "errors.proto",
},
}
// Extension fields to descriptorpb.EnumOptions.
var (
// optional int32 default_code = 1108;
E_DefaultCode = &file_errors_proto_extTypes[0]
)
// Extension fields to descriptorpb.EnumValueOptions.
var (
// optional int32 code = 1109;
E_Code = &file_errors_proto_extTypes[1]
)
var File_errors_proto protoreflect.FileDescriptor
var file_errors_proto_rawDesc = []byte{
0x0a, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,
0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc5, 0x01, 0x0a, 0x06, 0x53, 0x74, 0x61,
0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f,
0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12,
0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74,
0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x65, 0x72,
0x72, 0x6f, 0x72, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61,
0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64,
0x61, 0x74, 0x61, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
0x3a, 0x40, 0x0a, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65,
0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd4,
0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f,
0x64, 0x65, 0x3a, 0x36, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75,
0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd5, 0x08,
0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x42, 0x28, 0x5a, 0x26, 0x67, 0x69,
0x74, 0x2e, 0x64, 0x69, 0x75, 0x6c, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x6f, 0x67, 0x66,
0x65, 0x65, 0x2f, 0x6b, 0x69, 0x74, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x3b, 0x65, 0x72,
0x72, 0x6f, 0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_errors_proto_rawDescOnce sync.Once
file_errors_proto_rawDescData = file_errors_proto_rawDesc
)
func file_errors_proto_rawDescGZIP() []byte {
file_errors_proto_rawDescOnce.Do(func() {
file_errors_proto_rawDescData = protoimpl.X.CompressGZIP(file_errors_proto_rawDescData)
})
return file_errors_proto_rawDescData
}
var file_errors_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_errors_proto_goTypes = []interface{}{
(*Status)(nil), // 0: errors.Status
nil, // 1: errors.Status.MetadataEntry
(*descriptorpb.EnumOptions)(nil), // 2: google.protobuf.EnumOptions
(*descriptorpb.EnumValueOptions)(nil), // 3: google.protobuf.EnumValueOptions
}
var file_errors_proto_depIdxs = []int32{
1, // 0: errors.Status.metadata:type_name -> errors.Status.MetadataEntry
2, // 1: errors.default_code:extendee -> google.protobuf.EnumOptions
3, // 2: errors.code:extendee -> google.protobuf.EnumValueOptions
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
1, // [1:3] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_errors_proto_init() }
func file_errors_proto_init() {
if File_errors_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_errors_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Status); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_errors_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 2,
NumServices: 0,
},
GoTypes: file_errors_proto_goTypes,
DependencyIndexes: file_errors_proto_depIdxs,
MessageInfos: file_errors_proto_msgTypes,
ExtensionInfos: file_errors_proto_extTypes,
}.Build()
File_errors_proto = out.File
file_errors_proto_rawDesc = nil
file_errors_proto_goTypes = nil
file_errors_proto_depIdxs = nil
}

@ -0,0 +1,16 @@
syntax="proto3";
package errors;
option go_package="git.diulo.com/mogfee/kit/errors;errors";
import "google/protobuf/descriptor.proto";
message Status{
int32 code=1;
string reason=2;
string message=3;
map<string,string>metadata=4;
}
extend google.protobuf.EnumOptions{
int32 default_code=1108;
}
extend google.protobuf.EnumValueOptions{
int32 code=1109;
}

@ -0,0 +1,16 @@
package errors
import (
stderrors "errors"
)
func Is(err, target error) bool {
return stderrors.Is(err, target)
}
func As(err error, target any) bool {
return stderrors.As(err, target)
}
func Unwarp(err error) error {
return stderrors.Unwrap(err)
}

@ -0,0 +1,34 @@
package endpoint
import (
"net/url"
)
// NewEndpoint new an Endpoint URL.
func NewEndpoint(scheme, host string) *url.URL {
return &url.URL{Scheme: scheme, Host: host}
}
// ParseEndpoint parses an Endpoint URL.
func ParseEndpoint(endpoints []string, scheme string) (string, error) {
for _, e := range endpoints {
u, err := url.Parse(e)
if err != nil {
return "", err
}
if u.Scheme == scheme {
return u.Host, nil
}
}
return "", nil
}
// Scheme is the scheme of endpoint url.
// examples: scheme="http",isSecure=true get "https"
func Scheme(scheme string, isSecure bool) string {
if isSecure {
return scheme + "s"
}
return scheme
}

@ -0,0 +1,92 @@
package host
import (
"fmt"
"net"
"strconv"
)
// ExtractHostPort from address
func ExtractHostPort(addr string) (host string, port uint64, err error) {
var ports string
host, ports, err = net.SplitHostPort(addr)
if err != nil {
return
}
port, err = strconv.ParseUint(ports, 10, 16) //nolint:gomnd
return
}
func isValidIP(addr string) bool {
ip := net.ParseIP(addr)
return ip.IsGlobalUnicast() && !ip.IsInterfaceLocalMulticast()
}
// Port return a real port.
func Port(lis net.Listener) (int, bool) {
if addr, ok := lis.Addr().(*net.TCPAddr); ok {
return addr.Port, true
}
return 0, false
}
// Extract returns a private addr and port.
func Extract(hostPort string, lis net.Listener) (string, error) {
addr, port, err := net.SplitHostPort(hostPort)
if err != nil && lis == nil {
return "", err
}
if lis != nil {
p, ok := Port(lis)
if !ok {
return "", fmt.Errorf("failed to extract port: %v", lis.Addr())
}
port = strconv.Itoa(p)
}
if len(addr) > 0 && (addr != "0.0.0.0" && addr != "[::]" && addr != "::") {
return net.JoinHostPort(addr, port), nil
}
ifaces, err := net.Interfaces()
if err != nil {
return "", err
}
minIndex := int(^uint(0) >> 1)
ips := make([]net.IP, 0)
for _, iface := range ifaces {
if (iface.Flags & net.FlagUp) == 0 {
continue
}
if iface.Index >= minIndex && len(ips) != 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil {
continue
}
for i, rawAddr := range addrs {
var ip net.IP
switch addr := rawAddr.(type) {
case *net.IPAddr:
ip = addr.IP
case *net.IPNet:
ip = addr.IP
default:
continue
}
if isValidIP(ip.String()) {
minIndex = iface.Index
if i == 0 {
ips = make([]net.IP, 0, 1)
}
ips = append(ips, ip)
if ip.To4() != nil {
break
}
}
}
}
if len(ips) != 0 {
return net.JoinHostPort(ips[len(ips)-1].String(), port), nil
}
return "", nil
}

@ -2,7 +2,7 @@ package middleware
import ( import (
"context" "context"
"git.diulo.com/mogfee/protoc-gen-kit/constants" "git.diulo.com/mogfee/kit/constants"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
) )

@ -2,14 +2,13 @@ package middleware
import ( import (
"context" "context"
"git.diulo.com/mogfee/protoc-gen-kit/log" "git.diulo.com/mogfee/kit/log"
) )
func Logger(logger log.Logger) Middleware { func Logger(logger log.Logger) Middleware {
//helper:=log.NewHelper(logger) //helper:=log.NewHelper(logger)
return func(handler Handler) Handler { return func(handler Handler) Handler {
return func(ctx context.Context, a any) (any, error) { return func(ctx context.Context, a any) (any, error) {
//helper.Infof("")
return handler(ctx, a) return handler(ctx, a)
} }
} }

@ -1,4 +1,4 @@
package protoc_gen_kit package kit
import ( import (
"context" "context"

@ -1,16 +1,36 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
protoc_gen_kit "git.diulo.com/mogfee/protoc-gen-kit" "git.diulo.com/mogfee/kit"
"git.diulo.com/mogfee/kit/transport/http"
) )
func main() { func main() {
app := protoc_gen_kit.New( app := kit.New(
protoc_gen_kit.Name("user-server"), kit.Name("user-server"),
protoc_gen_kit.Server()) kit.Server(appServer()))
fmt.Println("run start") fmt.Println("run start")
app.Run() app.Run()
fmt.Println("run end") fmt.Println("run end")
app.Stop() app.Stop()
} }
func appServer() *http.Server {
srv := http.NewServer(http.Address("localhost:9019"))
group := srv.Route("/")
group.Handle("GET", "/", func(ctx http.Context) error {
return errors.New("abc ")
})
//group.GET("/", func(ctx http.Context) error {
// return ctx.String(200, "index page")
//})
//group.GET("/hello", func(ctx http.Context) error {
// return ctx.String(200, "hello page")
//})
//group.GET("/error", func(ctx http.Context) error {
// fmt.Println("err 1")
// return errors.New(400, "BAD_REQUEST", "")
//})
return srv
}

@ -0,0 +1,16 @@
syntax="proto3";
package errors;
option go_package="git.diulo.com/mogfee/kit/errors;errors";
import "google/protobuf/descriptor.proto";
message Status{
int32 code=1;
string reason=2;
string message=3;
map<string,string>metadata=4;
}
extend google.protobuf.EnumOptions{
int32 default_code=1108;
}
extend google.protobuf.EnumValueOptions{
int32 code=1109;
}

@ -0,0 +1,109 @@
package http
import (
"net/http"
)
// CallOption configures a Call before it starts or extracts information from
// a Call after it completes.
type CallOption interface {
// before is called before the call is sent to any server. If before
// returns a non-nil error, the RPC fails with that error.
before(*callInfo) error
// after is called after the call has completed. after cannot return an
// error, so any failures should be reported via output parameters.
after(*callInfo, *csAttempt)
}
type callInfo struct {
contentType string
operation string
pathTemplate string
}
// EmptyCallOption does not alter the Call configuration.
// It can be embedded in another structure to carry satellite data for use
// by interceptors.
type EmptyCallOption struct{}
func (EmptyCallOption) before(*callInfo) error { return nil }
func (EmptyCallOption) after(*callInfo, *csAttempt) {}
type csAttempt struct {
res *http.Response
}
// ContentType with request content type.
func ContentType(contentType string) CallOption {
return ContentTypeCallOption{ContentType: contentType}
}
// ContentTypeCallOption is BodyCallOption
type ContentTypeCallOption struct {
EmptyCallOption
ContentType string
}
func (o ContentTypeCallOption) before(c *callInfo) error {
c.contentType = o.ContentType
return nil
}
func defaultCallInfo(path string) callInfo {
return callInfo{
contentType: "application/json",
operation: path,
pathTemplate: path,
}
}
// Operation is serviceMethod call option
func Operation(operation string) CallOption {
return OperationCallOption{Operation: operation}
}
// OperationCallOption is set ServiceMethod for client call
type OperationCallOption struct {
EmptyCallOption
Operation string
}
func (o OperationCallOption) before(c *callInfo) error {
c.operation = o.Operation
return nil
}
// PathTemplate is http path template
func PathTemplate(pattern string) CallOption {
return PathTemplateCallOption{Pattern: pattern}
}
// PathTemplateCallOption is set path template for client call
type PathTemplateCallOption struct {
EmptyCallOption
Pattern string
}
func (o PathTemplateCallOption) before(c *callInfo) error {
c.pathTemplate = o.Pattern
return nil
}
// Header returns a CallOptions that retrieves the http response header
// from server reply.
func Header(header *http.Header) CallOption {
return HeaderCallOption{header: header}
}
// HeaderCallOption is retrieve response header for client call
type HeaderCallOption struct {
EmptyCallOption
header *http.Header
}
func (o HeaderCallOption) after(c *callInfo, cs *csAttempt) {
if cs.res != nil && cs.res.Header != nil {
*o.header = cs.res.Header
}
}

@ -81,7 +81,7 @@ func DefaultErrorEncoder(w http.ResponseWriter, r *http.Request, err error) {
return return
} }
w.Header().Set("Content-Type", httputil.ContentType(codec.Name())) w.Header().Set("Content-Type", httputil.ContentType(codec.Name()))
w.WriteHeader(int(se.Status)) w.WriteHeader(int(se.Status.Code))
_, _ = w.Write(body) _, _ = w.Write(body)
} }
func CodeForRequest(r *http.Request, name string) (encoding.Codec, bool) { func CodeForRequest(r *http.Request, name string) (encoding.Codec, bool) {

@ -12,7 +12,7 @@ type RouteInfo struct {
Path string Path string
Method string Method string
} }
type HandlerFunc func(Context) error type HandlerFunc func(ctx Context) error
type Router struct { type Router struct {
prefix string prefix string
pool sync.Pool pool sync.Pool

@ -3,7 +3,11 @@ package http
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"errors"
"git.diulo.com/mogfee/kit/internal/endpoint"
"git.diulo.com/mogfee/kit/internal/host"
"git.diulo.com/mogfee/kit/internal/matcher" "git.diulo.com/mogfee/kit/internal/matcher"
"git.diulo.com/mogfee/kit/log"
"git.diulo.com/mogfee/kit/middleware" "git.diulo.com/mogfee/kit/middleware"
"git.diulo.com/mogfee/kit/transport" "git.diulo.com/mogfee/kit/transport"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -156,6 +160,8 @@ func NewServer(opts ...ServerOption) *Server {
decVars: DefaultRequestVars, decVars: DefaultRequestVars,
decQuery: DefaultRequestQuery, decQuery: DefaultRequestQuery,
decBody: DefaultRequestDecoder, decBody: DefaultRequestDecoder,
enc: DefaultResponseEncoder,
ene: DefaultErrorEncoder,
strictSlash: true, strictSlash: true,
router: mux.NewRouter(), router: mux.NewRouter(),
} }
@ -198,7 +204,7 @@ func (s *Server) WalkRoute(fn WalkRoutFunc) error {
}) })
} }
func (s *Server) Kind() transport.Kind { func (s *Server) Kind() transport.Kind {
return transport.htt return transport.KindHTTP
} }
func (s *Server) Route(prefix string, filters ...FilterFunc) *Router { func (s *Server) Route(prefix string, filters ...FilterFunc) *Router {
return newRouter(prefix, s, filters...) return newRouter(prefix, s, filters...)
@ -218,6 +224,38 @@ func (s *Server) HandleHeader(key, val string, h http.Handler) {
func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) { func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) {
s.Handler.ServeHTTP(res, req) s.Handler.ServeHTTP(res, req)
} }
func (s *Server) Endpoint() (*url.URL, error) {
if err := s.listenAndEndpoint(); err != nil {
return nil, err
}
return s.endpoint, nil
}
func (s *Server) Start(ctx context.Context) error {
if err := s.listenAndEndpoint(); err != nil {
return err
}
s.BaseContext = func(listener net.Listener) context.Context {
return ctx
}
log.Infof("[HTTP] server listening on: %s", s.endpoint)
var err error
if s.tlsConf != nil {
err = s.ServeTLS(s.lis, "", "")
} else {
err = s.Serve(s.lis)
}
if !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
}
func (s *Server) Stop(ctx context.Context) error {
log.Info("[HTTP] server stopping")
return s.Shutdown(ctx)
}
func (s *Server) filter() mux.MiddlewareFunc { func (s *Server) filter() mux.MiddlewareFunc {
return func(handler http.Handler) http.Handler { return func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
@ -237,27 +275,38 @@ func (s *Server) filter() mux.MiddlewareFunc {
if route := mux.CurrentRoute(request); route != nil { if route := mux.CurrentRoute(request); route != nil {
pathTemplate, _ = route.GetPathTemplate() pathTemplate, _ = route.GetPathTemplate()
} }
tr := &Transport{} tr := &Transport{
operation: pathTemplate,
pathTemplate: pathTemplate,
reqHeader: headerCarrier(request.Header),
replyHeader: headerCarrier(writer.Header()),
request: request,
}
if s.endpoint != nil {
tr.endpoint = s.endpoint.String()
}
tr.request = request.WithContext(transport.NewServerContext(ctx, tr))
handler.ServeHTTP(writer, request)
}) })
} }
} }
func (s *Server) Endpoint() string { func (s *Server) listenAndEndpoint() error {
//TODO implement me if s.lis == nil {
panic("implement me") lis, err := net.Listen(s.network, s.address)
} if err != nil {
s.err = err
func (s *Server) Operation() string { return err
//TODO implement me }
panic("implement me") s.lis = lis
} }
if s.endpoint == nil {
func (s *Server) RequestHeader() transport.Header { addr, err := host.Extract(s.address, s.lis)
//TODO implement me if err != nil {
panic("implement me") s.err = err
} return err
}
func (s *Server) ReplyHeader() transport.Header { s.endpoint = endpoint.NewEndpoint(endpoint.Scheme("http", s.tlsConf != nil), addr)
//TODO implement me }
panic("implement me") return s.err
} }

@ -0,0 +1,96 @@
package status
import (
"google.golang.org/grpc/codes"
"net/http"
)
const (
ClientClosed = 499
)
type Converter interface {
ToGRPCCode(code int) codes.Code
FromGRPCCode(code codes.Code) int
}
var DefaultConverter Converter = statusConverter{}
type statusConverter struct {
}
func (s statusConverter) ToGRPCCode(code int) codes.Code {
switch code {
case http.StatusOK:
return codes.OK
case http.StatusBadRequest:
return codes.InvalidArgument
case http.StatusUnauthorized:
return codes.Unauthenticated
case http.StatusForbidden:
return codes.PermissionDenied
case http.StatusNotFound:
return codes.NotFound
case http.StatusConflict:
return codes.Aborted
case http.StatusTooManyRequests:
return codes.ResourceExhausted
case http.StatusInternalServerError:
return codes.Internal
case http.StatusNotImplemented:
return codes.Unimplemented
case http.StatusServiceUnavailable:
return codes.Unavailable
case http.StatusGatewayTimeout:
return codes.DeadlineExceeded
case ClientClosed:
return codes.Canceled
}
return codes.Unknown
}
func (s statusConverter) FromGRPCCode(code codes.Code) int {
switch code {
case codes.OK:
return http.StatusOK
case codes.Canceled:
return ClientClosed
case codes.Unknown:
return http.StatusInternalServerError
case codes.InvalidArgument:
return http.StatusBadRequest
case codes.DeadlineExceeded:
return http.StatusGatewayTimeout
case codes.NotFound:
return http.StatusNotFound
case codes.AlreadyExists:
return http.StatusConflict
case codes.PermissionDenied:
return http.StatusForbidden
case codes.Unauthenticated:
return http.StatusUnauthorized
case codes.ResourceExhausted:
return http.StatusTooManyRequests
case codes.FailedPrecondition:
return http.StatusBadRequest
case codes.Aborted:
return http.StatusConflict
case codes.OutOfRange:
return http.StatusBadRequest
case codes.Unimplemented:
return http.StatusNotImplemented
case codes.Internal:
return http.StatusInternalServerError
case codes.Unavailable:
return http.StatusServiceUnavailable
case codes.DataLoss:
return http.StatusInternalServerError
}
return http.StatusInternalServerError
}
func ToGRPCCode(code int) codes.Code {
return DefaultConverter.ToGRPCCode(code)
}
func FromGRPCCode(code codes.Code) int {
return DefaultConverter.FromGRPCCode(code)
}

@ -63,3 +63,21 @@ func RequestFromServerContext(ctx context.Context) (*http.Request, bool) {
} }
return nil, false return nil, false
} }
type headerCarrier http.Header
func (h headerCarrier) Get(key string) string {
return http.Header(h).Get(key)
}
func (h headerCarrier) Set(key string, value string) {
http.Header(h).Set(key, value)
}
func (h headerCarrier) Keys() []string {
kvs := make([]string, 0, len(h))
for k := range http.Header(h) {
kvs = append(kvs, k)
}
return kvs
}

Loading…
Cancel
Save