Compare commits

..

No commits in common. "main" and "v0.0.3" have entirely different histories.
main ... v0.0.3

38 changed files with 147 additions and 2440 deletions

3
.gitignore vendored
View File

@ -1,5 +1,5 @@
bin/ bin/
tests/
.svn/ .svn/
.godeps .godeps
./build ./build
@ -11,7 +11,6 @@ _posts
.vscode .vscode
vendor vendor
cmd/ cmd/
tools/
third_party/ third_party/
# Go.gitignore # Go.gitignore

View File

@ -4,99 +4,3 @@
# 环境变量
| 环境变量 | 描述 |
| --- | --- |
| AEUS_DEBUG | 是否开启debug模式 |
| HTTP_PORT | http服务端口 |
| GRPC_PORT | grpc服务端口 |
| CLI_PORT | cli服务端口 |
# 快速开始
## 创建一个项目
创建项目可以使用`aeus`命令行工具进行生成:
```
aeus new github.com/your-username/your-project-name
```
如果需要创建一个带管理后台的应用, 可以使用`--admin`参数:
```
aeus new github.com/your-username/your-project-name --admin
```
## 生成`Proto`文件
服务使用`proto3`作为通信协议,因此需要生成`Proto`文件。
```
make proto
```
清理生成的文件使用:
```
make proto-clean
```
## 编译项目
编译项目可以使用`make`命令进行编译:
```
make build
```
# 目录结构
```
├── api
│ └── v1
├── cmd
│ ├── main.go
├── config
│ ├── config.go
│ └── config.yaml
├── deploy
│ └── docker
├── go.mod
├── go.sum
├── internal
│ ├── models
│ ├── scope
│ ├── service
├── Makefile
├── README.md
├── third_party
│ ├── aeus
│ ├── errors
│ ├── google
│ ├── openapi
│ ├── README.md
│ └── validate
├── vendor
├── version
│ └── version.go
├── web
└── webhook.yaml
```
| 目录 | 描述 |
| --- | --- |
| api | api定义目录 |
| cmd | 启动命令目录 |
| config | 配置目录 |
| deploy | 部署目录 |
| internal | 内部文件目录 |
| internal.service | 服务定义目录 |
| internal.models | 模型定义目录 |
| internal.scope | 服务scope定义目录,主要有全局的变量(比如DB,Redis等) |
| third_party | 第三方proto文件目录 |
| web | 前端资源目录 |

26
app.go
View File

@ -6,7 +6,6 @@ import (
"os/signal" "os/signal"
"reflect" "reflect"
"runtime" "runtime"
"strconv"
"sync/atomic" "sync/atomic"
"syscall" "syscall"
"time" "time"
@ -36,19 +35,12 @@ func (s *Service) Name() string {
} }
func (s *Service) Debug() bool { func (s *Service) Debug() bool {
if s.opts != nil {
return s.opts.debug
}
return false return false
} }
func (s *Service) Version() string { func (s *Service) Version() string {
if s.opts != nil {
return s.opts.version return s.opts.version
} }
return ""
}
func (s *Service) Metadata() map[string]string { func (s *Service) Metadata() map[string]string {
if s.service == nil { if s.service == nil {
return nil return nil
@ -109,14 +101,10 @@ func (s *Service) injectVars(v any) {
continue continue
} }
fieldType := refType.Field(i) fieldType := refType.Field(i)
if !(fieldType.Type.Kind() != reflect.Ptr || fieldType.Type.Kind() != reflect.Interface) { if fieldType.Type.Kind() != reflect.Ptr {
continue continue
} }
for _, rv := range s.refValues { for _, rv := range s.refValues {
if fieldType.Type.Kind() == reflect.Interface && rv.Type().Implements(fieldType.Type) {
refValue.Field(i).Set(rv)
break
}
if fieldType.Type == rv.Type() { if fieldType.Type == rv.Type() {
refValue.Field(i).Set(rv) refValue.Field(i).Set(rv)
break break
@ -126,11 +114,8 @@ func (s *Service) injectVars(v any) {
} }
func (s *Service) preStart(ctx context.Context) (err error) { func (s *Service) preStart(ctx context.Context) (err error) {
s.Logger().Info(ctx, "starting") s.Logger().Info(s.ctx, "starting")
s.refValues = append(s.refValues, s.opts.injectVars...)
s.refValues = append(s.refValues, reflect.ValueOf(s.Logger()))
for _, ptr := range s.opts.servers { for _, ptr := range s.opts.servers {
s.injectVars(ptr)
s.refValues = append(s.refValues, reflect.ValueOf(ptr)) s.refValues = append(s.refValues, reflect.ValueOf(ptr))
} }
if s.opts.registry != nil { if s.opts.registry != nil {
@ -192,7 +177,7 @@ func (s *Service) preStart(ctx context.Context) (err error) {
o.Context = ctx o.Context = ctx
o.TTL = s.opts.registrarTimeout o.TTL = s.opts.registrarTimeout
}); err != nil { }); err != nil {
s.Logger().Warnf(ctx, "service register error: %v", err) s.Logger().Warn(ctx, "service register error: %v", err)
} }
} }
} }
@ -213,14 +198,14 @@ func (s *Service) preStop() (err error) {
}() }()
for _, srv := range s.opts.servers { for _, srv := range s.opts.servers {
if err = srv.Stop(ctx); err != nil { if err = srv.Stop(ctx); err != nil {
s.Logger().Warnf(ctx, "server stop error: %v", err) s.Logger().Warn(ctx, "server stop error: %v", err)
} }
} }
if s.opts.registry != nil { if s.opts.registry != nil {
if err = s.opts.registry.Deregister(s.service, func(o *registry.DeregisterOptions) { if err = s.opts.registry.Deregister(s.service, func(o *registry.DeregisterOptions) {
o.Context = ctx o.Context = ctx
}); err != nil { }); err != nil {
s.Logger().Warnf(ctx, "server deregister error: %v", err) s.Logger().Warn(ctx, "server deregister error: %v", err)
} }
} }
s.Logger().Info(ctx, "stopped") s.Logger().Info(ctx, "stopped")
@ -270,7 +255,6 @@ func New(cbs ...Option) *Service {
registrarTimeout: time.Second * 30, registrarTimeout: time.Second * 30,
}, },
} }
s.opts.debug, _ = strconv.ParseBool("AEUS_DEBUG")
s.opts.metadata = make(map[string]string) s.opts.metadata = make(map[string]string)
for _, cb := range cbs { for _, cb := range cbs {
cb(s.opts) cb(s.opts)

11
go.mod
View File

@ -5,32 +5,25 @@ go 1.23.0
toolchain go1.23.9 toolchain go1.23.9
require ( require (
github.com/envoyproxy/protoc-gen-validate v1.2.1
github.com/gin-gonic/gin v1.10.1 github.com/gin-gonic/gin v1.10.1
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/mattn/go-runewidth v0.0.16 github.com/mattn/go-runewidth v0.0.16
github.com/peterh/liner v1.2.2 github.com/peterh/liner v1.2.2
github.com/redis/go-redis/v9 v9.10.0
github.com/spf13/cobra v1.9.1
go.etcd.io/etcd/api/v3 v3.6.0 go.etcd.io/etcd/api/v3 v3.6.0
go.etcd.io/etcd/client/v3 v3.6.0 go.etcd.io/etcd/client/v3 v3.6.0
golang.org/x/sync v0.12.0 golang.org/x/sync v0.12.0
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb
google.golang.org/grpc v1.72.2 google.golang.org/grpc v1.72.2
google.golang.org/protobuf v1.36.5 google.golang.org/protobuf v1.36.5
gorm.io/gorm v1.30.0
) )
require ( require (
github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect github.com/cloudwego/iasm v0.2.0 // indirect
github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
@ -40,9 +33,6 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
@ -51,7 +41,6 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.6.0 // indirect go.etcd.io/etcd/client/pkg/v3 v3.6.0 // indirect

28
go.sum
View File

@ -1,13 +1,7 @@
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
@ -16,14 +10,9 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@ -47,8 +36,6 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
@ -58,12 +45,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@ -94,17 +75,10 @@ github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw=
github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI= github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs=
github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@ -203,7 +177,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@ -2,7 +2,6 @@ package metadata
import ( import (
"context" "context"
"iter"
"maps" "maps"
"strings" "strings"
) )
@ -12,78 +11,35 @@ type metadataKey struct{}
// Metadata is our way of representing request headers internally. // Metadata is our way of representing request headers internally.
// They're used at the RPC level and translate back and forth // They're used at the RPC level and translate back and forth
// from Transport headers. // from Transport headers.
type Metadata map[string]string
type Metadata struct {
teeReader TeeReader
teeWriter TeeWriter
variables map[string]string
}
func canonicalMetadataKey(key string) string { func canonicalMetadataKey(key string) string {
return strings.ToLower(key) return strings.ToLower(key)
} }
// TeeReader sets the tee reader. func (md Metadata) Has(key string) bool {
func (m *Metadata) TeeReader(r TeeReader) { _, ok := md[canonicalMetadataKey(key)]
m.teeReader = r
}
// TeeWriter sets the tee writer.
func (m *Metadata) TeeWriter(w TeeWriter) {
m.teeWriter = w
}
// Has returns true if the metadata contains the given key.
func (m *Metadata) Has(key string) bool {
_, ok := m.Get(key)
return ok return ok
} }
// Get returns the first value associated with the given key. func (md Metadata) Get(key string) (string, bool) {
func (m *Metadata) Get(key string) (string, bool) { val, ok := md[canonicalMetadataKey(key)]
key = canonicalMetadataKey(key)
val, ok := m.variables[key]
if !ok && m.teeReader != nil {
if val = m.teeReader.Get(key); val != "" {
ok = true
}
}
return val, ok return val, ok
} }
// Set sets a metadata key/value pair. func (md Metadata) Set(key, val string) {
func (m *Metadata) Set(key, val string) { md[canonicalMetadataKey(key)] = val
if m.variables == nil {
m.variables = make(map[string]string)
}
key = canonicalMetadataKey(key)
m.variables[key] = val
if m.teeWriter != nil {
m.teeWriter.Set(key, val)
}
} }
// Delete removes a key from the metadata. func (md Metadata) Delete(key string) {
func (m *Metadata) Delete(key string) { delete(md, canonicalMetadataKey(key))
key = canonicalMetadataKey(key)
if m.variables != nil {
delete(m.variables, key)
}
if m.teeWriter != nil {
m.teeWriter.Set(key, "")
}
} }
// Keys returns a sequence of the metadata keys. // Copy makes a copy of the metadata.
func (m *Metadata) Keys() iter.Seq[string] { func Copy(md Metadata) Metadata {
return func(yield func(string) bool) { cmd := make(Metadata, len(md))
for k := range m.variables { maps.Copy(cmd, md)
if !yield(k) { return cmd
return
}
}
}
} }
// Delete key from metadata. // Delete key from metadata.
@ -93,60 +49,67 @@ func Delete(ctx context.Context, k string) context.Context {
// Set add key with val to metadata. // Set add key with val to metadata.
func Set(ctx context.Context, k, v string) context.Context { func Set(ctx context.Context, k, v string) context.Context {
md := FromContext(ctx) md, ok := FromContext(ctx)
if !ok {
md = make(Metadata)
}
k = canonicalMetadataKey(k) k = canonicalMetadataKey(k)
if v == "" { if v == "" {
md.Delete(k) delete(md, k)
} else { } else {
md.Set(k, v) md[k] = v
} }
return context.WithValue(ctx, metadataKey{}, md) return context.WithValue(ctx, metadataKey{}, md)
} }
// Get returns a single value from metadata in the context. // Get returns a single value from metadata in the context.
func Get(ctx context.Context, key string) (string, bool) { func Get(ctx context.Context, key string) (string, bool) {
md := FromContext(ctx) md, ok := FromContext(ctx)
if !ok {
return "", ok
}
key = canonicalMetadataKey(key) key = canonicalMetadataKey(key)
val, ok := md.Get(key) val, ok := md[canonicalMetadataKey(key)]
return val, ok return val, ok
} }
// FromContext returns metadata from the given context. // FromContext returns metadata from the given context.
func FromContext(ctx context.Context) *Metadata { func FromContext(ctx context.Context) (Metadata, bool) {
md, ok := ctx.Value(metadataKey{}).(*Metadata) md, ok := ctx.Value(metadataKey{}).(Metadata)
if !ok { if !ok {
return New() return nil, ok
} }
return md
// capitalise all values
newMD := make(Metadata, len(md))
for k, v := range md {
newMD[canonicalMetadataKey(k)] = v
}
return newMD, ok
} }
// NewContext creates a new context with the given metadata. // NewContext creates a new context with the given metadata.
func NewContext(ctx context.Context, md *Metadata) context.Context { func NewContext(ctx context.Context, md Metadata) context.Context {
return context.WithValue(ctx, metadataKey{}, md) return context.WithValue(ctx, metadataKey{}, md)
} }
// MergeContext merges metadata to existing metadata, overwriting if specified. // MergeContext merges metadata to existing metadata, overwriting if specified.
func MergeContext(ctx context.Context, patchMd *Metadata, overwrite bool) context.Context { func MergeContext(ctx context.Context, patchMd Metadata, overwrite bool) context.Context {
if ctx == nil { if ctx == nil {
ctx = context.Background() ctx = context.Background()
} }
md, _ := ctx.Value(metadataKey{}).(Metadata) md, _ := ctx.Value(metadataKey{}).(Metadata)
cmd := New() cmd := make(Metadata, len(md))
maps.Copy(cmd.variables, md.variables) maps.Copy(cmd, md)
for k, v := range patchMd.variables { for k, v := range patchMd {
if _, ok := cmd.variables[k]; ok && !overwrite { if _, ok := cmd[k]; ok && !overwrite {
// skip // skip
} else if v != "" { } else if v != "" {
cmd.variables[k] = v cmd[k] = v
} else { } else {
delete(cmd.variables, k) delete(cmd, k)
} }
} }
return context.WithValue(ctx, metadataKey{}, cmd) return context.WithValue(ctx, metadataKey{}, cmd)
} }
func New() *Metadata {
return &Metadata{
variables: make(map[string]string, 16),
}
}

View File

@ -5,13 +5,3 @@ const (
RequestPathKey = "X-AEUS-Request-Path" RequestPathKey = "X-AEUS-Request-Path"
RequestProtocolKey = "X-AEUS-Request-Protocol" RequestProtocolKey = "X-AEUS-Request-Protocol"
) )
type (
TeeReader interface {
Get(string) string
}
TeeWriter interface {
Set(string, string)
}
)

View File

@ -1,164 +1 @@
package auth package auth
import (
"context"
"reflect"
"strings"
"git.nobla.cn/golang/aeus/metadata"
"git.nobla.cn/golang/aeus/middleware"
"git.nobla.cn/golang/aeus/pkg/errors"
jwt "github.com/golang-jwt/jwt/v5"
)
type authKey struct{}
const (
// bearerWord the bearer key word for authorization
bearerWord string = "Bearer"
// bearerFormat authorization token format
bearerFormat string = "Bearer %s"
// authorizationKey holds the key used to store the JWT Token in the request tokenHeader.
authorizationKey string = "Authorization"
// reason holds the error reason.
reason string = "UNAUTHORIZED"
)
type Option func(*options)
type Validate interface {
Validate(ctx context.Context, token string) error
}
// Parser is a jwt parser
type options struct {
allows []string
claims reflect.Type
validate Validate
}
// WithAllow with allow path
func WithAllow(paths ...string) Option {
return func(o *options) {
if o.allows == nil {
o.allows = make([]string, 0, 16)
}
for _, s := range paths {
s = strings.TrimSpace(s)
if len(s) == 0 {
continue
}
o.allows = append(o.allows, s)
}
}
}
func WithClaims(claims any) Option {
return func(o *options) {
if tv, ok := claims.(reflect.Type); ok {
o.claims = tv
} else {
o.claims = reflect.TypeOf(claims)
if o.claims.Kind() == reflect.Ptr {
o.claims = o.claims.Elem()
}
}
}
}
func WithValidate(fn Validate) Option {
return func(o *options) {
o.validate = fn
}
}
// isAllowed check if the path is allowed
func isAllowed(uripath string, allows []string) bool {
for _, pattern := range allows {
n := len(pattern)
if pattern == uripath {
return true
}
if pattern == "*" {
return true
}
if n > 1 && pattern[n-1] == '*' {
if strings.HasPrefix(uripath, pattern[:n-1]) {
return true
}
}
}
return false
}
// JWT auth middleware
func JWT(keyFunc jwt.Keyfunc, cbs ...Option) middleware.Middleware {
opts := options{}
for _, cb := range cbs {
cb(&opts)
}
return func(next middleware.Handler) middleware.Handler {
return func(ctx context.Context) (err error) {
md := metadata.FromContext(ctx)
if len(opts.allows) > 0 {
requestPath, ok := md.Get(metadata.RequestPathKey)
if ok {
if isAllowed(requestPath, opts.allows) {
return next(ctx)
}
}
}
token, ok := md.Get(authorizationKey)
if !ok {
return errors.ErrAccessDenied
}
if opts.validate != nil {
if err = opts.validate.Validate(ctx, token); err != nil {
return err
}
}
token, _ = strings.CutPrefix(token, bearerWord)
var (
ti *jwt.Token
)
token = strings.TrimSpace(token)
if opts.claims != nil {
if claims, ok := reflect.New(opts.claims).Interface().(jwt.Claims); ok {
ti, err = jwt.ParseWithClaims(token, claims, keyFunc)
}
}
if ti == nil {
ti, err = jwt.Parse(token, keyFunc)
}
if err != nil {
if errors.Is(err, jwt.ErrTokenMalformed) || errors.Is(err, jwt.ErrTokenUnverifiable) {
return errors.ErrAccessDenied
}
if errors.Is(err, jwt.ErrTokenNotValidYet) || errors.Is(err, jwt.ErrTokenExpired) {
return errors.ErrTokenExpired
}
return errors.ErrPermissionDenied
}
if !ti.Valid {
return errors.ErrPermissionDenied
}
ctx = NewContext(ctx, ti.Claims)
return next(ctx)
}
}
}
// NewContext put auth info into context
func NewContext(ctx context.Context, info jwt.Claims) context.Context {
return context.WithValue(ctx, authKey{}, info)
}
// FromContext extract auth info from context
func FromContext(ctx context.Context) (token jwt.Claims, ok bool) {
token, ok = ctx.Value(authKey{}).(jwt.Claims)
return
}

View File

@ -2,8 +2,6 @@ package aeus
import ( import (
"context" "context"
"maps"
"reflect"
"time" "time"
"git.nobla.cn/golang/aeus/pkg/logger" "git.nobla.cn/golang/aeus/pkg/logger"
@ -20,12 +18,10 @@ type options struct {
servers []Server servers []Server
endpoints []string endpoints []string
scope Scope scope Scope
debug bool
registrarTimeout time.Duration registrarTimeout time.Duration
registry registry.Registry registry registry.Registry
serviceLoader ServiceLoader serviceLoader ServiceLoader
stopTimeout time.Duration stopTimeout time.Duration
injectVars []reflect.Value
} }
func WithName(name string) Option { func WithName(name string) Option {
@ -45,7 +41,9 @@ func WithMetadata(metadata map[string]string) Option {
if o.metadata == nil { if o.metadata == nil {
o.metadata = make(map[string]string) o.metadata = make(map[string]string)
} }
maps.Copy(o.metadata, metadata) for k, v := range metadata {
o.metadata[k] = v
}
} }
} }
@ -73,20 +71,6 @@ func WithScope(scope Scope) Option {
} }
} }
func WithDebug(debug bool) Option {
return func(o *options) {
o.debug = debug
}
}
func WithInjectVars(vars ...any) Option {
return func(o *options) {
for _, v := range vars {
o.injectVars = append(o.injectVars, reflect.ValueOf(v))
}
}
}
func WithServiceLoader(loader ServiceLoader) Option { func WithServiceLoader(loader ServiceLoader) Option {
return func(o *options) { return func(o *options) {
o.serviceLoader = loader o.serviceLoader = loader

View File

@ -1,12 +0,0 @@
//go:build appengine
// +build appengine
package bs
func BytesToString(b []byte) string {
return string(b)
}
func StringToBytes(s string) []byte {
return []byte(s)
}

View File

@ -1,23 +0,0 @@
//go:build !appengine
// +build !appengine
package bs
import (
"unsafe"
)
// BytesToString converts byte slice to string.
func BytesToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
// StringToBytes converts string to byte slice.
func StringToBytes(s string) []byte {
return *(*[]byte)(unsafe.Pointer(
&struct {
string
Cap int
}{s, len(s)},
))
}

18
pkg/cache/cache.go vendored
View File

@ -13,29 +13,23 @@ var (
type Cache interface { type Cache interface {
// Get gets a cached value by key. // Get gets a cached value by key.
Load(ctx context.Context, key string, val any) error Get(ctx context.Context, key string) (any, time.Time, error)
// Get gets a cached value by key.
Exists(ctx context.Context, key string) (bool, error)
// Put stores a key-value pair into cache. // Put stores a key-value pair into cache.
Store(ctx context.Context, key string, val any, d time.Duration) error Put(ctx context.Context, key string, val any, d time.Duration) error
// Delete removes a key from cache. // Delete removes a key from cache.
Delete(ctx context.Context, key string) error Delete(ctx context.Context, key string) error
// String returns the name of the implementation. // String returns the name of the implementation.
String() string String() string
} }
func Default() Cache {
return std
}
// Get gets a cached value by key. // Get gets a cached value by key.
func Load(ctx context.Context, key string, val any) error { func Get(ctx context.Context, key string) (any, time.Time, error) {
return std.Load(ctx, key, val) return std.Get(ctx, key)
} }
// Put stores a key-value pair into cache. // Put stores a key-value pair into cache.
func Store(ctx context.Context, key string, val any, d time.Duration) error { func Put(ctx context.Context, key string, val any, d time.Duration) error {
return std.Store(ctx, key, val, d) return std.Put(ctx, key, val, d)
} }
// String returns the name of the implementation. // String returns the name of the implementation.

View File

@ -2,18 +2,10 @@ package memory
import ( import (
"context" "context"
"fmt"
"reflect"
"sync" "sync"
"time" "time"
"errors" "git.nobla.cn/golang/aeus/pkg/errors"
)
var (
ErrWongType = errors.New("val must be a pointer")
ErrNotExists = errors.New("not exists")
ErrAaddressable = errors.New("cannot set value: val is not addressable")
) )
type memCache struct { type memCache struct {
@ -23,47 +15,22 @@ type memCache struct {
sync.RWMutex sync.RWMutex
} }
func (c *memCache) Load(ctx context.Context, key string, val any) error { func (c *memCache) Get(ctx context.Context, key string) (interface{}, time.Time, error) {
c.RWMutex.RLock() c.RWMutex.RLock()
defer c.RWMutex.RUnlock() defer c.RWMutex.RUnlock()
item, found := c.items[key] item, found := c.items[key]
if !found { if !found {
return ErrNotExists return nil, time.Time{}, errors.ErrNotFound
} }
if item.Expired() { if item.Expired() {
return ErrNotExists return nil, time.Time{}, errors.ErrExpired
}
refValue := reflect.ValueOf(val)
if refValue.Type().Kind() != reflect.Ptr {
return ErrWongType
}
refElem := refValue.Elem()
if !refElem.CanSet() {
return ErrAaddressable
}
targetValue := reflect.Indirect(reflect.ValueOf(item.Value))
if targetValue.Type() != refElem.Type() {
return fmt.Errorf("type mismatch: expected %v, got %v", refElem.Type(), targetValue.Type())
}
refElem.Set(targetValue)
return nil
} }
func (c *memCache) Exists(ctx context.Context, key string) (bool, error) { return item.Value, time.Unix(0, item.Expiration), nil
c.RWMutex.RLock()
defer c.RWMutex.RUnlock()
item, found := c.items[key]
if !found {
return false, nil
}
if item.Expired() {
return false, nil
}
return true, nil
} }
func (c *memCache) Store(ctx context.Context, key string, val any, d time.Duration) error { func (c *memCache) Put(ctx context.Context, key string, val interface{}, d time.Duration) error {
var e int64 var e int64
if d == DefaultExpiration { if d == DefaultExpiration {
d = c.opts.Expiration d = c.opts.Expiration
@ -89,8 +56,9 @@ func (c *memCache) Delete(ctx context.Context, key string) error {
_, found := c.items[key] _, found := c.items[key]
if !found { if !found {
return ErrNotExists return errors.ErrNotFound
} }
delete(c.items, key) delete(c.items, key)
return nil return nil
} }
@ -98,18 +66,3 @@ func (c *memCache) Delete(ctx context.Context, key string) error {
func (m *memCache) String() string { func (m *memCache) String() string {
return "memory" return "memory"
} }
// NewCache returns a new cache.
func NewCache(opts ...Option) *memCache {
options := NewOptions(opts...)
items := make(map[string]Item)
if len(options.Items) > 0 {
items = options.Items
}
return &memCache{
opts: options,
items: items,
}
}

View File

@ -4,7 +4,7 @@ import "time"
// Item represents an item stored in the cache. // Item represents an item stored in the cache.
type Item struct { type Item struct {
Value any Value interface{}
Expiration int64 Expiration int64
} }
@ -16,3 +16,18 @@ func (i *Item) Expired() bool {
return time.Now().UnixNano() > i.Expiration return time.Now().UnixNano() > i.Expiration
} }
// NewCache returns a new cache.
func NewCache(opts ...Option) *memCache {
options := NewOptions(opts...)
items := make(map[string]Item)
if len(options.Items) > 0 {
items = options.Items
}
return &memCache{
opts: options,
items: items,
}
}

View File

@ -1,70 +0,0 @@
package redis
import (
"context"
"encoding/json"
"time"
"git.nobla.cn/golang/aeus"
)
type redisCache struct {
opts *options
}
func (c *redisCache) buildKey(key string) string {
return c.opts.prefix + key
}
func (c *redisCache) Load(ctx context.Context, key string, val any) error {
var (
err error
buf []byte
)
if buf, err = c.opts.client.Get(ctx, c.buildKey(key)).Bytes(); err == nil {
err = json.Unmarshal(buf, val)
}
return err
}
func (c *redisCache) Exists(ctx context.Context, key string) (ok bool, err error) {
var n int64
n, err = c.opts.client.Exists(ctx, c.buildKey(key)).Result()
if n > 0 {
ok = true
}
return
}
// Put stores a key-value pair into cache.
func (c *redisCache) Store(ctx context.Context, key string, val any, d time.Duration) error {
var (
err error
buf []byte
)
if buf, err = json.Marshal(val); err == nil {
err = c.opts.client.Set(ctx, c.buildKey(key), buf, d).Err()
}
return err
}
// Delete removes a key from cache.
func (c *redisCache) Delete(ctx context.Context, key string) error {
return c.opts.client.Del(ctx, c.buildKey(key)).Err()
}
// String returns the name of the implementation.
func (c *redisCache) String() string {
return "redis"
}
func NewCache(opts ...Option) *redisCache {
cache := &redisCache{
opts: newOptions(opts...),
}
app := aeus.FromContext(cache.opts.context)
if app != nil {
cache.opts.prefix = app.Name() + ":" + cache.opts.prefix
}
return cache
}

View File

@ -1,46 +0,0 @@
package redis
import (
"context"
"github.com/redis/go-redis/v9"
)
type (
options struct {
context context.Context
client *redis.Client
prefix string
}
Option func(*options)
)
func WithClient(client *redis.Client) Option {
return func(o *options) {
o.client = client
}
}
func WithContext(ctx context.Context) Option {
return func(o *options) {
o.context = ctx
}
}
func WithPrefix(prefix string) Option {
return func(o *options) {
o.prefix = prefix
}
}
// NewOptions returns a new options struct.
func newOptions(opts ...Option) *options {
options := &options{
prefix: "cache:",
}
for _, o := range opts {
o(options)
}
return options
}

View File

@ -4,31 +4,21 @@ const (
OK = 0 //success OK = 0 //success
Exit = 1000 //normal exit Exit = 1000 //normal exit
Invalid = 1001 //payload invalid Invalid = 1001 //payload invalid
Exists = 1002 //already exists Timeout = 1002 //timeout
Unavailable = 1003 //service unavailable Expired = 1003 //expired
Incompatible = 1004 //type incompatible
Timeout = 2001 //timeout
Expired = 2002 //expired
TokenExpired = 4002 //token expired
NotFound = 4004 //not found
PermissionDenied = 4003 //permission denied
AccessDenied = 4005 //access denied AccessDenied = 4005 //access denied
NetworkUnreachable = 5001 //network unreachable PermissionDenied = 4003 //permission denied
ConnectionRefused = 5002 //connection refused NotFound = 4004 //not found
Unavailable = 5000 //service unavailable
) )
var ( var (
ErrExit = New(Exit, "normal exit") ErrExit = New(Exit, "normal exit")
ErrTimeout = New(Timeout, "timeout") ErrTimeout = New(Timeout, "timeout")
ErrExists = New(Exists, "already exists")
ErrExpired = New(Expired, "expired") ErrExpired = New(Expired, "expired")
ErrInvalid = New(Invalid, "invalid payload") ErrValidate = New(Invalid, "invalid payload")
ErrNotFound = New(NotFound, "not found") ErrNotFound = New(NotFound, "not found")
ErrAccessDenied = New(AccessDenied, "access denied") ErrAccessDenied = New(AccessDenied, "access denied")
ErrPermissionDenied = New(PermissionDenied, "permission denied") ErrPermissionDenied = New(PermissionDenied, "permission denied")
ErrTokenExpired = New(TokenExpired, "token expired")
ErrUnavailable = New(Unavailable, "service unavailable") ErrUnavailable = New(Unavailable, "service unavailable")
ErrNetworkUnreachable = New(NetworkUnreachable, "network unreachable")
ErrConnectionRefused = New(ConnectionRefused, "connection refused")
ErrIncompatible = New(Incompatible, "incompatible")
) )

View File

@ -14,23 +14,8 @@ func (e *Error) Error() string {
return fmt.Sprintf("code: %d, message: %s", e.Code, e.Message) return fmt.Sprintf("code: %d, message: %s", e.Code, e.Message)
} }
func (e *Error) GetCode() int { func Format(code int, msg string, args ...any) Error {
return e.Code return Error{
}
func (e *Error) GetMessage() string {
return e.Message
}
func Warp(code int, err error) error {
return &Error{
Code: code,
Message: err.Error(),
}
}
func Format(code int, msg string, args ...any) *Error {
return &Error{
Code: code, Code: code,
Message: fmt.Sprintf(msg, args...), Message: fmt.Sprintf(msg, args...),
} }

View File

@ -1,27 +0,0 @@
package httpclient
import (
"encoding/base64"
"fmt"
)
type Authorization interface {
Token() string
}
type BasicAuth struct {
Username string
Password string
}
type BearerAuth struct {
AccessToken string
}
func (auth *BasicAuth) Token() string {
return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(auth.Username+":"+auth.Password)))
}
func (auth *BearerAuth) Token() string {
return fmt.Sprintf("Bearer %s", auth.AccessToken)
}

View File

@ -1,174 +0,0 @@
package httpclient
import (
"crypto/tls"
"io"
"net"
"net/http"
"net/http/cookiejar"
"strings"
"time"
)
type (
BeforeRequest func(req *http.Request) (err error)
AfterRequest func(req *http.Request, res *http.Response) (err error)
Client struct {
baseUrl string
Authorization Authorization
client *http.Client
cookieJar *cookiejar.Jar
interceptorRequest []BeforeRequest
interceptorResponse []AfterRequest
}
)
var (
DefaultClient = &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
Proxy: http.ProxyFromEnvironment,
ForceAttemptHTTP2: true,
MaxIdleConns: 64,
MaxIdleConnsPerHost: 8,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
Timeout: time.Second * 30,
}
)
func (client *Client) stashUri(urlPath string) string {
var (
pos int
)
if len(urlPath) == 0 {
return client.baseUrl
}
if pos = strings.Index(urlPath, "//"); pos == -1 {
if client.baseUrl != "" {
if urlPath[0] != '/' {
urlPath = "/" + urlPath
}
return client.baseUrl + urlPath
}
}
return urlPath
}
func (client *Client) BeforeRequest(cb BeforeRequest) *Client {
client.interceptorRequest = append(client.interceptorRequest, cb)
return client
}
func (client *Client) AfterRequest(cb AfterRequest) *Client {
client.interceptorResponse = append(client.interceptorResponse, cb)
return client
}
func (client *Client) SetBaseUrl(s string) *Client {
client.baseUrl = strings.TrimSuffix(s, "/")
return client
}
func (client *Client) SetCookieJar(cookieJar *cookiejar.Jar) *Client {
client.client.Jar = cookieJar
return client
}
func (client *Client) SetClient(httpClient *http.Client) *Client {
client.client = httpClient
if client.cookieJar != nil {
client.client.Jar = client.cookieJar
}
return client
}
func (client *Client) SetTransport(transport http.RoundTripper) *Client {
client.client.Transport = transport
return client
}
func (client *Client) Get(urlPath string) *Request {
return newRequest(http.MethodGet, client.stashUri(urlPath), client)
}
func (client *Client) Put(urlPath string) *Request {
return newRequest(http.MethodPut, client.stashUri(urlPath), client)
}
func (client *Client) Post(urlPath string) *Request {
return newRequest(http.MethodPost, client.stashUri(urlPath), client)
}
func (client *Client) Delete(urlPath string) *Request {
return newRequest(http.MethodDelete, client.stashUri(urlPath), client)
}
func (client *Client) execute(r *Request) (res *http.Response, err error) {
var (
n int
reader io.Reader
)
if r.contentType == "" && r.body != nil {
r.contentType = r.detectContentType(r.body)
}
if r.body != nil {
if reader, err = r.readRequestBody(r.contentType, r.body); err != nil {
return
}
}
if r.rawRequest, err = http.NewRequest(r.method, r.uri, reader); err != nil {
return
}
for k, vs := range r.header {
for _, v := range vs {
r.rawRequest.Header.Add(k, v)
}
}
if r.contentType != "" {
r.rawRequest.Header.Set("Content-Type", r.contentType)
}
if client.Authorization != nil {
r.rawRequest.Header.Set("Authorization", client.Authorization.Token())
}
if r.context != nil {
r.rawRequest = r.rawRequest.WithContext(r.context)
}
n = len(client.interceptorRequest)
for i := n - 1; i >= 0; i-- {
if err = client.interceptorRequest[i](r.rawRequest); err != nil {
return
}
}
if r.rawResponse, err = client.client.Do(r.rawRequest); err != nil {
return nil, err
}
n = len(client.interceptorResponse)
for i := n - 1; i >= 0; i-- {
if err = client.interceptorResponse[i](r.rawRequest, r.rawResponse); err != nil {
_ = r.rawResponse.Body.Close()
return
}
}
return r.rawResponse, err
}
func New() *Client {
client := &Client{
client: DefaultClient,
interceptorRequest: make([]BeforeRequest, 0, 10),
interceptorResponse: make([]AfterRequest, 0, 10),
}
client.cookieJar, _ = cookiejar.New(nil)
client.client.Jar = client.cookieJar
return client
}

View File

@ -1,28 +0,0 @@
package httpclient
import (
"bytes"
"encoding/json"
"io"
"strings"
)
func encodeBody(data any) (r io.Reader, contentType string, err error) {
var (
buf []byte
)
switch v := data.(type) {
case string:
r = strings.NewReader(v)
contentType = "x-www-form-urlencoded"
case []byte:
r = bytes.NewReader(v)
contentType = "x-www-form-urlencoded"
default:
if buf, err = json.Marshal(v); err == nil {
r = bytes.NewReader(buf)
contentType = "application/json"
}
}
return
}

View File

@ -1,162 +0,0 @@
package httpclient
import (
"context"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"net/http"
"net/url"
"path"
"strings"
)
func doHttpRequest(req *http.Request, opts *options) (res *http.Response, err error) {
if opts.human {
if req.Header.Get("User-Agent") == "" {
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.54")
}
if req.Header.Get("Referer") == "" {
req.Header.Set("Referer", req.URL.String())
}
}
return opts.client.Do(req)
}
// Get performs a GET request to the specified URL with optional parameters and headers.
func Get(ctx context.Context, urlString string, cbs ...Option) (res *http.Response, err error) {
var (
uri *url.URL
req *http.Request
)
opts := newOptions()
for _, cb := range cbs {
cb(opts)
}
if uri, err = url.Parse(urlString); err != nil {
return
}
if opts.params != nil {
qs := uri.Query()
for k, v := range opts.params {
qs.Set(k, v)
}
uri.RawQuery = qs.Encode()
}
if req, err = http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil); err != nil {
return
}
if opts.header != nil {
for k, v := range opts.header {
req.Header.Set(k, v)
}
}
return doHttpRequest(req, opts)
}
// Post performs a POST request to the specified URL with optional parameters, headers, and data.
func Post(ctx context.Context, urlString string, cbs ...Option) (res *http.Response, err error) {
var (
uri *url.URL
req *http.Request
contentType string
reader io.Reader
)
opts := newOptions()
for _, cb := range cbs {
cb(opts)
}
if uri, err = url.Parse(urlString); err != nil {
return
}
if opts.params != nil {
qs := uri.Query()
for k, v := range opts.params {
qs.Set(k, v)
}
uri.RawQuery = qs.Encode()
}
if opts.body != nil {
if reader, contentType, err = encodeBody(opts.body); err != nil {
return
}
}
if req, err = http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), reader); err != nil {
return
}
if opts.header != nil {
for k, v := range opts.header {
req.Header.Set(k, v)
}
}
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
return doHttpRequest(req, opts)
}
// Do performs a request to the specified URL with optional parameters, headers, and data.
func Do(ctx context.Context, urlString string, result any, cbs ...Option) (err error) {
var (
contentType string
reader io.Reader
uri *url.URL
res *http.Response
req *http.Request
)
opts := newOptions()
for _, cb := range cbs {
cb(opts)
}
if uri, err = url.Parse(urlString); err != nil {
return
}
if opts.params != nil {
qs := uri.Query()
for k, v := range opts.params {
qs.Set(k, v)
}
uri.RawQuery = qs.Encode()
}
if opts.body != nil {
if reader, contentType, err = encodeBody(opts.body); err != nil {
return
}
}
if req, err = http.NewRequestWithContext(ctx, opts.method, uri.String(), reader); err != nil {
return
}
if opts.header != nil {
for k, v := range opts.header {
req.Header.Set(k, v)
}
}
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
if res, err = doHttpRequest(req, opts); err != nil {
return
}
defer func() {
_ = res.Body.Close()
}()
if res.StatusCode != http.StatusOK {
err = fmt.Errorf("unexpected status %s(%d)", res.Status, res.StatusCode)
return
}
//don't care response
if result == nil {
return nil
}
contentType = strings.ToLower(res.Header.Get("Content-Type"))
extName := path.Ext(req.URL.String())
if strings.Contains(contentType, JSON) || extName == ".json" {
err = json.NewDecoder(res.Body).Decode(result)
} else if strings.Contains(contentType, XML) || extName == ".xml" {
err = xml.NewDecoder(res.Body).Decode(result)
} else {
err = fmt.Errorf("unsupported content type: %s", contentType)
}
return
}

View File

@ -1,235 +0,0 @@
package httpclient
import (
"bytes"
"context"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"reflect"
"regexp"
"strings"
)
const (
JSON = "application/json"
XML = "application/xml"
plainTextType = "text/plain; charset=utf-8"
jsonContentType = "application/json"
formContentType = "application/x-www-form-urlencoded"
)
var (
jsonCheck = regexp.MustCompile(`(?i:(application|text)/(json|.*\+json|json\-.*)(;|$))`)
xmlCheck = regexp.MustCompile(`(?i:(application|text)/(xml|.*\+xml)(;|$))`)
)
type Request struct {
context context.Context
method string
uri string
url *url.URL
body any
query url.Values
formData url.Values
header http.Header
contentType string
authorization Authorization
client *Client
rawRequest *http.Request
rawResponse *http.Response
}
func (r *Request) detectContentType(body any) string {
contentType := plainTextType
kind := reflect.Indirect(reflect.ValueOf(body)).Type().Kind()
switch kind {
case reflect.Struct, reflect.Map:
contentType = jsonContentType
case reflect.String:
contentType = plainTextType
default:
if b, ok := body.([]byte); ok {
contentType = http.DetectContentType(b)
} else if kind == reflect.Slice {
contentType = jsonContentType
}
}
return contentType
}
func (r *Request) readRequestBody(contentType string, body any) (reader io.Reader, err error) {
var (
ok bool
s string
buf []byte
)
kind := reflect.Indirect(reflect.ValueOf(body)).Type().Kind()
if reader, ok = r.body.(io.Reader); ok {
return reader, nil
}
if buf, ok = r.body.([]byte); ok {
goto __end
}
if s, ok = r.body.(string); ok {
buf = []byte(s)
goto __end
}
if jsonCheck.MatchString(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) {
buf, err = json.Marshal(r.body)
goto __end
}
if xmlCheck.MatchString(contentType) && (kind == reflect.Struct) {
buf, err = xml.Marshal(r.body)
goto __end
}
err = fmt.Errorf("unmarshal content type %s", contentType)
__end:
if err == nil {
if len(buf) > 0 {
return bytes.NewReader(buf), nil
}
}
return
}
func (r *Request) SetContext(ctx context.Context) *Request {
r.context = ctx
return r
}
func (r *Request) AddQuery(k, v string) *Request {
r.query.Add(k, v)
return r
}
func (r *Request) SetQuery(vs map[string]string) *Request {
for k, v := range vs {
r.query.Set(k, v)
}
return r
}
func (r *Request) AddFormData(k, v string) *Request {
r.contentType = formContentType
r.formData.Add(k, v)
return r
}
func (r *Request) SetFormData(vs map[string]string) *Request {
r.contentType = formContentType
for k, v := range vs {
r.formData.Set(k, v)
}
return r
}
func (r *Request) SetBody(v any) *Request {
r.body = v
return r
}
func (r *Request) SetContentType(v string) *Request {
r.contentType = v
return r
}
func (r *Request) AddHeader(k, v string) *Request {
r.header.Add(k, v)
return r
}
func (r *Request) SetHeader(h http.Header) *Request {
r.header = h
return r
}
func (r *Request) Do() (res *http.Response, err error) {
var s string
s = r.formData.Encode()
if len(s) > 0 {
r.body = s
}
r.url.RawQuery = r.query.Encode()
r.uri = r.url.String()
return r.client.execute(r)
}
func (r *Request) Response(v any) (err error) {
var (
res *http.Response
buf []byte
contentType string
)
if res, err = r.Do(); err != nil {
return
}
defer func() {
_ = res.Body.Close()
}()
if res.StatusCode/100 != 2 {
if buf, err = io.ReadAll(res.Body); err == nil && len(buf) > 0 {
err = fmt.Errorf("http response %s(%d): %s", res.Status, res.StatusCode, string(buf))
} else {
err = fmt.Errorf("http response %d: %s", res.StatusCode, res.Status)
}
return
}
contentType = strings.ToLower(res.Header.Get("Content-Type"))
extName := path.Ext(r.rawRequest.URL.String())
if strings.Contains(contentType, JSON) || extName == ".json" {
err = json.NewDecoder(res.Body).Decode(v)
} else if strings.Contains(contentType, XML) || extName == ".xml" {
err = xml.NewDecoder(res.Body).Decode(v)
} else {
err = fmt.Errorf("unsupported content type: %s", contentType)
}
return
}
func (r *Request) Download(s string) (err error) {
var (
fp *os.File
res *http.Response
)
if res, err = r.Do(); err != nil {
return
}
defer func() {
_ = res.Body.Close()
}()
if fp, err = os.OpenFile(s, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
return
}
defer func() {
_ = fp.Close()
}()
_, err = io.Copy(fp, res.Body)
return
}
func newRequest(method string, uri string, client *Client) *Request {
var (
err error
)
r := &Request{
context: context.Background(),
method: method,
uri: uri,
header: make(http.Header),
formData: make(url.Values),
client: client,
}
if r.url, err = url.Parse(uri); err == nil {
r.query = r.url.Query()
} else {
r.query = make(url.Values)
}
return r
}

View File

@ -1,71 +0,0 @@
package httpclient
import (
"maps"
"net/http"
)
type (
options struct {
url string
method string
header map[string]string
params map[string]string
body any
human bool
client *http.Client
}
Option func(o *options)
)
func WithUrl(s string) Option {
return func(o *options) {
o.url = s
}
}
func WithMethod(s string) Option {
return func(o *options) {
o.method = s
}
}
func WithHuman() Option {
return func(o *options) {
o.human = true
}
}
func WithClient(c *http.Client) Option {
return func(o *options) {
o.client = c
}
}
func WithHeader(h map[string]string) Option {
return func(o *options) {
if o.header == nil {
o.header = make(map[string]string)
}
maps.Copy(o.header, h)
}
}
func WithParams(h map[string]string) Option {
return func(o *options) {
o.params = h
}
}
func WithBody(v any) Option {
return func(o *options) {
o.body = v
}
}
func newOptions() *options {
return &options{
client: DefaultClient,
method: http.MethodGet,
}
}

View File

@ -11,34 +11,18 @@ type logger struct {
} }
func (l *logger) Debug(ctx context.Context, msg string, args ...any) { func (l *logger) Debug(ctx context.Context, msg string, args ...any) {
l.log.DebugContext(ctx, msg, args...)
}
func (l *logger) Debugf(ctx context.Context, msg string, args ...any) {
l.log.DebugContext(ctx, fmt.Sprintf(msg, args...)) l.log.DebugContext(ctx, fmt.Sprintf(msg, args...))
} }
func (l *logger) Info(ctx context.Context, msg string, args ...any) { func (l *logger) Info(ctx context.Context, msg string, args ...any) {
l.log.InfoContext(ctx, msg, args...)
}
func (l *logger) Infof(ctx context.Context, msg string, args ...any) {
l.log.InfoContext(ctx, fmt.Sprintf(msg, args...)) l.log.InfoContext(ctx, fmt.Sprintf(msg, args...))
} }
func (l *logger) Warn(ctx context.Context, msg string, args ...any) { func (l *logger) Warn(ctx context.Context, msg string, args ...any) {
l.log.WarnContext(ctx, msg, args...)
}
func (l *logger) Warnf(ctx context.Context, msg string, args ...any) {
l.log.WarnContext(ctx, fmt.Sprintf(msg, args...)) l.log.WarnContext(ctx, fmt.Sprintf(msg, args...))
} }
func (l *logger) Error(ctx context.Context, msg string, args ...any) { func (l *logger) Error(ctx context.Context, msg string, args ...any) {
l.log.ErrorContext(ctx, msg, args...)
}
func (l *logger) Errorf(ctx context.Context, msg string, args ...any) {
l.log.ErrorContext(ctx, fmt.Sprintf(msg, args...)) l.log.ErrorContext(ctx, fmt.Sprintf(msg, args...))
} }

View File

@ -3,7 +3,6 @@ package logger
import ( import (
"context" "context"
"log/slog" "log/slog"
"os"
) )
var ( var (
@ -11,56 +10,32 @@ var (
) )
func init() { func init() {
log = NewLogger(slog.New( log = NewLogger(slog.Default())
slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}),
))
} }
type Logger interface { type Logger interface {
Debug(ctx context.Context, format string, args ...any) //Structured context as loosely typed key-value pairs. Debug(ctx context.Context, format string, args ...any)
Debugf(ctx context.Context, format string, args ...any)
Info(ctx context.Context, format string, args ...any) Info(ctx context.Context, format string, args ...any)
Infof(ctx context.Context, format string, args ...any)
Warn(ctx context.Context, format string, args ...any) Warn(ctx context.Context, format string, args ...any)
Warnf(ctx context.Context, format string, args ...any)
Error(ctx context.Context, format string, args ...any) Error(ctx context.Context, format string, args ...any)
Errorf(ctx context.Context, format string, args ...any)
} }
func Debug(ctx context.Context, format string, args ...any) { func Debug(ctx context.Context, format string, args ...any) {
log.Debug(ctx, format, args...) log.Debug(ctx, format, args...)
} }
func Debugf(ctx context.Context, format string, args ...any) {
log.Debugf(ctx, format, args...)
}
func Info(ctx context.Context, format string, args ...any) { func Info(ctx context.Context, format string, args ...any) {
log.Info(ctx, format, args...) log.Debug(ctx, format, args...)
}
func Infof(ctx context.Context, format string, args ...any) {
log.Infof(ctx, format, args...)
} }
func Warn(ctx context.Context, format string, args ...any) { func Warn(ctx context.Context, format string, args ...any) {
log.Warn(ctx, format, args...) log.Debug(ctx, format, args...)
}
func Warnf(ctx context.Context, format string, args ...any) {
log.Warnf(ctx, format, args...)
} }
func Error(ctx context.Context, format string, args ...any) { func Error(ctx context.Context, format string, args ...any) {
log.Debug(ctx, format, args...) log.Debug(ctx, format, args...)
} }
func Errorf(ctx context.Context, format string, args ...any) {
log.Errorf(ctx, format, args...)
}
func Default() Logger { func Default() Logger {
return log return log
} }

View File

@ -31,16 +31,6 @@ type RestFieldOptions struct {
Format string `protobuf:"bytes,5,opt,name=format,proto3" json:"format,omitempty"` Format string `protobuf:"bytes,5,opt,name=format,proto3" json:"format,omitempty"`
Props string `protobuf:"bytes,6,opt,name=props,proto3" json:"props,omitempty"` Props string `protobuf:"bytes,6,opt,name=props,proto3" json:"props,omitempty"`
Rule string `protobuf:"bytes,7,opt,name=rule,proto3" json:"rule,omitempty"` Rule string `protobuf:"bytes,7,opt,name=rule,proto3" json:"rule,omitempty"`
Live string `protobuf:"bytes,8,opt,name=live,proto3" json:"live,omitempty"`
Dropdown string `protobuf:"bytes,9,opt,name=dropdown,proto3" json:"dropdown,omitempty"`
Enum string `protobuf:"bytes,10,opt,name=enum,proto3" json:"enum,omitempty"`
Match string `protobuf:"bytes,11,opt,name=match,proto3" json:"match,omitempty"`
Invisible string `protobuf:"bytes,12,opt,name=invisible,proto3" json:"invisible,omitempty"`
Tooltip string `protobuf:"bytes,13,opt,name=tooltip,proto3" json:"tooltip,omitempty"`
Uploaduri string `protobuf:"bytes,14,opt,name=uploaduri,proto3" json:"uploaduri,omitempty"`
Description string `protobuf:"bytes,15,opt,name=description,proto3" json:"description,omitempty"`
Readonly string `protobuf:"bytes,16,opt,name=readonly,proto3" json:"readonly,omitempty"`
Endofnow string `protobuf:"bytes,17,opt,name=endofnow,proto3" json:"endofnow,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@ -124,76 +114,6 @@ func (x *RestFieldOptions) GetRule() string {
return "" return ""
} }
func (x *RestFieldOptions) GetLive() string {
if x != nil {
return x.Live
}
return ""
}
func (x *RestFieldOptions) GetDropdown() string {
if x != nil {
return x.Dropdown
}
return ""
}
func (x *RestFieldOptions) GetEnum() string {
if x != nil {
return x.Enum
}
return ""
}
func (x *RestFieldOptions) GetMatch() string {
if x != nil {
return x.Match
}
return ""
}
func (x *RestFieldOptions) GetInvisible() string {
if x != nil {
return x.Invisible
}
return ""
}
func (x *RestFieldOptions) GetTooltip() string {
if x != nil {
return x.Tooltip
}
return ""
}
func (x *RestFieldOptions) GetUploaduri() string {
if x != nil {
return x.Uploaduri
}
return ""
}
func (x *RestFieldOptions) GetDescription() string {
if x != nil {
return x.Description
}
return ""
}
func (x *RestFieldOptions) GetReadonly() string {
if x != nil {
return x.Readonly
}
return ""
}
func (x *RestFieldOptions) GetEndofnow() string {
if x != nil {
return x.Endofnow
}
return ""
}
type RestMessageOptions struct { type RestMessageOptions struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Table string `protobuf:"bytes,1,opt,name=table,proto3" json:"table,omitempty"` Table string `protobuf:"bytes,1,opt,name=table,proto3" json:"table,omitempty"`
@ -278,7 +198,7 @@ var File_rest_proto protoreflect.FileDescriptor
const file_rest_proto_rawDesc = "" + const file_rest_proto_rawDesc = "" +
"\n" + "\n" +
"\n" + "\n" +
"rest.proto\x12\x04aeus\x1a google/protobuf/descriptor.proto\"\xc6\x03\n" + "rest.proto\x12\x04aeus\x1a google/protobuf/descriptor.proto\"\xbc\x01\n" +
"\x10RestFieldOptions\x12\x12\n" + "\x10RestFieldOptions\x12\x12\n" +
"\x04gorm\x18\x01 \x01(\tR\x04gorm\x12\x18\n" + "\x04gorm\x18\x01 \x01(\tR\x04gorm\x12\x18\n" +
"\acomment\x18\x02 \x01(\tR\acomment\x12\x1c\n" + "\acomment\x18\x02 \x01(\tR\acomment\x12\x1c\n" +
@ -286,18 +206,7 @@ const file_rest_proto_rawDesc = "" +
"\bposition\x18\x04 \x01(\tR\bposition\x12\x16\n" + "\bposition\x18\x04 \x01(\tR\bposition\x12\x16\n" +
"\x06format\x18\x05 \x01(\tR\x06format\x12\x14\n" + "\x06format\x18\x05 \x01(\tR\x06format\x12\x14\n" +
"\x05props\x18\x06 \x01(\tR\x05props\x12\x12\n" + "\x05props\x18\x06 \x01(\tR\x05props\x12\x12\n" +
"\x04rule\x18\a \x01(\tR\x04rule\x12\x12\n" + "\x04rule\x18\a \x01(\tR\x04rule\"*\n" +
"\x04live\x18\b \x01(\tR\x04live\x12\x1a\n" +
"\bdropdown\x18\t \x01(\tR\bdropdown\x12\x12\n" +
"\x04enum\x18\n" +
" \x01(\tR\x04enum\x12\x14\n" +
"\x05match\x18\v \x01(\tR\x05match\x12\x1c\n" +
"\tinvisible\x18\f \x01(\tR\tinvisible\x12\x18\n" +
"\atooltip\x18\r \x01(\tR\atooltip\x12\x1c\n" +
"\tuploaduri\x18\x0e \x01(\tR\tuploaduri\x12 \n" +
"\vdescription\x18\x0f \x01(\tR\vdescription\x12\x1a\n" +
"\breadonly\x18\x10 \x01(\tR\breadonly\x12\x1a\n" +
"\bendofnow\x18\x11 \x01(\tR\bendofnow\"*\n" +
"\x12RestMessageOptions\x12\x14\n" + "\x12RestMessageOptions\x12\x14\n" +
"\x05table\x18\x01 \x01(\tR\x05table:M\n" + "\x05table\x18\x01 \x01(\tR\x05table:M\n" +
"\x05field\x12\x1d.google.protobuf.FieldOptions\x18\x96\x97\x03 \x01(\v2\x16.aeus.RestFieldOptionsR\x05field:O\n" + "\x05field\x12\x1d.google.protobuf.FieldOptions\x18\x96\x97\x03 \x01(\v2\x16.aeus.RestFieldOptionsR\x05field:O\n" +

View File

@ -21,16 +21,6 @@ message RestFieldOptions {
string format = 5; string format = 5;
string props = 6; string props = 6;
string rule= 7; string rule= 7;
string live = 8;
string dropdown = 9;
string enum = 10;
string match = 11;
string invisible = 12;
string tooltip = 13;
string uploaduri = 14;
string description = 15;
string readonly = 16;
string endofnow = 17;
} }
extend google.protobuf.MessageOptions { extend google.protobuf.MessageOptions {

View File

@ -1 +0,0 @@
package rest

View File

@ -1,350 +0,0 @@
package reflection
import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
)
var (
allowTags = []string{"json", "yaml", "xml", "name"}
)
var (
ErrValueAssociated = errors.New("value cannot be associated")
)
func findField(v reflect.Value, field string) reflect.Value {
var (
pos int
tagValue string
refType reflect.Type
fieldType reflect.StructField
)
refType = v.Type()
for i := range refType.NumField() {
fieldType = refType.Field(i)
for _, tagName := range allowTags {
tagValue = fieldType.Tag.Get(tagName)
if tagValue == "" {
continue
}
if pos = strings.IndexByte(tagValue, ','); pos != -1 {
tagValue = tagValue[:pos]
}
if tagValue == field {
return v.Field(i)
}
}
}
return v.FieldByName(field)
}
func safeAssignment(variable reflect.Value, value any) (err error) {
var (
n int64
un uint64
fn float64
kind reflect.Kind
)
rv := reflect.ValueOf(value)
kind = variable.Kind()
if kind != reflect.Slice && kind != reflect.Array && kind != reflect.Map && kind == rv.Kind() {
variable.Set(rv)
return
}
switch kind {
case reflect.Bool:
switch rv.Kind() {
case reflect.Bool:
variable.SetBool(rv.Bool())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if rv.Int() != 0 {
variable.SetBool(true)
} else {
variable.SetBool(false)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if rv.Uint() != 0 {
variable.SetBool(true)
} else {
variable.SetBool(false)
}
case reflect.Float32, reflect.Float64:
if rv.Float() != 0 {
variable.SetBool(true)
} else {
variable.SetBool(false)
}
case reflect.String:
var tv bool
tv, err = strconv.ParseBool(rv.String())
if err == nil {
variable.SetBool(tv)
}
default:
err = fmt.Errorf("boolean value can not assign %s", rv.Kind())
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch rv.Kind() {
case reflect.Bool:
if rv.Bool() {
variable.SetInt(1)
} else {
variable.SetInt(0)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
variable.SetInt(rv.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
variable.SetInt(int64(rv.Uint()))
case reflect.Float32, reflect.Float64:
variable.SetInt(int64(rv.Float()))
case reflect.String:
if n, err = strconv.ParseInt(rv.String(), 10, 64); err == nil {
variable.SetInt(n)
}
default:
err = fmt.Errorf("integer value can not assign %s", rv.Kind())
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
switch rv.Kind() {
case reflect.Bool:
if rv.Bool() {
variable.SetUint(1)
} else {
variable.SetUint(0)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
variable.SetUint(uint64(rv.Int()))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
variable.SetUint(rv.Uint())
case reflect.Float32, reflect.Float64:
variable.SetUint(uint64(rv.Float()))
case reflect.String:
if un, err = strconv.ParseUint(rv.String(), 10, 64); err == nil {
variable.SetUint(un)
}
default:
err = fmt.Errorf("unsigned integer value can not assign %s", rv.Kind())
}
case reflect.Float32, reflect.Float64:
switch rv.Kind() {
case reflect.Bool:
if rv.Bool() {
variable.SetFloat(1)
} else {
variable.SetFloat(0)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
variable.SetFloat(float64(rv.Int()))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
variable.SetFloat(float64(rv.Uint()))
case reflect.Float32, reflect.Float64:
variable.SetFloat(rv.Float())
case reflect.String:
if fn, err = strconv.ParseFloat(rv.String(), 64); err == nil {
variable.SetFloat(fn)
}
default:
err = fmt.Errorf("decimal value can not assign %s", rv.Kind())
}
case reflect.String:
switch rv.Kind() {
case reflect.Bool:
if rv.Bool() {
variable.SetString("true")
} else {
variable.SetString("false")
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
variable.SetString(strconv.FormatInt(rv.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
variable.SetString(strconv.FormatUint(rv.Uint(), 10))
case reflect.Float32, reflect.Float64:
variable.SetString(strconv.FormatFloat(rv.Float(), 'f', -1, 64))
case reflect.String:
variable.SetString(rv.String())
default:
variable.SetString(fmt.Sprint(value))
}
case reflect.Interface:
variable.Set(rv)
default:
err = fmt.Errorf("unsupported kind %s", kind)
}
return
}
func Set(hacky any, field string, value any) (err error) {
var (
n int
refField reflect.Value
)
refVal := reflect.ValueOf(hacky)
if refVal.Kind() == reflect.Ptr {
refVal = reflect.Indirect(refVal)
}
if refVal.Kind() != reflect.Struct {
return fmt.Errorf("%s kind is %v", refVal.Type().String(), refField.Kind())
}
refField = findField(refVal, field)
if !refField.IsValid() {
return fmt.Errorf("%s field `%s` not found", refVal.Type(), field)
}
rv := reflect.ValueOf(value)
fieldKind := refField.Kind()
if fieldKind != reflect.Slice && fieldKind != reflect.Array && fieldKind != reflect.Map && fieldKind == rv.Kind() {
refField.Set(rv)
return
}
switch fieldKind {
case reflect.Struct:
if rv.Kind() != reflect.Map {
return ErrValueAssociated
}
keys := rv.MapKeys()
subVal := reflect.New(refField.Type())
for _, key := range keys {
pv := rv.MapIndex(key)
if key.Kind() == reflect.String {
if err = Set(subVal.Interface(), key.String(), pv.Interface()); err != nil {
return err
}
}
}
refField.Set(subVal.Elem())
case reflect.Ptr:
elemType := refField.Type()
if elemType.Elem().Kind() != reflect.Struct {
return ErrValueAssociated
} else {
if rv.Kind() != reflect.Map {
return ErrValueAssociated
}
keys := rv.MapKeys()
subVal := reflect.New(elemType.Elem())
for _, key := range keys {
pv := rv.MapIndex(key)
if key.Kind() == reflect.String {
if err = Set(subVal.Interface(), key.String(), pv.Interface()); err != nil {
return err
}
}
}
refField.Set(subVal)
}
case reflect.Map:
if rv.Kind() != reflect.Map {
return ErrValueAssociated
}
targetValue := reflect.MakeMap(refField.Type())
keys := rv.MapKeys()
for _, key := range keys {
pv := rv.MapIndex(key)
kVal := reflect.New(refField.Type().Key())
eVal := reflect.New(refField.Type().Elem())
if err = safeAssignment(kVal.Elem(), key.Interface()); err != nil {
return ErrValueAssociated
}
if refField.Type().Elem().Kind() == reflect.Struct {
if pv.Elem().Kind() != reflect.Map {
return ErrValueAssociated
}
subKeys := pv.Elem().MapKeys()
for _, subKey := range subKeys {
subVal := pv.Elem().MapIndex(subKey)
if subKey.Kind() == reflect.String {
if err = Set(eVal.Interface(), subKey.String(), subVal.Interface()); err != nil {
return err
}
}
}
targetValue.SetMapIndex(kVal.Elem(), eVal.Elem())
} else {
if err = safeAssignment(eVal.Elem(), pv.Interface()); err != nil {
return ErrValueAssociated
}
targetValue.SetMapIndex(kVal.Elem(), eVal.Elem())
}
}
refField.Set(targetValue)
case reflect.Array, reflect.Slice:
n = 0
innerType := refField.Type().Elem()
if rv.Kind() == reflect.Array || rv.Kind() == reflect.Slice {
if innerType.Kind() == reflect.Struct {
sliceVar := reflect.MakeSlice(refField.Type(), rv.Len(), rv.Len())
for i := 0; i < rv.Len(); i++ {
srcVal := rv.Index(i)
if srcVal.Kind() != reflect.Map {
return ErrValueAssociated
}
dstVal := reflect.New(innerType)
keys := srcVal.MapKeys()
for _, key := range keys {
kv := srcVal.MapIndex(key)
if key.Kind() == reflect.String {
if err = Set(dstVal.Interface(), key.String(), kv.Interface()); err != nil {
return
}
}
}
sliceVar.Index(n).Set(dstVal.Elem())
n++
}
refField.Set(sliceVar.Slice(0, n))
} else if innerType.Kind() == reflect.Ptr {
sliceVar := reflect.MakeSlice(refField.Type(), rv.Len(), rv.Len())
for i := 0; i < rv.Len(); i++ {
srcVal := rv.Index(i)
if srcVal.Kind() != reflect.Map {
return ErrValueAssociated
}
dstVal := reflect.New(innerType.Elem())
keys := srcVal.MapKeys()
for _, key := range keys {
kv := srcVal.MapIndex(key)
if key.Kind() == reflect.String {
if err = Set(dstVal.Interface(), key.String(), kv.Interface()); err != nil {
return
}
}
}
sliceVar.Index(n).Set(dstVal)
n++
}
refField.Set(sliceVar.Slice(0, n))
} else {
sliceVar := reflect.MakeSlice(refField.Type(), rv.Len(), rv.Len())
for i := range rv.Len() {
srcVal := rv.Index(i)
dstVal := reflect.New(innerType).Elem()
if err = safeAssignment(dstVal, srcVal.Interface()); err != nil {
return
}
sliceVar.Index(n).Set(dstVal)
n++
}
refField.Set(sliceVar.Slice(0, n))
}
}
default:
err = safeAssignment(refField, value)
}
return
}
func Assign(variable reflect.Value, value any) (err error) {
return safeAssignment(variable, value)
}
func Setter[T string | int | int64 | float64 | any](hacky any, variables map[string]T) (err error) {
for k, v := range variables {
if err = Set(hacky, k, v); err != nil {
return err
}
}
return
}

View File

@ -6,16 +6,12 @@ import (
"math" "math"
"net" "net"
"net/url" "net/url"
"os"
"runtime" "runtime"
"strconv"
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"git.nobla.cn/golang/aeus/metadata"
"git.nobla.cn/golang/aeus/middleware"
"git.nobla.cn/golang/aeus/pkg/errors" "git.nobla.cn/golang/aeus/pkg/errors"
"git.nobla.cn/golang/aeus/pkg/logger" "git.nobla.cn/golang/aeus/pkg/logger"
netutil "git.nobla.cn/golang/aeus/pkg/net" netutil "git.nobla.cn/golang/aeus/pkg/net"
@ -31,12 +27,6 @@ type Server struct {
ctxMap sync.Map ctxMap sync.Map
uri *url.URL uri *url.URL
exitFlag int32 exitFlag int32
middleware []middleware.Middleware
Logger logger.Logger
}
func (svr *Server) Use(middlewares ...middleware.Middleware) {
svr.middleware = append(svr.middleware, middlewares...)
} }
func (svr *Server) Handle(pathname string, desc string, cb HandleFunc) { func (svr *Server) Handle(pathname string, desc string, cb HandleFunc) {
@ -100,13 +90,13 @@ func (s *Server) execute(ctx *Context, frame *Frame) (err error) {
} }
if r, args, err = s.router.Lookup(tokens); err != nil { if r, args, err = s.router.Lookup(tokens); err != nil {
if errors.Is(err, ErrNotFound) { if errors.Is(err, ErrNotFound) {
err = ctx.Error(errors.NotFound, fmt.Sprintf("Command %s not found", cmd)) err = ctx.Error(errNotFound, fmt.Sprintf("Command %s not found", cmd))
} else { } else {
err = ctx.Error(errors.Unavailable, err.Error()) err = ctx.Error(errExecuteFailed, err.Error())
} }
} else { } else {
if len(r.params) > len(args) { if len(r.params) > len(args) {
err = ctx.Error(errors.Unavailable, r.Usage()) err = ctx.Error(errExecuteFailed, r.Usage())
return return
} }
if len(r.params) > 0 { if len(r.params) > 0 {
@ -117,17 +107,7 @@ func (s *Server) execute(ctx *Context, frame *Frame) (err error) {
} }
ctx.setArgs(args) ctx.setArgs(args)
ctx.setParam(params) ctx.setParam(params)
h := func(c context.Context) error { err = r.command.Handle(ctx)
return r.command.Handle(ctx)
}
next := middleware.Chain(s.middleware...)(h)
md := metadata.FromContext(ctx.ctx)
md.Set(metadata.RequestPathKey, r.command.Path)
md.Set(metadata.RequestProtocolKey, Protocol)
md.TeeReader(&cliMetadataReader{ctx: ctx})
md.TeeWriter(&cliMetadataWriter{ctx: ctx})
ctx.ctx = metadata.NewContext(ctx.ctx, md)
err = next(ctx.ctx)
} }
return return
} }
@ -135,7 +115,7 @@ func (s *Server) execute(ctx *Context, frame *Frame) (err error) {
func (svr *Server) nextSequence() int64 { func (svr *Server) nextSequence() int64 {
svr.sequenceLocker.Lock() svr.sequenceLocker.Lock()
defer svr.sequenceLocker.Unlock() defer svr.sequenceLocker.Unlock()
if svr.sequence == math.MaxInt64 { if svr.sequence >= math.MaxInt64 {
svr.sequence = 1 svr.sequence = 1
} }
svr.sequence++ svr.sequence++
@ -209,16 +189,10 @@ func (s *Server) serve() (err error) {
func (s *Server) Start(ctx context.Context) (err error) { func (s *Server) Start(ctx context.Context) (err error) {
s.ctx = ctx s.ctx = ctx
if s.opts.logger != nil {
s.Logger = s.opts.logger
}
if s.Logger == nil {
s.Logger = logger.Default()
}
if err = s.createListener(); err != nil { if err = s.createListener(); err != nil {
return return
} }
s.Logger.Infof(ctx, "cli server listen on: %s", s.uri.Host) s.opts.logger.Info(ctx, "cli server listen on: %s", s.uri.Host)
s.Handle("/help", "Display help information", func(ctx *Context) (err error) { s.Handle("/help", "Display help information", func(ctx *Context) (err error) {
return ctx.Success(s.router.String()) return ctx.Success(s.router.String())
}) })
@ -231,9 +205,7 @@ func (s *Server) Stop(ctx context.Context) (err error) {
return return
} }
if s.listener != nil { if s.listener != nil {
if err = s.listener.Close(); err != nil { err = s.listener.Close()
s.Logger.Warnf(ctx, "cli listener close error: %v", err)
}
} }
s.ctxMap.Range(func(key, value any) bool { s.ctxMap.Range(func(key, value any) bool {
if ctx, ok := value.(*Context); ok { if ctx, ok := value.(*Context); ok {
@ -241,7 +213,6 @@ func (s *Server) Stop(ctx context.Context) (err error) {
} }
return true return true
}) })
s.Logger.Info(ctx, "cli server stopped")
return return
} }
@ -250,12 +221,11 @@ func New(cbs ...Option) *Server {
opts: &options{ opts: &options{
network: "tcp", network: "tcp",
address: ":0", address: ":0",
logger: logger.Default(),
}, },
uri: &url.URL{Scheme: "cli"}, uri: &url.URL{Scheme: "cli"},
router: newRouter(""), router: newRouter(""),
} }
port, _ := strconv.Atoi(os.Getenv("CLI_PORT"))
srv.opts.address = fmt.Sprintf(":%d", port)
for _, cb := range cbs { for _, cb := range cbs {
cb(srv.opts) cb(srv.opts)
} }

View File

@ -16,7 +16,8 @@ var (
) )
const ( const (
Protocol = "cli" errNotFound = 4004
errExecuteFailed = 4005
) )
var ( var (
@ -83,13 +84,6 @@ type (
ServerTime time.Time `json:"server_time"` ServerTime time.Time `json:"server_time"`
RemoteAddr string `json:"remote_addr"` RemoteAddr string `json:"remote_addr"`
} }
cliMetadataReader struct {
ctx *Context
}
cliMetadataWriter struct {
ctx *Context
}
) )
func WithAddress(addr string) Option { func WithAddress(addr string) Option {
@ -115,11 +109,3 @@ func WithContext(ctx context.Context) Option {
o.context = ctx o.context = ctx
} }
} }
func (r *cliMetadataReader) Get(key string) string {
return r.ctx.Param(key)
}
func (r *cliMetadataWriter) Set(key string, value string) {
r.ctx.SetValue(key, value)
}

View File

@ -2,11 +2,8 @@ package grpc
import ( import (
"context" "context"
"fmt"
"net" "net"
"net/url" "net/url"
"os"
"strconv"
"git.nobla.cn/golang/aeus/metadata" "git.nobla.cn/golang/aeus/metadata"
"git.nobla.cn/golang/aeus/middleware" "git.nobla.cn/golang/aeus/middleware"
@ -25,7 +22,6 @@ type Server struct {
serve *grpc.Server serve *grpc.Server
listener net.Listener listener net.Listener
middlewares []middleware.Middleware middlewares []middleware.Middleware
Logger logger.Logger
} }
func (s *Server) createListener() (err error) { func (s *Server) createListener() (err error) {
@ -45,59 +41,24 @@ func (s *Server) unaryServerInterceptor() grpc.UnaryServerInterceptor {
return return
} }
h := middleware.Chain(s.middlewares...)(next) h := middleware.Chain(s.middlewares...)(next)
md := metadata.FromContext(ctx) md := make(metadata.Metadata)
grpcIncommingMetadata, ok := grpcmd.FromIncomingContext(ctx)
if ok {
md.TeeReader(&grpcMetadataReader{grpcIncommingMetadata})
}
grpcOutgoingMetadata, ok := grpcmd.FromOutgoingContext(ctx)
if !ok {
grpcOutgoingMetadata = make(grpcmd.MD)
}
md.TeeWriter(&grpcMetadataWriter{grpcOutgoingMetadata})
if !md.Has(metadata.RequestIDKey) { if !md.Has(metadata.RequestIDKey) {
md.Set(metadata.RequestIDKey, uuid.New().String()) md.Set(metadata.RequestIDKey, uuid.New().String())
} }
md.Set(metadata.RequestPathKey, info.FullMethod) md.Set(metadata.RequestPathKey, info.FullMethod)
md.Set(metadata.RequestProtocolKey, Protocol) md.Set(metadata.RequestProtocolKey, Protocol)
ctx = metadata.NewContext(ctx, md) if gmd, ok := grpcmd.FromIncomingContext(ctx); ok {
for k, v := range gmd {
if len(v) > 0 {
md.Set(k, v[0])
}
}
}
ctx = metadata.MergeContext(ctx, md, true)
ctx = context.WithValue(ctx, requestValueContextKey{}, req) ctx = context.WithValue(ctx, requestValueContextKey{}, req)
err = h(ctx) err = h(ctx)
if grpcOutgoingMetadata.Len() > 0 { // grpcmd.AppendToOutgoingContext(ctx, grpcmd.New(metadata.FromContext(ctx)))
grpc.SetHeader(ctx, grpcOutgoingMetadata) // grpc.SetHeader()
}
return
}
}
func (s *Server) streamServerInterceptor() grpc.StreamServerInterceptor {
return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error) {
ctx := ss.Context()
next := func(ctx context.Context) (err error) {
err = handler(srv, ss)
return
}
h := middleware.Chain(s.middlewares...)(next)
md := metadata.FromContext(ctx)
grpcIncommingMetadata, ok := grpcmd.FromIncomingContext(ctx)
if ok {
md.TeeReader(&grpcMetadataReader{grpcIncommingMetadata})
}
grpcOutgoingMetadata, ok := grpcmd.FromOutgoingContext(ctx)
if !ok {
grpcOutgoingMetadata = make(grpcmd.MD)
}
md.TeeWriter(&grpcMetadataWriter{grpcOutgoingMetadata})
if !md.Has(metadata.RequestIDKey) {
md.Set(metadata.RequestIDKey, uuid.New().String())
}
md.Set(metadata.RequestPathKey, info.FullMethod)
md.Set(metadata.RequestProtocolKey, Protocol)
ctx = metadata.NewContext(ctx, md)
err = h(ctx)
if grpcOutgoingMetadata.Len() > 0 {
grpc.SetHeader(ctx, grpcOutgoingMetadata)
}
return return
} }
} }
@ -108,16 +69,11 @@ func (s *Server) Use(middlewares ...middleware.Middleware) {
func (s *Server) Start(ctx context.Context) (err error) { func (s *Server) Start(ctx context.Context) (err error) {
s.ctx = ctx s.ctx = ctx
if s.opts.logger != nil {
s.Logger = s.opts.logger
}
if s.Logger == nil {
s.Logger = logger.Default()
}
if err = s.createListener(); err != nil { if err = s.createListener(); err != nil {
return return
} }
s.Logger.Infof(ctx, "grpc server listen on: %s", s.uri.Host) s.opts.logger.Info(ctx, "grpc server listen on: %s", s.uri.Host)
reflection.Register(s.serve) reflection.Register(s.serve)
s.serve.Serve(s.listener) s.serve.Serve(s.listener)
return return
@ -136,7 +92,7 @@ func (s *Server) RegisterService(sd *grpc.ServiceDesc, ss any) {
func (s *Server) Stop(ctx context.Context) (err error) { func (s *Server) Stop(ctx context.Context) (err error) {
s.serve.GracefulStop() s.serve.GracefulStop()
s.Logger.Infof(s.ctx, "grpc server stopped") s.opts.logger.Info(s.ctx, "grpc server stopped")
return return
} }
@ -144,6 +100,8 @@ func New(cbs ...Option) *Server {
svr := &Server{ svr := &Server{
opts: &options{ opts: &options{
network: "tcp", network: "tcp",
logger: logger.Default(),
address: ":0",
grpcOpts: make([]grpc.ServerOption, 0, 10), grpcOpts: make([]grpc.ServerOption, 0, 10),
}, },
uri: &url.URL{ uri: &url.URL{
@ -151,13 +109,10 @@ func New(cbs ...Option) *Server {
}, },
middlewares: make([]middleware.Middleware, 0, 10), middlewares: make([]middleware.Middleware, 0, 10),
} }
port, _ := strconv.Atoi(os.Getenv("GRPC_PORT"))
svr.opts.address = fmt.Sprintf(":%d", port)
for _, cb := range cbs { for _, cb := range cbs {
cb(svr.opts) cb(svr.opts)
} }
svr.opts.grpcOpts = append(svr.opts.grpcOpts, grpc.ChainUnaryInterceptor(svr.unaryServerInterceptor())) svr.opts.grpcOpts = append(svr.opts.grpcOpts, grpc.ChainUnaryInterceptor(svr.unaryServerInterceptor()))
svr.opts.grpcOpts = append(svr.opts.grpcOpts, grpc.ChainStreamInterceptor(svr.streamServerInterceptor()))
svr.serve = grpc.NewServer(svr.opts.grpcOpts...) svr.serve = grpc.NewServer(svr.opts.grpcOpts...)
return svr return svr
} }

View File

@ -7,7 +7,6 @@ import (
"git.nobla.cn/golang/aeus/pkg/logger" "git.nobla.cn/golang/aeus/pkg/logger"
"git.nobla.cn/golang/aeus/registry" "git.nobla.cn/golang/aeus/registry"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/metadata"
) )
const ( const (
@ -33,14 +32,6 @@ type (
ClientOption func(*clientOptions) ClientOption func(*clientOptions)
grpcMetadataReader struct {
md metadata.MD
}
grpcMetadataWriter struct {
md metadata.MD
}
requestValueContextKey struct{} requestValueContextKey struct{}
) )
@ -92,21 +83,3 @@ func GetRequestValueFromContext(ctx context.Context) any {
} }
return ctx.Value(requestValueContextKey{}) return ctx.Value(requestValueContextKey{})
} }
func (m *grpcMetadataReader) Get(key string) string {
if m.md == nil {
return ""
}
vs := m.md.Get(key)
if len(vs) > 0 {
return vs[0]
}
return ""
}
func (m *grpcMetadataWriter) Set(key string, value string) {
if m.md == nil {
return
}
m.md.Set(key, value)
}

View File

@ -22,10 +22,6 @@ func (c *Context) Context() context.Context {
return c.ctx.Request.Context() return c.ctx.Request.Context()
} }
func (c *Context) Gin() *gin.Context {
return c.ctx
}
func (c *Context) Request() *http.Request { func (c *Context) Request() *http.Request {
return c.ctx.Request return c.ctx.Request
} }
@ -42,14 +38,6 @@ func (c *Context) Param(key string) string {
return c.ctx.Param(key) return c.ctx.Param(key)
} }
func (c *Context) Query(key string) string {
qs := c.ctx.Request.URL.Query()
if qs != nil {
return qs.Get(key)
}
return ""
}
func (c *Context) Bind(val any) (err error) { func (c *Context) Bind(val any) (err error) {
// if params exists, try bind params first // if params exists, try bind params first
if len(c.ctx.Params) > 0 { if len(c.ctx.Params) > 0 {

View File

@ -1,153 +0,0 @@
package http
import (
"io/fs"
"net/http"
"os"
"path"
"strings"
"time"
)
type (
filesystem struct {
fs http.FileSystem
modtime time.Time
prefix string
indexFile string
denyDirectory bool
}
httpFile struct {
fp http.File
modtime time.Time
}
httpFileInfo struct {
name string
size int64
mode fs.FileMode
isDir bool
modtime time.Time
}
)
func (fi *httpFileInfo) Name() string {
return fi.name
}
func (fi *httpFileInfo) Size() int64 {
return fi.size
}
func (fi *httpFileInfo) Mode() fs.FileMode {
return fi.mode
}
func (fi *httpFileInfo) ModTime() time.Time {
return fi.modtime
}
func (fi *httpFileInfo) IsDir() bool {
return fi.isDir
}
func (fi *httpFileInfo) Sys() any {
return nil
}
func (file *httpFile) Close() error {
return file.fp.Close()
}
func (file *httpFile) Read(p []byte) (n int, err error) {
return file.fp.Read(p)
}
func (file *httpFile) Seek(offset int64, whence int) (int64, error) {
return file.fp.Seek(offset, whence)
}
func (file *httpFile) Readdir(count int) ([]fs.FileInfo, error) {
return file.fp.Readdir(count)
}
func (file *httpFile) Stat() (fs.FileInfo, error) {
fi, err := file.fp.Stat()
if err != nil {
return nil, err
}
return newFileInfo(fi, file.modtime), nil
}
func (fs *filesystem) DenyAccessDirectory() {
fs.denyDirectory = true
}
func (fs *filesystem) SetPrefix(prefix string) {
if prefix != "" {
if prefix[0] != '/' {
prefix = "/" + prefix
}
prefix = strings.TrimRight(prefix, "/")
fs.prefix = prefix
}
}
func (fs *filesystem) SetIndexFile(indexFile string) {
fs.indexFile = indexFile
}
func (fs *filesystem) Open(name string) (http.File, error) {
var (
needRetry bool
)
if name == "" || name == "/" {
needRetry = true
}
if fs.prefix != "" {
if !strings.HasPrefix(name, fs.prefix) {
name = path.Join(fs.prefix, name)
}
}
fp, err := fs.fs.Open(name)
if err != nil {
return nil, err
}
if fs.denyDirectory {
state, err := fp.Stat()
if err != nil {
return nil, err
}
if state.IsDir() {
if needRetry {
if fs.indexFile != "" {
return fs.Open(path.Join(name, fs.indexFile))
}
}
return nil, os.ErrPermission
}
}
return &httpFile{fp: fp, modtime: fs.modtime}, nil
}
func newFS(modtime time.Time, fs http.FileSystem) *filesystem {
return &filesystem{
fs: fs,
modtime: modtime,
}
}
func newFileInfo(fi fs.FileInfo, modtime time.Time) *httpFileInfo {
return &httpFileInfo{
name: fi.Name(),
size: fi.Size(),
mode: fi.Mode(),
isDir: fi.IsDir(),
modtime: modtime,
}
}
func FileSystem(s fs.FS) http.FileSystem {
return http.FS(s)
}

View File

@ -2,20 +2,11 @@ package http
import ( import (
"context" "context"
"fmt"
"io"
"net" "net"
"net/http" "net/http"
"net/http/pprof" "net/http/pprof"
"net/url" "net/url"
"os"
"path"
"path/filepath"
"slices"
"strconv"
"strings"
"sync" "sync"
"time"
"git.nobla.cn/golang/aeus/metadata" "git.nobla.cn/golang/aeus/metadata"
"git.nobla.cn/golang/aeus/middleware" "git.nobla.cn/golang/aeus/middleware"
@ -34,9 +25,7 @@ type Server struct {
engine *gin.Engine engine *gin.Engine
once sync.Once once sync.Once
listener net.Listener listener net.Listener
fs *filesystem
middlewares []middleware.Middleware middlewares []middleware.Middleware
Logger logger.Logger
} }
func (s *Server) Endpoint(ctx context.Context) (string, error) { func (s *Server) Endpoint(ctx context.Context) (string, error) {
@ -98,111 +87,10 @@ func (s *Server) Use(middlewares ...middleware.Middleware) {
s.middlewares = append(s.middlewares, middlewares...) s.middlewares = append(s.middlewares, middlewares...)
} }
func (s *Server) Handle(method string, uri string, handler http.HandlerFunc) {
s.engine.Handle(method, uri, func(ctx *gin.Context) {
handler(ctx.Writer, ctx.Request)
})
}
func (s *Server) Webroot(prefix string, fs http.FileSystem) {
s.fs = newFS(time.Now(), fs)
s.fs.SetPrefix(prefix)
s.fs.DenyAccessDirectory()
s.fs.SetIndexFile("/index.html")
}
func (s *Server) shouldCompress(req *http.Request) bool {
if !strings.Contains(req.Header.Get(headerAcceptEncoding), "gzip") ||
strings.Contains(req.Header.Get("Connection"), "Upgrade") {
return false
}
// Check if the request path is excluded from compression
extension := filepath.Ext(req.URL.Path)
if slices.Contains(assetsExtensions, extension) {
return true
}
return false
}
func (s *Server) staticHandle(ctx *gin.Context, fp http.File) {
uri := path.Clean(ctx.Request.URL.Path)
fi, err := fp.Stat()
if err != nil {
return
}
if !fi.IsDir() {
//https://github.com/gin-contrib/gzip
if s.shouldCompress(ctx.Request) && fi.Size() > 8192 {
gzWriter := newGzipWriter()
gzWriter.Reset(ctx.Writer)
ctx.Header(headerContentEncoding, "gzip")
ctx.Writer.Header().Add(headerVary, headerAcceptEncoding)
originalEtag := ctx.GetHeader("ETag")
if originalEtag != "" && !strings.HasPrefix(originalEtag, "W/") {
ctx.Header("ETag", "W/"+originalEtag)
}
ctx.Writer = &gzipWriter{ctx.Writer, gzWriter}
defer func() {
if ctx.Writer.Size() < 0 {
gzWriter.Reset(io.Discard)
}
gzWriter.Close()
if ctx.Writer.Size() > -1 {
ctx.Header("Content-Length", strconv.Itoa(ctx.Writer.Size()))
}
putGzipWriter(gzWriter)
}()
}
}
http.ServeContent(ctx.Writer, ctx.Request, path.Base(uri), s.fs.modtime, fp)
ctx.Abort()
}
func (s *Server) notFoundHandle(ctx *gin.Context) {
if s.fs != nil && ctx.Request.Method == http.MethodGet {
uri := path.Clean(ctx.Request.URL.Path)
if fp, err := s.fs.Open(uri); err == nil {
s.staticHandle(ctx, fp)
fp.Close()
}
}
ctx.JSON(http.StatusNotFound, newResponse(errors.NotFound, "Not Found", nil))
}
func (s *Server) CORSInterceptor() gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.Method == "OPTIONS" {
c.Writer.Header().Add("Vary", "Origin")
c.Writer.Header().Add("Vary", "Access-Control-Request-Method")
c.Writer.Header().Add("Vary", "Access-Control-Request-Headers")
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
h := c.Request.Header.Get("Access-Control-Request-Headers")
if h != "" {
c.Writer.Header().Set("Access-Control-Allow-Headers", h)
}
c.AbortWithStatus(204)
return
} else {
c.Writer.Header().Add("Vary", "Origin")
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
h := c.Request.Header.Get("Access-Control-Request-Headers")
if h != "" {
c.Writer.Header().Set("Access-Control-Allow-Headers", h)
}
}
c.Next()
}
}
func (s *Server) requestInterceptor() gin.HandlerFunc { func (s *Server) requestInterceptor() gin.HandlerFunc {
return func(ginCtx *gin.Context) { return func(ginCtx *gin.Context) {
ctx := ginCtx.Request.Context() ctx := ginCtx.Request.Context()
next := func(ctx context.Context) error { next := func(ctx context.Context) error {
ginCtx.Request = ginCtx.Request.WithContext(ctx)
ginCtx.Next() ginCtx.Next()
if err := ginCtx.Errors.Last(); err != nil { if err := ginCtx.Errors.Last(); err != nil {
return err.Err return err.Err
@ -210,20 +98,18 @@ func (s *Server) requestInterceptor() gin.HandlerFunc {
return nil return nil
} }
handler := middleware.Chain(s.middlewares...)(next) handler := middleware.Chain(s.middlewares...)(next)
md := metadata.FromContext(ctx) md := make(metadata.Metadata)
md.TeeReader(&httpMetadataReader{ for k, v := range ginCtx.Request.Header {
hd: ginCtx.Request.Header, if len(v) > 0 {
}) md.Set(k, v[0])
md.TeeWriter(&httpMetadataWriter{ }
w: ginCtx.Writer, }
})
if !md.Has(metadata.RequestIDKey) { if !md.Has(metadata.RequestIDKey) {
md.Set(metadata.RequestIDKey, uuid.New().String()) md.Set(metadata.RequestIDKey, uuid.New().String())
} }
md.Set(metadata.RequestProtocolKey, Protocol) md.Set(metadata.RequestProtocolKey, Protocol)
md.Set(metadata.RequestPathKey, ginCtx.Request.URL.Path) md.Set(metadata.RequestPathKey, ginCtx.Request.URL.Path)
md.Set("method", ginCtx.Request.Method) ctx = metadata.MergeContext(ctx, md, true)
ctx = metadata.NewContext(ctx, md)
if err := handler(ctx); err != nil { if err := handler(ctx); err != nil {
if se, ok := err.(*errors.Error); ok { if se, ok := err.(*errors.Error); ok {
ginCtx.AbortWithStatusJSON(http.StatusInternalServerError, newResponse(se.Code, se.Message, nil)) ginCtx.AbortWithStatusJSON(http.StatusInternalServerError, newResponse(se.Code, se.Message, nil))
@ -255,12 +141,6 @@ func (s *Server) Start(ctx context.Context) (err error) {
Addr: s.opts.address, Addr: s.opts.address,
Handler: s.engine, Handler: s.engine,
} }
if s.opts.logger != nil {
s.Logger = s.opts.logger
}
if s.Logger == nil {
s.Logger = logger.Default()
}
s.ctx = ctx s.ctx = ctx
if s.opts.debug { if s.opts.debug {
s.engine.Handle(http.MethodGet, "/debug/pprof/", s.wrapHandle(pprof.Index)) s.engine.Handle(http.MethodGet, "/debug/pprof/", s.wrapHandle(pprof.Index))
@ -276,8 +156,7 @@ func (s *Server) Start(ctx context.Context) (err error) {
if err = s.createListener(); err != nil { if err = s.createListener(); err != nil {
return return
} }
s.engine.NoRoute(s.notFoundHandle) s.opts.logger.Info(ctx, "http server listen on: %s", s.uri.Host)
s.Logger.Infof(ctx, "http server listen on: %s", s.uri.Host)
if s.opts.certFile != "" && s.opts.keyFile != "" { if s.opts.certFile != "" && s.opts.keyFile != "" {
s.uri.Scheme = "https" s.uri.Scheme = "https"
err = s.serve.ServeTLS(s.listener, s.opts.certFile, s.opts.keyFile) err = s.serve.ServeTLS(s.listener, s.opts.certFile, s.opts.keyFile)
@ -292,7 +171,7 @@ func (s *Server) Start(ctx context.Context) (err error) {
func (s *Server) Stop(ctx context.Context) (err error) { func (s *Server) Stop(ctx context.Context) (err error) {
err = s.serve.Shutdown(ctx) err = s.serve.Shutdown(ctx)
s.Logger.Infof(ctx, "http server stopped") s.opts.logger.Info(ctx, "http server stopped")
return return
} }
@ -301,10 +180,10 @@ func New(cbs ...Option) *Server {
uri: &url.URL{Scheme: "http"}, uri: &url.URL{Scheme: "http"},
opts: &options{ opts: &options{
network: "tcp", network: "tcp",
logger: logger.Default(),
address: ":0",
}, },
} }
port, _ := strconv.Atoi(os.Getenv("HTTP_PORT"))
svr.opts.address = fmt.Sprintf(":%d", port)
for _, cb := range cbs { for _, cb := range cbs {
cb(svr.opts) cb(svr.opts)
} }
@ -312,9 +191,6 @@ func New(cbs ...Option) *Server {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
} }
svr.engine = gin.New(svr.opts.ginOptions...) svr.engine = gin.New(svr.opts.ginOptions...)
if svr.opts.enableCORS {
svr.engine.Use(svr.CORSInterceptor())
}
svr.engine.Use(svr.requestInterceptor()) svr.engine.Use(svr.requestInterceptor())
return svr return svr
} }

View File

@ -1,14 +1,8 @@
package http package http
import ( import (
"bufio"
"compress/gzip"
"context" "context"
"errors"
"io"
"net"
"net/http" "net/http"
"sync"
"git.nobla.cn/golang/aeus/pkg/logger" "git.nobla.cn/golang/aeus/pkg/logger"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -31,105 +25,19 @@ type (
logger logger.Logger logger logger.Logger
context context.Context context context.Context
ginOptions []gin.OptionFunc ginOptions []gin.OptionFunc
enableCORS bool
} }
HandleFunc func(ctx *Context) (err error) HandleFunc func(ctx *Context) (err error)
Middleware func(http.Handler) http.Handler Middleware func(http.Handler) http.Handler
httpMetadataReader struct {
hd http.Header
}
httpMetadataWriter struct {
w http.ResponseWriter
}
) )
const (
headerAcceptEncoding = "Accept-Encoding"
headerContentEncoding = "Content-Encoding"
headerVary = "Vary"
)
var (
gzPool sync.Pool
assetsExtensions = []string{".css", ".js", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".woff", ".woff2", ".ttf", ".eot", ".otf"}
)
type gzipWriter struct {
gin.ResponseWriter
writer *gzip.Writer
}
func (g *gzipWriter) WriteString(s string) (int, error) {
g.Header().Del("Content-Length")
return g.writer.Write([]byte(s))
}
func (g *gzipWriter) Write(data []byte) (int, error) {
g.Header().Del("Content-Length")
return g.writer.Write(data)
}
func (g *gzipWriter) Flush() {
_ = g.writer.Flush()
g.ResponseWriter.Flush()
}
// Fix: https://github.com/mholt/caddy/issues/38
func (g *gzipWriter) WriteHeader(code int) {
g.Header().Del("Content-Length")
g.ResponseWriter.WriteHeader(code)
}
var _ http.Hijacker = (*gzipWriter)(nil)
// Hijack allows the caller to take over the connection from the HTTP server.
// After a call to Hijack, the HTTP server library will not do anything else with the connection.
// It becomes the caller's responsibility to manage and close the connection.
//
// It returns the underlying net.Conn, a buffered reader/writer for the connection, and an error
// if the ResponseWriter does not support the Hijacker interface.
func (g *gzipWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hijacker, ok := g.ResponseWriter.(http.Hijacker)
if !ok {
return nil, nil, errors.New("the ResponseWriter doesn't support the Hijacker interface")
}
return hijacker.Hijack()
}
func newGzipWriter() (writer *gzip.Writer) {
v := gzPool.Get()
if v == nil {
writer, _ = gzip.NewWriterLevel(io.Discard, gzip.DefaultCompression)
} else {
if w, ok := v.(*gzip.Writer); ok {
return w
} else {
writer, _ = gzip.NewWriterLevel(io.Discard, gzip.DefaultCompression)
}
}
return
}
func putGzipWriter(writer *gzip.Writer) {
gzPool.Put(writer)
}
func WithNetwork(network string) Option { func WithNetwork(network string) Option {
return func(o *options) { return func(o *options) {
o.network = network o.network = network
} }
} }
func WithCORS() Option {
return func(o *options) {
o.enableCORS = true
}
}
func WithAddress(address string) Option { func WithAddress(address string) Option {
return func(o *options) { return func(o *options) {
o.address = address o.address = address
@ -177,17 +85,3 @@ func WithGinOptions(opts ...gin.OptionFunc) Option {
o.ginOptions = opts o.ginOptions = opts
} }
} }
func (m *httpMetadataReader) Get(key string) string {
if m.hd == nil {
return ""
}
return m.hd.Get(key)
}
func (m *httpMetadataWriter) Set(key string, value string) {
if m.w == nil {
return
}
m.w.Header().Set(key, value)
}