Compare commits
No commits in common. "main" and "v0.0.8" have entirely different histories.
|
@ -11,7 +11,6 @@ _posts
|
|||
.vscode
|
||||
vendor
|
||||
cmd/
|
||||
tools/
|
||||
third_party/
|
||||
|
||||
# Go.gitignore
|
||||
|
|
94
README.md
94
README.md
|
@ -4,99 +4,5 @@
|
|||
|
||||
|
||||
|
||||
# 环境变量
|
||||
|
||||
| 环境变量 | 描述 |
|
||||
| --- | --- |
|
||||
| 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 | 前端资源目录 |
|
13
app.go
13
app.go
|
@ -6,7 +6,6 @@ import (
|
|||
"os/signal"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
@ -36,19 +35,12 @@ func (s *Service) Name() string {
|
|||
}
|
||||
|
||||
func (s *Service) Debug() bool {
|
||||
if s.opts != nil {
|
||||
return s.opts.debug
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Service) Version() string {
|
||||
if s.opts != nil {
|
||||
return s.opts.version
|
||||
}
|
||||
return ""
|
||||
return s.opts.version
|
||||
}
|
||||
|
||||
func (s *Service) Metadata() map[string]string {
|
||||
if s.service == nil {
|
||||
return nil
|
||||
|
@ -122,7 +114,7 @@ func (s *Service) injectVars(v any) {
|
|||
}
|
||||
|
||||
func (s *Service) preStart(ctx context.Context) (err error) {
|
||||
s.Logger().Info(ctx, "starting")
|
||||
s.Logger().Info(s.ctx, "starting")
|
||||
for _, ptr := range s.opts.servers {
|
||||
s.refValues = append(s.refValues, reflect.ValueOf(ptr))
|
||||
}
|
||||
|
@ -263,7 +255,6 @@ func New(cbs ...Option) *Service {
|
|||
registrarTimeout: time.Second * 30,
|
||||
},
|
||||
}
|
||||
s.opts.debug, _ = strconv.ParseBool("AEUS_DEBUG")
|
||||
s.opts.metadata = make(map[string]string)
|
||||
for _, cb := range cbs {
|
||||
cb(s.opts)
|
||||
|
|
|
@ -47,26 +47,13 @@ func WithAllow(paths ...string) Option {
|
|||
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)
|
||||
}
|
||||
o.allows = append(o.allows, paths...)
|
||||
}
|
||||
}
|
||||
|
||||
func WithClaims(claims any) Option {
|
||||
func WithClaims(claims reflect.Type) 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()
|
||||
}
|
||||
}
|
||||
o.claims = claims
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,19 +65,19 @@ func WithValidate(fn Validate) Option {
|
|||
|
||||
// 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
|
||||
for _, str := range allows {
|
||||
n := len(str)
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
if pattern == "*" {
|
||||
return true
|
||||
}
|
||||
if n > 1 && pattern[n-1] == '*' {
|
||||
if strings.HasPrefix(uripath, pattern[:n-1]) {
|
||||
if n > 1 && str[n-1] == '*' {
|
||||
if strings.HasPrefix(uripath, str[:n-1]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if str == uripath {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -121,7 +108,9 @@ func JWT(keyFunc jwt.Keyfunc, cbs ...Option) middleware.Middleware {
|
|||
return err
|
||||
}
|
||||
}
|
||||
token, _ = strings.CutPrefix(token, bearerWord)
|
||||
if strings.HasPrefix(token, bearerWord) {
|
||||
token = strings.TrimPrefix(token, bearerWord)
|
||||
}
|
||||
var (
|
||||
ti *jwt.Token
|
||||
)
|
||||
|
|
12
options.go
12
options.go
|
@ -2,7 +2,6 @@ package aeus
|
|||
|
||||
import (
|
||||
"context"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"git.nobla.cn/golang/aeus/pkg/logger"
|
||||
|
@ -19,7 +18,6 @@ type options struct {
|
|||
servers []Server
|
||||
endpoints []string
|
||||
scope Scope
|
||||
debug bool
|
||||
registrarTimeout time.Duration
|
||||
registry registry.Registry
|
||||
serviceLoader ServiceLoader
|
||||
|
@ -43,7 +41,9 @@ func WithMetadata(metadata map[string]string) Option {
|
|||
if o.metadata == nil {
|
||||
o.metadata = make(map[string]string)
|
||||
}
|
||||
maps.Copy(o.metadata, metadata)
|
||||
for k, v := range metadata {
|
||||
o.metadata[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,12 +71,6 @@ func WithScope(scope Scope) Option {
|
|||
}
|
||||
}
|
||||
|
||||
func WithDebug(debug bool) Option {
|
||||
return func(o *options) {
|
||||
o.debug = debug
|
||||
}
|
||||
}
|
||||
|
||||
func WithServiceLoader(loader ServiceLoader) Option {
|
||||
return func(o *options) {
|
||||
o.serviceLoader = loader
|
||||
|
|
|
@ -4,8 +4,6 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"git.nobla.cn/golang/aeus"
|
||||
)
|
||||
|
||||
type redisCache struct {
|
||||
|
@ -59,12 +57,7 @@ func (c *redisCache) String() string {
|
|||
}
|
||||
|
||||
func NewCache(opts ...Option) *redisCache {
|
||||
cache := &redisCache{
|
||||
return &redisCache{
|
||||
opts: newOptions(opts...),
|
||||
}
|
||||
app := aeus.FromContext(cache.opts.context)
|
||||
if app != nil {
|
||||
cache.opts.prefix = app.Name() + ":" + cache.opts.prefix
|
||||
}
|
||||
return cache
|
||||
}
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type (
|
||||
options struct {
|
||||
context context.Context
|
||||
client *redis.Client
|
||||
prefix string
|
||||
client *redis.Client
|
||||
prefix string
|
||||
}
|
||||
|
||||
Option func(*options)
|
||||
|
@ -22,12 +19,6 @@ func WithClient(client *redis.Client) Option {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package generator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"git.nobla.cn/golang/aeus/tools/gen/internal/types"
|
||||
)
|
||||
|
||||
var (
|
||||
fileMap = map[string]string{
|
||||
"cmd/main.go": MainTemp,
|
||||
"internal/scope/scope.go": ScopeTemp,
|
||||
"internal/service/service.go": ServiceLoaderTemp,
|
||||
"api/v1/pb/greeter.proto": GreeterTemp,
|
||||
"version/version.go": VersionTemp,
|
||||
"Makefile": MakefileTemp,
|
||||
".gitignore": GitIgnoreTemp,
|
||||
"README.md": ReadmeTemp,
|
||||
"go.mod": GoModTemp,
|
||||
"webhook.yaml": WebhookTemp,
|
||||
"deploy/docker/deployment.yaml": DeploymentTemp,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed third_party
|
||||
protoDir embed.FS
|
||||
)
|
||||
|
||||
type (
|
||||
TemplateData struct {
|
||||
ShortName string
|
||||
PackageName string
|
||||
Datetime string
|
||||
Version string
|
||||
ImageRegistry string
|
||||
}
|
||||
)
|
||||
|
||||
func writeFile(file string, buf []byte) (err error) {
|
||||
dirname := path.Dir(file)
|
||||
if _, err = os.Stat(dirname); err != nil {
|
||||
if err = os.MkdirAll(dirname, 0755); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = os.WriteFile(file, buf, 0644)
|
||||
return
|
||||
}
|
||||
|
||||
func scanDir(s embed.FS, dirname string, callback func(file string) error) (err error) {
|
||||
var (
|
||||
entities []fs.DirEntry
|
||||
)
|
||||
if entities, err = s.ReadDir(dirname); err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, entity := range entities {
|
||||
if entity.Name() == "." || entity.Name() == ".." {
|
||||
continue
|
||||
}
|
||||
name := path.Join(dirname, entity.Name())
|
||||
if entity.IsDir() {
|
||||
scanDir(s, name, callback)
|
||||
} else {
|
||||
if err = callback(name); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Geerate(app *types.Applicetion) (err error) {
|
||||
shortName := app.ShortName()
|
||||
data := TemplateData{
|
||||
ShortName: shortName,
|
||||
PackageName: app.Package,
|
||||
Version: app.Version,
|
||||
ImageRegistry: "{{IMAGE_REGISTRY_URL}}",
|
||||
Datetime: time.Now().Format(time.DateTime),
|
||||
}
|
||||
if data.Version == "" {
|
||||
data.Version = "v0.0.1"
|
||||
}
|
||||
var t *template.Template
|
||||
writer := bytes.NewBuffer(nil)
|
||||
for name, tmpl := range fileMap {
|
||||
if t, err = template.New(name).Parse(tmpl); err != nil {
|
||||
return
|
||||
}
|
||||
if err = t.Execute(writer, data); err != nil {
|
||||
return
|
||||
}
|
||||
if err = writeFile(path.Join(shortName, name), writer.Bytes()); err != nil {
|
||||
return
|
||||
}
|
||||
writer.Reset()
|
||||
}
|
||||
if err = writeFile(path.Join(shortName, shortName+".go"), []byte("package "+shortName)); err != nil {
|
||||
return
|
||||
}
|
||||
err = scanDir(protoDir, "third_party", func(filename string) error {
|
||||
if fp, openerr := protoDir.Open(filename); openerr != nil {
|
||||
return openerr
|
||||
} else {
|
||||
if buf, readerr := io.ReadAll(fp); readerr == nil {
|
||||
writeFile(path.Join(shortName, filename), buf)
|
||||
}
|
||||
fp.Close()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
|
@ -0,0 +1,271 @@
|
|||
package generator
|
||||
|
||||
var (
|
||||
MainTemp = `
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"flag"
|
||||
"git.nobla.cn/golang/aeus"
|
||||
"git.nobla.cn/golang/aeus/transport/cli"
|
||||
"git.nobla.cn/golang/aeus/transport/grpc"
|
||||
"git.nobla.cn/golang/aeus/transport/http"
|
||||
|
||||
"{{.PackageName}}/version"
|
||||
"{{.PackageName}}/internal/scope"
|
||||
"{{.PackageName}}/internal/service"
|
||||
)
|
||||
|
||||
var (
|
||||
versionFlag = flag.Bool("version", false, "Show version")
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
flag.Parse()
|
||||
if *versionFlag{
|
||||
fmt.Println(version.Info())
|
||||
os.Exit(0)
|
||||
}
|
||||
app := aeus.New(
|
||||
aeus.WithName(version.ProductName),
|
||||
aeus.WithVersion(version.Version),
|
||||
aeus.WithServer(
|
||||
http.New(),
|
||||
grpc.New(),
|
||||
cli.New(),
|
||||
),
|
||||
aeus.WithScope(scope.NewScope()),
|
||||
aeus.WithServiceLoader(service.NewLoader()),
|
||||
)
|
||||
|
||||
if err = app.Run(); err != nil {
|
||||
fmt.Println("app run error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
ScopeTemp = `
|
||||
package scope
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.nobla.cn/golang/aeus/transport/cli"
|
||||
"git.nobla.cn/golang/aeus/transport/grpc"
|
||||
"git.nobla.cn/golang/aeus/transport/http"
|
||||
)
|
||||
|
||||
type ScopeContext struct {
|
||||
ctx context.Context
|
||||
Http *http.Server
|
||||
Grpc *grpc.Server
|
||||
Cli *cli.Server
|
||||
}
|
||||
|
||||
func (s *ScopeContext) Init(ctx context.Context) (err error) {
|
||||
s.ctx = ctx
|
||||
return
|
||||
}
|
||||
|
||||
func NewScope() *ScopeContext {
|
||||
return &ScopeContext{}
|
||||
}
|
||||
`
|
||||
|
||||
ServiceLoaderTemp = `
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"{{.PackageName}}/internal/scope"
|
||||
"git.nobla.cn/golang/aeus/transport/cli"
|
||||
"git.nobla.cn/golang/aeus/transport/grpc"
|
||||
"git.nobla.cn/golang/aeus/transport/http"
|
||||
)
|
||||
|
||||
type serviceLoader struct {
|
||||
Sope *scope.ScopeContext
|
||||
Http *http.Server
|
||||
Grpc *grpc.Server
|
||||
Cli *cli.Server
|
||||
}
|
||||
|
||||
func (s *serviceLoader) Init(ctx context.Context) (err error) {
|
||||
// bind services here
|
||||
return
|
||||
}
|
||||
|
||||
func (s *serviceLoader) Run(ctx context.Context) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func NewLoader() *serviceLoader {
|
||||
return &serviceLoader{}
|
||||
}
|
||||
`
|
||||
|
||||
MakefileTemp = `
|
||||
GOHOSTOS:=$(shell go env GOHOSTOS)
|
||||
GOPATH:=$(shell go env GOPATH)
|
||||
VERSION=$(shell git describe --tags --always)
|
||||
DATETIME:=$(shell date "+%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
PROTO_DIR="api/v1/pb"
|
||||
PROTO_OUT_DIR="api/v1/pb"
|
||||
PROTO_FILES=$(shell find api -name *.proto)
|
||||
|
||||
.PHONY: proto
|
||||
proto:
|
||||
protoc --proto_path=$(PROTO_DIR) \
|
||||
--proto_path=./third_party \
|
||||
--go_out=paths=source_relative:$(PROTO_OUT_DIR) \
|
||||
--go-grpc_out=paths=source_relative:$(PROTO_OUT_DIR) \
|
||||
--go-aeus_out=paths=source_relative:$(PROTO_OUT_DIR) \
|
||||
--validate_out=paths=source_relative,lang=go:$(PROTO_OUT_DIR) \
|
||||
$(PROTO_FILES)
|
||||
|
||||
.PHONY: proto-clean
|
||||
proto-clean:
|
||||
rm -rf $(PROTO_OUT_DIR)/*.pb.go
|
||||
rm -rf $(PROTO_OUT_DIR)/*.pb.validate.go
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
docker build . -t $(IMAGE_REGISTRY_URL)
|
||||
|
||||
.PHONY: deploy
|
||||
deploy:
|
||||
dkctl apply -f deployment.yaml
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go mod tidy
|
||||
go mod vendor
|
||||
CGO_ENABLED=0 go build -a -installsuffix cgo -ldflags "-s -w -X '{{.PackageName}}/version.Version=$(VERSION)' -X '{{.PackageName}}/version.BuildDate=$(DATETIME)'" -o bin/{{.ShortName}} cmd/main.go
|
||||
`
|
||||
|
||||
GreeterTemp = `
|
||||
syntax = "proto3";
|
||||
|
||||
package greeter;
|
||||
|
||||
import "google/api/annotations.proto";
|
||||
import "aeus/command.proto";
|
||||
import "aeus/rest.proto";
|
||||
import "validate/validate.proto";
|
||||
|
||||
option go_package = "{{.PackageName}}/api/v1/pb;pb";
|
||||
|
||||
// The greeting service definition.
|
||||
service Greeter {
|
||||
// Sends a greeting
|
||||
rpc SayHello (HelloRequest) returns (HelloReply) {
|
||||
option (google.api.http) = {
|
||||
get: "/helloworld/{name}"
|
||||
};
|
||||
option (aeus.command) = {
|
||||
path: "/helloworld/:name",
|
||||
description: "Greeter"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// The request message containing the user's name.
|
||||
message HelloRequest {
|
||||
option (aeus.rest) = {
|
||||
table: "users"
|
||||
};
|
||||
int64 id = 1 [(aeus.field)={gorm:"primary_key"},(validate.rules).int64.gt = 999];
|
||||
string name = 2;
|
||||
}
|
||||
|
||||
// The response message containing the greetings
|
||||
message HelloReply {
|
||||
string message = 1;
|
||||
}
|
||||
`
|
||||
VersionTemp = `
|
||||
package version
|
||||
|
||||
import "fmt"
|
||||
|
||||
var (
|
||||
Version = "{{.Version}}"
|
||||
BuildDate = "{{.Datetime}}"
|
||||
ProductName = "{{.ShortName}}"
|
||||
)
|
||||
|
||||
func Info() string {
|
||||
return fmt.Sprintf("%s version: %s (built at %s)", ProductName, Version, BuildDate)
|
||||
}
|
||||
`
|
||||
|
||||
GitIgnoreTemp = `
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
bin/
|
||||
.svn/
|
||||
.godeps
|
||||
./build
|
||||
.cover/
|
||||
*.dat
|
||||
vendor
|
||||
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
`
|
||||
ReadmeTemp = ``
|
||||
|
||||
GoModTemp = `
|
||||
module {{.PackageName}}
|
||||
|
||||
go 1.23.0
|
||||
`
|
||||
WebhookTemp = `
|
||||
name: {{.ShortName}}
|
||||
steps:
|
||||
- name: build
|
||||
run: "make build"
|
||||
- name: docker
|
||||
run: "make docker"
|
||||
- name: deploy
|
||||
run: "make deploy"
|
||||
replacements:
|
||||
- src: deploy/docker/deployment.yaml
|
||||
dst: deployment.yaml
|
||||
`
|
||||
DeploymentTemp = `
|
||||
name: {{.ShortName}}
|
||||
image: {{.ImageRegistry}}
|
||||
command: ["{{.ShortName}}"]
|
||||
|
||||
network:
|
||||
name: employ
|
||||
ip: 10.5.10.2
|
||||
|
||||
env:
|
||||
- name: TZ
|
||||
value: "Asia/Shanghai"
|
||||
- name: APP_NAME
|
||||
value: "{{.ShortName}}"
|
||||
|
||||
volume:
|
||||
- name: config
|
||||
path: /etc/{{.ShortName}}/
|
||||
hostPath: /apps/{{.ShortName}}/conf/
|
||||
`
|
||||
)
|
|
@ -0,0 +1,16 @@
|
|||
package types
|
||||
|
||||
import "strings"
|
||||
|
||||
type Applicetion struct {
|
||||
Package string
|
||||
Version string
|
||||
}
|
||||
|
||||
func (app *Applicetion) ShortName() string {
|
||||
pos := strings.LastIndex(app.Package, "/")
|
||||
if pos > -1 {
|
||||
return app.Package[pos+1:]
|
||||
}
|
||||
return app.Package
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"git.nobla.cn/golang/aeus/tools/gen/internal/generator"
|
||||
"git.nobla.cn/golang/aeus/tools/gen/internal/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func waitingSignal(ctx context.Context, cancelFunc context.CancelFunc) {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signals := []os.Signal{syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL}
|
||||
signal.Notify(ch, signals...)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-ch:
|
||||
cancelFunc()
|
||||
close(ch)
|
||||
}
|
||||
}
|
||||
|
||||
func createCommand() *cobra.Command {
|
||||
var (
|
||||
version string
|
||||
)
|
||||
cmd := &cobra.Command{
|
||||
Use: "new",
|
||||
Short: "Create microservice application",
|
||||
Long: "Create microservice application",
|
||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("Please specify the package name")
|
||||
}
|
||||
if version, err = cmd.Flags().GetString("version"); err != nil {
|
||||
return
|
||||
}
|
||||
return generator.Geerate(&types.Applicetion{
|
||||
Package: args[0],
|
||||
Version: version,
|
||||
})
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringP("version", "v", "v0.0.1", "Application version")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func main() {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
cmd := &cobra.Command{
|
||||
Use: "aeus",
|
||||
Short: "aeus is a tool for manager microservices",
|
||||
Long: "aeus is a tool for manager microservice application",
|
||||
SilenceErrors: true,
|
||||
}
|
||||
go waitingSignal(ctx, cancelFunc)
|
||||
cmd.AddCommand(createCommand())
|
||||
if err = cmd.ExecuteContext(ctx); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
cancelFunc()
|
||||
}
|
|
@ -22,10 +22,6 @@ func (c *Context) Context() context.Context {
|
|||
return c.ctx.Request.Context()
|
||||
}
|
||||
|
||||
func (c *Context) Gin() *gin.Context {
|
||||
return c.ctx
|
||||
}
|
||||
|
||||
func (c *Context) Request() *http.Request {
|
||||
return c.ctx.Request
|
||||
}
|
||||
|
@ -42,14 +38,6 @@ func (c *Context) Param(key string) string {
|
|||
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) {
|
||||
// if params exists, try bind params first
|
||||
if len(c.ctx.Params) > 0 {
|
||||
|
|
|
@ -3,17 +3,13 @@ package http
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -110,61 +106,14 @@ func (s *Server) Webroot(prefix string, fs http.FileSystem) {
|
|||
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()
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
http.ServeContent(ctx.Writer, ctx.Request, path.Base(uri), s.fs.modtime, fp)
|
||||
fp.Close()
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.JSON(http.StatusNotFound, newResponse(errors.NotFound, "Not Found", nil))
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"git.nobla.cn/golang/aeus/pkg/logger"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
@ -47,77 +41,6 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
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 {
|
||||
return func(o *options) {
|
||||
o.network = network
|
||||
|
|
Loading…
Reference in New Issue