Compare commits
No commits in common. "main" and "v0.0.2" have entirely different histories.
|
@ -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
|
||||||
|
|
96
README.md
96
README.md
|
@ -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 | 前端资源目录 |
|
|
28
app.go
28
app.go
|
@ -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
11
go.mod
|
@ -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
28
go.sum
|
@ -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=
|
||||||
|
|
|
@ -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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
22
options.go
22
options.go
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)},
|
|
||||||
))
|
|
||||||
}
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 item.Value, time.Unix(0, item.Expiration), nil
|
||||||
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) {
|
func (c *memCache) Put(ctx context.Context, key string, val interface{}, d time.Duration) error {
|
||||||
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 {
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,34 +1,24 @@
|
||||||
package errors
|
package errors
|
||||||
|
|
||||||
const (
|
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
|
AccessDenied = 4005 //access denied
|
||||||
Timeout = 2001 //timeout
|
PermissionDenied = 4003 //permission denied
|
||||||
Expired = 2002 //expired
|
NotFound = 4004 //not found
|
||||||
TokenExpired = 4002 //token expired
|
Unavailable = 5000 //service unavailable
|
||||||
NotFound = 4004 //not found
|
|
||||||
PermissionDenied = 4003 //permission denied
|
|
||||||
AccessDenied = 4005 //access denied
|
|
||||||
NetworkUnreachable = 5001 //network unreachable
|
|
||||||
ConnectionRefused = 5002 //connection refused
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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")
|
ErrValidate = New(Invalid, "invalid payload")
|
||||||
ErrInvalid = 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")
|
ErrUnavailable = New(Unavailable, "service unavailable")
|
||||||
ErrTokenExpired = New(TokenExpired, "token expired")
|
|
||||||
ErrUnavailable = New(Unavailable, "service unavailable")
|
|
||||||
ErrNetworkUnreachable = New(NetworkUnreachable, "network unreachable")
|
|
||||||
ErrConnectionRefused = New(ConnectionRefused, "connection refused")
|
|
||||||
ErrIncompatible = New(Incompatible, "incompatible")
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -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...),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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...))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"`
|
||||||
|
@ -251,8 +171,8 @@ var file_rest_proto_extTypes = []protoimpl.ExtensionInfo{
|
||||||
ExtendedType: (*descriptorpb.MessageOptions)(nil),
|
ExtendedType: (*descriptorpb.MessageOptions)(nil),
|
||||||
ExtensionType: (*RestMessageOptions)(nil),
|
ExtensionType: (*RestMessageOptions)(nil),
|
||||||
Field: 52119,
|
Field: 52119,
|
||||||
Name: "aeus.rest",
|
Name: "aeus.opts",
|
||||||
Tag: "bytes,52119,opt,name=rest",
|
Tag: "bytes,52119,opt,name=opts",
|
||||||
Filename: "rest.proto",
|
Filename: "rest.proto",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -269,8 +189,8 @@ var (
|
||||||
var (
|
var (
|
||||||
// ormable will cause orm code to be generated for this message/object
|
// ormable will cause orm code to be generated for this message/object
|
||||||
//
|
//
|
||||||
// optional aeus.RestMessageOptions rest = 52119;
|
// optional aeus.RestMessageOptions opts = 52119;
|
||||||
E_Rest = &file_rest_proto_extTypes[1]
|
E_Opts = &file_rest_proto_extTypes[1]
|
||||||
)
|
)
|
||||||
|
|
||||||
var File_rest_proto protoreflect.FileDescriptor
|
var File_rest_proto protoreflect.FileDescriptor
|
||||||
|
@ -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,22 +206,11 @@ 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" +
|
||||||
"\x04rest\x12\x1f.google.protobuf.MessageOptions\x18\x97\x97\x03 \x01(\v2\x18.aeus.RestMessageOptionsR\x04restB)Z'git.nobla.cn/golang/aeus/pkg/proto/restb\x06proto3"
|
"\x04opts\x12\x1f.google.protobuf.MessageOptions\x18\x97\x97\x03 \x01(\v2\x18.aeus.RestMessageOptionsR\x04optsB)Z'git.nobla.cn/golang/aeus/pkg/proto/restb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_rest_proto_rawDescOnce sync.Once
|
file_rest_proto_rawDescOnce sync.Once
|
||||||
|
@ -324,9 +233,9 @@ var file_rest_proto_goTypes = []any{
|
||||||
}
|
}
|
||||||
var file_rest_proto_depIdxs = []int32{
|
var file_rest_proto_depIdxs = []int32{
|
||||||
2, // 0: aeus.field:extendee -> google.protobuf.FieldOptions
|
2, // 0: aeus.field:extendee -> google.protobuf.FieldOptions
|
||||||
3, // 1: aeus.rest:extendee -> google.protobuf.MessageOptions
|
3, // 1: aeus.opts:extendee -> google.protobuf.MessageOptions
|
||||||
0, // 2: aeus.field:type_name -> aeus.RestFieldOptions
|
0, // 2: aeus.field:type_name -> aeus.RestFieldOptions
|
||||||
1, // 3: aeus.rest:type_name -> aeus.RestMessageOptions
|
1, // 3: aeus.opts:type_name -> aeus.RestMessageOptions
|
||||||
4, // [4:4] is the sub-list for method output_type
|
4, // [4:4] is the sub-list for method output_type
|
||||||
4, // [4:4] is the sub-list for method input_type
|
4, // [4:4] is the sub-list for method input_type
|
||||||
2, // [2:4] is the sub-list for extension type_name
|
2, // [2:4] is the sub-list for extension type_name
|
||||||
|
|
|
@ -21,21 +21,11 @@ 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 {
|
||||||
// ormable will cause orm code to be generated for this message/object
|
// ormable will cause orm code to be generated for this message/object
|
||||||
RestMessageOptions rest = 52119;
|
RestMessageOptions opts = 52119;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RestMessageOptions {
|
message RestMessageOptions {
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
package rest
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue