Merge branch 'develop'

This commit is contained in:
John Guo
2022-06-15 21:43:04 +08:00
17 changed files with 405 additions and 120 deletions

View File

@ -23,7 +23,7 @@ type (
SrcFolder string `short:"s" name:"srcFolder" brief:"source folder path to be parsed. default: internal/logic" d:"internal/logic"`
DstFolder string `short:"d" name:"dstFolder" brief:"destination folder path storing automatically generated go files. default: internal/service" d:"internal/service"`
WatchFile string `short:"w" name:"watchFile" brief:"used in file watcher, it generates service go files only if given file is under srcFolder"`
StPattern string `short:"a" name:"stPattern" brief:"regular expression matching struct name for generating service. default: s([A-Z]\\w+)" d:"s([A-Z]\\w+)"`
StPattern string `short:"a" name:"stPattern" brief:"regular expression matching struct name for generating service. default: s([A-Z]\\\w+)" d:"s([A-Z]\\w+)"`
Packages string `short:"p" name:"packages" brief:"produce go files only for given source packages, multiple packages joined with char ','"`
ImportPrefix string `short:"i" name:"importPrefix" brief:"custom import prefix to calculate import path for generated importing go file of logic"`
OverWrite bool `short:"o" name:"overwrite" brief:"overwrite service go files that already exist in generating folder. default: true" d:"true" orphan:"true"`

View File

@ -0,0 +1,79 @@
# GoFrame Etcd Registry
Use `etcd` as service registration and discovery management.
## Installation
```
go get -u -v github.com/gogf/gf/contrib/registry/etcd/v2
```
suggested using `go.mod`:
```
require github.com/gogf/gf/contrib/registry/etcd/v2 latest
```
## Example
### Reference example
[server](example/registry/etcd/server/main.go)
```go
package main
import (
"github.com/gogf/gf/contrib/registry/etcd/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/net/gsvc"
)
func main() {
gsvc.SetRegistry(etcd.New(`127.0.0.1:2379`))
s := g.Server(`hello.svc`)
s.BindHandler("/", func(r *ghttp.Request) {
g.Log().Info(r.Context(), `request received`)
r.Response.Write(`Hello world`)
})
s.Run()
}
```
[client](example/registry/etcd/client/main.go)
```go
package main
import (
"fmt"
"time"
"github.com/gogf/gf/contrib/registry/etcd/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gsel"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/os/gctx"
)
func main() {
gsvc.SetRegistry(etcd.New(`127.0.0.1:2379`))
gsel.SetBuilder(gsel.NewBuilderRoundRobin())
client := g.Client()
for i := 0; i < 100; i++ {
res, err := client.Get(gctx.New(), `http://hello.svc/`)
if err != nil {
panic(err)
}
fmt.Println(res.ReadAllString())
res.Close()
time.Sleep(time.Second)
}
}
```
## License
`GoFrame etcd` is licensed under the [MIT License](../../../LICENSE), 100% free and open-source, forever.

View File

@ -2,7 +2,7 @@
English | [简体中文](README_ZH.MD)
Use Polaris mesh as service registration, discovery management and heartbeat reporting
Use `PolarisMesh` as service registration, discovery management and heartbeat reporting.
## Installation
@ -14,11 +14,6 @@ suggested using `go.mod`:
require github.com/gogf/gf/contrib/registry/polaris/v2 latest
```
## Limitation
```
golang version >= 1.15
```
## Example
@ -50,7 +45,6 @@ func main() {
})
s.Run()
}
```
[client](example/registry/polaris/client/main.go)
@ -85,7 +79,6 @@ func main() {
time.Sleep(time.Second)
}
}
```
## License

View File

@ -2,7 +2,7 @@
[English](README.MD) | 简体中文
使用Polarismesh作为服务注册、发现管理和心跳上报
使用`PolarisMesh`作为服务注册、发现管理和心跳上报
## Installation

View File

@ -222,6 +222,9 @@ func Code(err error) gcode.Code {
if e, ok := err.(iNext); ok {
return Code(e.Next())
}
if e, ok := err.(iUnwrap); ok {
return Code(e.Unwrap())
}
return gcode.CodeNil
}
@ -236,6 +239,9 @@ func Cause(err error) error {
if e, ok := err.(iNext); ok {
return Cause(e.Next())
}
if e, ok := err.(iUnwrap); ok {
return Cause(e.Unwrap())
}
return err
}

View File

@ -49,3 +49,9 @@ type iNext interface {
Error() string
Next() error
}
// iUnwrap is the interface for Unwrap feature.
type iUnwrap interface {
Error() string
Unwrap() error
}

View File

@ -55,3 +55,30 @@ func ExampleWrapCodef() {
// It's Custom Error: permission denied
// 10000
}
func ExampleEqual() {
err1 := errors.New("permission denied")
err2 := gerror.New("permission denied")
err3 := gerror.NewCode(gcode.CodeNotAuthorized, "permission denied")
fmt.Println(gerror.Equal(err1, err2))
fmt.Println(gerror.Equal(err2, err3))
// Output:
// true
// false
}
func ExampleIs() {
err1 := errors.New("permission denied")
err2 := gerror.Wrap(err1, "operation failed")
fmt.Println(gerror.Is(err1, err1))
fmt.Println(gerror.Is(err2, err2))
fmt.Println(gerror.Is(err2, err1))
fmt.Println(gerror.Is(err1, err2))
// Output:
// false
// true
// true
// false
}

View File

@ -79,13 +79,17 @@ func New() *Client {
func (c *Client) Clone() *Client {
newClient := New()
*newClient = *c
newClient.header = make(map[string]string)
newClient.cookies = make(map[string]string)
for k, v := range c.header {
newClient.header[k] = v
if len(c.header) > 0 {
newClient.header = make(map[string]string)
for k, v := range c.header {
newClient.header[k] = v
}
}
for k, v := range c.cookies {
newClient.cookies[k] = v
if len(c.cookies) > 0 {
newClient.cookies = make(map[string]string)
for k, v := range c.cookies {
newClient.cookies[k] = v
}
}
return newClient
}

View File

@ -12,6 +12,9 @@ import (
// Prefix is a chaining function,
// which sets the URL prefix for next request of this client.
// Eg:
// Prefix("http://127.0.0.1:8199/api/v1")
// Prefix("http://127.0.0.1:8199/api/v2")
func (c *Client) Prefix(prefix string) *Client {
newClient := c.Clone()
newClient.SetPrefix(prefix)

View File

@ -0,0 +1,95 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gclient
import (
"context"
"net/http"
"reflect"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/goai"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gmeta"
)
// DoRequestObj does HTTP request using standard request/response object.
// The request object `req` is defined like:
// type UseCreateReq struct {
// g.Meta `path:"/user" method:"put"`
// // other fields....
// }
// The response object `res` should be a pointer type. It automatically converts result
// to given object `res` is success.
// Eg:
// var (
// req = UseCreateReq{}
// res *UseCreateRes
// )
// DoRequestObj(ctx, req, &res)
func (c *Client) DoRequestObj(ctx context.Context, req, res interface{}) error {
var (
method = gmeta.Get(req, goai.TagNameMethod).String()
path = gmeta.Get(req, goai.TagNamePath).String()
)
if method == "" {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`no "%s" tag found in request object: %s`,
goai.TagNameMethod, reflect.TypeOf(req).String(),
)
}
if path == "" {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`no "%s" tag found in request object: %s`,
goai.TagNamePath, reflect.TypeOf(req).String(),
)
}
path = c.handlePathForObjRequest(path, req)
switch gstr.ToUpper(method) {
case
http.MethodGet,
http.MethodPut,
http.MethodPost,
http.MethodDelete,
http.MethodHead,
http.MethodPatch,
http.MethodConnect,
http.MethodOptions,
http.MethodTrace:
if result := c.RequestVar(ctx, method, path, req); res != nil && !result.IsEmpty() {
return result.Scan(res)
}
return nil
default:
return gerror.Newf(`invalid HTTP method "%s"`, method)
}
}
// handlePathForObjRequest replaces parameters in `path` with parameters from request object.
// Eg:
// /order/{id} -> /order/1
// /user/{name} -> /order/john
func (c *Client) handlePathForObjRequest(path string, req interface{}) string {
if gstr.Contains(path, "{") {
requestParamsMap := gconv.MapStrStr(req)
if len(requestParamsMap) > 0 {
path, _ = gregex.ReplaceStringFuncMatch(`\{(\w+)\}`, path, func(match []string) string {
if v, ok := requestParamsMap[match[1]]; ok {
return v
}
return match[1]
})
}
}
return path
}

View File

@ -11,13 +11,14 @@ import (
"crypto/tls"
"encoding/hex"
"fmt"
"net/http"
"time"
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/net/gclient"
"github.com/gogf/gf/v2/net/gtcp"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gfile"
"net/http"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"

View File

@ -0,0 +1,78 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gclient_test
import (
"context"
"fmt"
"net/http"
"testing"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/internal/tracing"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/net/gtcp"
"github.com/gogf/gf/v2/test/gtest"
"go.opentelemetry.io/otel"
sdkTrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)
type CustomProvider struct {
*sdkTrace.TracerProvider
}
func NewCustomProvider() *CustomProvider {
return &CustomProvider{
TracerProvider: sdkTrace.NewTracerProvider(
sdkTrace.WithIDGenerator(NewCustomIDGenerator()),
),
}
}
type CustomIDGenerator struct{}
func NewCustomIDGenerator() *CustomIDGenerator {
return &CustomIDGenerator{}
}
func (id *CustomIDGenerator) NewIDs(ctx context.Context) (traceID trace.TraceID, spanID trace.SpanID) {
return tracing.NewIDs()
}
func (id *CustomIDGenerator) NewSpanID(ctx context.Context, traceID trace.TraceID) (spanID trace.SpanID) {
return tracing.NewSpanID()
}
func TestClient_CustomProvider(t *testing.T) {
provider := otel.GetTracerProvider()
defer otel.SetTracerProvider(provider)
otel.SetTracerProvider(NewCustomProvider())
p, _ := gtcp.GetFreePort()
s := g.Server(p)
s.BindHandler("/hello", func(r *ghttp.Request) {
r.Response.WriteHeader(200)
r.Response.WriteJson(g.Map{"field": "test_for_response_body"})
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
c := g.Client()
url := fmt.Sprintf("127.0.0.1:%d/hello", p)
resp, err := c.DoRequest(ctx, http.MethodGet, url)
t.AssertNil(err)
t.AssertNE(resp, nil)
t.Assert(resp.ReadAllString(), "{\"field\":\"test_for_response_body\"}")
})
}

View File

@ -0,0 +1,78 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gclient_test
import (
"fmt"
"testing"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/net/gtcp"
"github.com/gogf/gf/v2/test/gtest"
)
func Test_Client_DoRequestObj(t *testing.T) {
type UserCreateReq struct {
g.Meta `path:"/user" method:"post"`
Id int
Name string
}
type UserCreateRes struct {
Id int
}
type UserQueryReq struct {
g.Meta `path:"/user" method:"get"`
}
type UserQueryRes struct {
Id int
Name string
}
p, _ := gtcp.GetFreePort()
s := g.Server(p)
s.Group("/user", func(group *ghttp.RouterGroup) {
group.GET("/", func(r *ghttp.Request) {
r.Response.WriteJson(g.Map{"id": 1, "name": "john"})
})
group.POST("/", func(r *ghttp.Request) {
r.Response.WriteJson(g.Map{"id": r.Get("Id")})
})
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
url := fmt.Sprintf("http://127.0.0.1:%d", p)
client := g.Client().SetPrefix(url).ContentJson()
var (
createRes *UserCreateRes
createReq = UserCreateReq{
Id: 1,
Name: "john",
}
)
err := client.DoRequestObj(ctx, createReq, &createRes)
t.AssertNil(err)
t.Assert(createRes.Id, 1)
})
gtest.C(t, func(t *gtest.T) {
url := fmt.Sprintf("http://127.0.0.1:%d", p)
client := g.Client().SetPrefix(url).ContentJson()
var (
queryRes *UserQueryRes
queryReq = UserQueryReq{}
)
err := client.DoRequestObj(ctx, queryReq, &queryRes)
t.AssertNil(err)
t.Assert(queryRes.Id, 1)
t.Assert(queryRes.Name, "john")
})
}

View File

@ -11,16 +11,13 @@ import (
"context"
"crypto/tls"
"fmt"
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/internal/tracing"
"go.opentelemetry.io/otel"
sdkTrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
"io/ioutil"
"net/http"
"testing"
"time"
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gclient"
@ -643,58 +640,3 @@ func TestClient_RequestVar(t *testing.T) {
t.AssertNE(users, nil)
})
}
type CustomProvider struct {
*sdkTrace.TracerProvider
}
func NewCustomProvider() *CustomProvider {
return &CustomProvider{
TracerProvider: sdkTrace.NewTracerProvider(
sdkTrace.WithIDGenerator(NewCustomIDGenerator()),
),
}
}
type CustomIDGenerator struct{}
func NewCustomIDGenerator() *CustomIDGenerator {
return &CustomIDGenerator{}
}
func (id *CustomIDGenerator) NewIDs(ctx context.Context) (traceID trace.TraceID, spanID trace.SpanID) {
return tracing.NewIDs()
}
func (id *CustomIDGenerator) NewSpanID(ctx context.Context, traceID trace.TraceID) (spanID trace.SpanID) {
return tracing.NewSpanID()
}
func TestClient_CustomProvider(t *testing.T) {
provider := otel.GetTracerProvider()
defer otel.SetTracerProvider(provider)
otel.SetTracerProvider(NewCustomProvider())
p, _ := gtcp.GetFreePort()
s := g.Server(p)
s.BindHandler("/hello", func(r *ghttp.Request) {
r.Response.WriteHeader(200)
r.Response.WriteJson(g.Map{"field": "test_for_response_body"})
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
c := g.Client()
url := fmt.Sprintf("127.0.0.1:%d/hello", p)
resp, err := c.DoRequest(ctx, http.MethodGet, url)
t.AssertNil(err)
t.AssertNE(resp, nil)
t.Assert(resp.ReadAllString(), "{\"field\":\"test_for_response_body\"}")
})
}

View File

@ -11,7 +11,6 @@ import (
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/intlog"
)
// DefaultHandlerResponse is the default implementation of HandlerResponse.
@ -32,7 +31,6 @@ func MiddlewareHandlerResponse(r *Request) {
var (
msg string
ctx = r.Context()
err = r.GetError()
res = r.GetHandlerResponse()
code = gerror.Code(err)
@ -55,12 +53,9 @@ func MiddlewareHandlerResponse(r *Request) {
} else {
code = gcode.CodeOK
}
internalErr := r.Response.WriteJson(DefaultHandlerResponse{
r.Response.WriteJson(DefaultHandlerResponse{
Code: code.Code(),
Message: msg,
Data: res,
})
if internalErr != nil {
intlog.Errorf(ctx, `%+v`, internalErr)
}
}

View File

@ -12,6 +12,7 @@ import (
"net/http"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/util/gconv"
)
@ -102,48 +103,42 @@ func (r *Response) WriteflnExit(format string, params ...interface{}) {
}
// WriteJson writes `content` to the response with JSON format.
func (r *Response) WriteJson(content interface{}) error {
func (r *Response) WriteJson(content interface{}) {
r.Header().Set("Content-Type", contentTypeJson)
// If given string/[]byte, response it directly to the client.
switch content.(type) {
case string, []byte:
r.Write(gconv.String(content))
return nil
}
// Else use json.Marshal function to encode the parameter.
if b, err := json.Marshal(content); err != nil {
return err
panic(gerror.Wrap(err, `WriteJson failed`))
} else {
r.Write(b)
}
return nil
}
// WriteJsonExit writes `content` to the response with JSON format and exits executing
// of current handler if success. The "Exit" feature is commonly used to replace usage of
// return statements in the handler, for convenience.
func (r *Response) WriteJsonExit(content interface{}) error {
if err := r.WriteJson(content); err != nil {
return err
}
func (r *Response) WriteJsonExit(content interface{}) {
r.WriteJson(content)
r.Request.Exit()
return nil
}
// WriteJsonP writes `content` to the response with JSONP format.
//
// Note that there should be a "callback" parameter in the request for JSONP format.
func (r *Response) WriteJsonP(content interface{}) error {
func (r *Response) WriteJsonP(content interface{}) {
r.Header().Set("Content-Type", contentTypeJson)
// If given string/[]byte, response it directly to client.
switch content.(type) {
case string, []byte:
r.Write(gconv.String(content))
return nil
}
// Else use json.Marshal function to encode the parameter.
if b, err := json.Marshal(content); err != nil {
return err
panic(gerror.Wrap(err, `WriteJsonP failed`))
} else {
// r.Header().Set("Content-Type", "application/json")
if callback := r.Request.Get("callback").String(); callback != "" {
@ -156,7 +151,6 @@ func (r *Response) WriteJsonP(content interface{}) error {
r.Write(b)
}
}
return nil
}
// WriteJsonPExit writes `content` to the response with JSONP format and exits executing
@ -164,40 +158,32 @@ func (r *Response) WriteJsonP(content interface{}) error {
// return statements in the handler, for convenience.
//
// Note that there should be a "callback" parameter in the request for JSONP format.
func (r *Response) WriteJsonPExit(content interface{}) error {
if err := r.WriteJsonP(content); err != nil {
return err
}
func (r *Response) WriteJsonPExit(content interface{}) {
r.WriteJsonP(content)
r.Request.Exit()
return nil
}
// WriteXml writes `content` to the response with XML format.
func (r *Response) WriteXml(content interface{}, rootTag ...string) error {
func (r *Response) WriteXml(content interface{}, rootTag ...string) {
r.Header().Set("Content-Type", contentTypeXml)
// If given string/[]byte, response it directly to clients.
switch content.(type) {
case string, []byte:
r.Write(gconv.String(content))
return nil
}
if b, err := gjson.New(content).ToXml(rootTag...); err != nil {
return err
panic(gerror.Wrap(err, `WriteXml failed`))
} else {
r.Write(b)
}
return nil
}
// WriteXmlExit writes `content` to the response with XML format and exits executing
// of current handler if success. The "Exit" feature is commonly used to replace usage
// of return statements in the handler, for convenience.
func (r *Response) WriteXmlExit(content interface{}, rootTag ...string) error {
if err := r.WriteXml(content, rootTag...); err != nil {
return err
}
func (r *Response) WriteXmlExit(content interface{}, rootTag ...string) {
r.WriteXml(content, rootTag...)
r.Request.Exit()
return nil
}
// WriteStatus writes HTTP `status` and `content` to the response.

View File

@ -9,7 +9,6 @@ package ghttp
import (
"context"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/net/goai"
"github.com/gogf/gf/v2/text/gstr"
)
@ -48,16 +47,9 @@ func (s *Server) initOpenApi() {
// openapiSpec is a build-in handler automatic producing for openapi specification json file.
func (s *Server) openapiSpec(r *Request) {
var (
err error
)
if s.config.OpenApiPath == "" {
r.Response.Write(`OpenApi specification file producing is disabled`)
} else {
err = r.Response.WriteJson(s.openapi)
}
if err != nil {
intlog.Errorf(r.Context(), `%+v`, err)
r.Response.WriteJson(s.openapi)
}
}