Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/v2.3.0 #2296

Merged
merged 8 commits into from
Jan 9, 2023
9 changes: 9 additions & 0 deletions cmd/gf/internal/cmd/cmd_fix.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,16 @@ func (c cFix) doFix() (err error) {
// doFixV23 fixes code when upgrading to GoFrame v2.3.
func (c cFix) doFixV23(version string) error {
replaceFunc := func(path, content string) string {
// gdb.TX from struct to interface.
content = gstr.Replace(content, "*gdb.TX", "gdb.TX")
// function name changes for package gtcp/gudp.
if gstr.Contains(content, "/gf/v2/net/gtcp") || gstr.Contains(content, "/gf/v2/net/gudp") {
content = gstr.ReplaceByMap(content, g.MapStrStr{
".SetSendDeadline": ".SetDeadlineSend",
".SetReceiveDeadline": ".SetDeadlineRecv",
".SetReceiveBufferWait": ".SetBufferWaitRecv",
})
}
return content
}
return gfile.ReplaceDirFunc(replaceFunc, ".", "*.go", true)
Expand Down
50 changes: 50 additions & 0 deletions example/httpserver/proxy/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package main

import (
"net/http"
"net/http/httputil"
"net/url"

"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)

const (
PortOfServer1 = 8198
PortOfServer2 = 8199
UpStream = "http://127.0.0.1:8198"
)

// StartServer1 starts Server1: A simple http server for demo.
func StartServer1() {
s := g.Server(1)
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Write("response from server 1")
})
s.BindHandler("/user/1", func(r *ghttp.Request) {
r.Response.Write("user info from server 1")
})
s.SetPort(PortOfServer1)
s.Run()
}

// StartServer2 starts Server2:
// All requests to Server2 are directly redirected to Server1.
func StartServer2() {
s := g.Server(2)
u, _ := url.Parse(UpStream)
s.BindHandler("/*", func(r *ghttp.Request) {
proxy := httputil.NewSingleHostReverseProxy(u)
proxy.ErrorHandler = func(writer http.ResponseWriter, request *http.Request, e error) {
writer.WriteHeader(http.StatusBadGateway)
}
proxy.ServeHTTP(r.Response.Writer.RawWriter(), r.Request)
})
s.SetPort(PortOfServer2)
s.Run()
}

func main() {
go StartServer1()
StartServer2()
}
93 changes: 93 additions & 0 deletions example/tcp/server/proxy/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package main

import (
"context"
"fmt"
"io"
"time"

"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gtcp"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/os/gtimer"
)

const (
AddressOfServer1 = ":8198"
AddressOfServer2 = ":8199"
UpStream = "127.0.0.1:8198"
)

var (
ctx = gctx.GetInitCtx()
)

// StartTCPServer1 starts Server1: A simple tcp server for demo.
// It reads the content from client connect and write it back to client.
func StartTCPServer1() {
s := g.TCPServer(1)
s.SetHandler(func(conn *gtcp.Conn) {
defer conn.Close()
for {
data, err := conn.Recv(-1)
if err != nil {
g.Log().Errorf(ctx, `%+v`, err)
break
}
if len(data) > 0 {
err = conn.Send([]byte(fmt.Sprintf(`received: %s`, data)))
if err != nil {
g.Log().Errorf(ctx, `%+v`, err)
break
}
}
}
})
s.SetAddress(AddressOfServer1)
s.Run()
}

// StartTCPServer2 starts Server2:
// All requests to Server2 are directly redirected to Server1.
func StartTCPServer2() {
s := g.TCPServer(2)
s.SetHandler(func(conn *gtcp.Conn) {
defer conn.Close()
// Each client connection associates an upstream connection.
upstreamClient, err := gtcp.NewConn(UpStream)
if err != nil {
_, _ = conn.Write([]byte(fmt.Sprintf(
`cannot connect to upstream "%s": %s`, UpStream, err.Error(),
)))
return
}
// Redirect the client connection reading and writing to upstream connection.
for {
go io.Copy(upstreamClient, conn)
_, err = io.Copy(conn, upstreamClient)
if err != nil {
_, _ = conn.Write([]byte(fmt.Sprintf(
`io.Copy to upstream "%s" failed: %s`, UpStream, err.Error(),
)))
}
}
})
s.SetAddress(AddressOfServer2)
s.Run()
}

func main() {
go StartTCPServer1()
go StartTCPServer2()
time.Sleep(time.Second)
gtimer.Add(ctx, time.Second, func(ctx context.Context) {
address := fmt.Sprintf(`127.0.0.1%s`, AddressOfServer2)
result, err := gtcp.SendRecv(address, []byte(gtime.Now().String()), -1)
if err != nil {
g.Log().Errorf(ctx, `send data failed: %+v`, err)
}
g.Log().Info(ctx, result)
})
g.Listen()
}
27 changes: 14 additions & 13 deletions net/ghttp/ghttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,19 +128,20 @@ const (
)

const (
supportedHttpMethods = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
defaultMethod = "ALL"
routeCacheDuration = time.Hour
ctxKeyForRequest = "gHttpRequestObject"
contentTypeXml = "text/xml"
contentTypeHtml = "text/html"
contentTypeJson = "application/json"
swaggerUIPackedPath = "/goframe/swaggerui"
responseTraceIDHeader = "Trace-ID"
specialMethodNameInit = "Init"
specialMethodNameShut = "Shut"
specialMethodNameIndex = "Index"
gracefulShutdownTimeout = 5 * time.Second
supportedHttpMethods = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
defaultMethod = "ALL"
routeCacheDuration = time.Hour
ctxKeyForRequest = "gHttpRequestObject"
contentTypeXml = "text/xml"
contentTypeHtml = "text/html"
contentTypeJson = "application/json"
swaggerUIPackedPath = "/goframe/swaggerui"
responseHeaderTraceID = "Trace-ID"
responseHeaderContentLength = "Content-Length"
specialMethodNameInit = "Init"
specialMethodNameShut = "Shut"
specialMethodNameIndex = "Index"
gracefulShutdownTimeout = 5 * time.Second
)

const (
Expand Down
2 changes: 1 addition & 1 deletion net/ghttp/ghttp_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func (r *Response) ClearBuffer() {

// Flush outputs the buffer content to the client and clears the buffer.
func (r *Response) Flush() {
r.Header().Set(responseTraceIDHeader, gtrace.GetTraceID(r.Request.Context()))
r.Header().Set(responseHeaderTraceID, gtrace.GetTraceID(r.Request.Context()))
if r.Server.config.ServerAgent != "" {
r.Header().Set("Server", r.Server.config.ServerAgent)
}
Expand Down
20 changes: 13 additions & 7 deletions net/ghttp/ghttp_response_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ import (

// ResponseWriter is the custom writer for http response.
type ResponseWriter struct {
Status int // HTTP status.
writer http.ResponseWriter // The underlying ResponseWriter.
buffer *bytes.Buffer // The output buffer.
hijacked bool // Mark this request is hijacked or not.
wroteHeader bool // Is header wrote or not, avoiding error: superfluous/multiple response.WriteHeader call.
Status int // HTTP status.
writer http.ResponseWriter // The underlying ResponseWriter.
buffer *bytes.Buffer // The output buffer.
hijacked bool // Mark this request is hijacked or not.
}

// RawWriter returns the underlying ResponseWriter.
Expand Down Expand Up @@ -55,8 +54,7 @@ func (w *ResponseWriter) Flush() {
if w.hijacked {
return
}
if w.Status != 0 && !w.wroteHeader {
w.wroteHeader = true
if w.Status != 0 && !w.isHeaderWritten() {
w.writer.WriteHeader(w.Status)
}
// Default status text output.
Expand All @@ -68,3 +66,11 @@ func (w *ResponseWriter) Flush() {
w.buffer.Reset()
}
}

// isHeaderWrote checks and returns whether the header is written.
func (w *ResponseWriter) isHeaderWritten() bool {
if _, ok := w.writer.Header()[responseHeaderContentLength]; ok {
return true
}
return false
}
2 changes: 0 additions & 2 deletions net/ghttp/ghttp_server_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,6 @@ func (s *Server) serveFile(r *Request, f *staticFile, allowIndex ...bool) {
}
} else {
info := f.File.FileInfo()
r.Response.wroteHeader = true
http.ServeContent(r.Response.Writer.RawWriter(), r.Request, info.Name(), info.ModTime(), f.File)
}
return
Expand All @@ -301,7 +300,6 @@ func (s *Server) serveFile(r *Request, f *staticFile, allowIndex ...bool) {
r.Response.WriteStatus(http.StatusForbidden)
}
} else {
r.Response.wroteHeader = true
http.ServeContent(r.Response.Writer.RawWriter(), r.Request, info.Name(), info.ModTime(), file)
}
}
Expand Down
11 changes: 2 additions & 9 deletions net/ghttp/ghttp_server_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ func (s *Server) setHandler(ctx context.Context, in setHandlerInput) {
pattern, handler.Source, duplicatedHandler.Source,
)
}
return
}
}
}
Expand Down Expand Up @@ -245,14 +244,8 @@ func (s *Server) setHandler(ctx context.Context, in setHandlerInput) {
s.routesMap[routerKey] = make([]*HandlerItem, 0)
}

switch handler.Type {
case HandlerTypeHandler, HandlerTypeObject:
// Overwrite the route.
s.routesMap[routerKey] = []*HandlerItem{handler}
default:
// Append the route.
s.routesMap[routerKey] = append(s.routesMap[routerKey], handler)
}
// Append the route.
s.routesMap[routerKey] = append(s.routesMap[routerKey], handler)
}

// compareRouterPriority compares the priority between `newItem` and `oldItem`. It returns true
Expand Down
8 changes: 4 additions & 4 deletions net/ghttp/ghttp_z_unit_feature_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ func Test_Params_Basic(t *testing.T) {
type User struct {
Id int
Name string
Pass1 string `params:"password1"`
Pass2 string `params:"password2"`
Pass1 string `p:"password1"`
Pass2 string `p:"password2"`
}
s := g.Server(guid.S())
// GET
Expand Down Expand Up @@ -236,8 +236,8 @@ func Test_Params_Basic(t *testing.T) {
})
s.BindHandler("/struct-with-base", func(r *ghttp.Request) {
type Base struct {
Pass1 string `params:"password1"`
Pass2 string `params:"password2"`
Pass1 string `p:"password1"`
Pass2 string `p:"password2"`
}
type UserWithBase1 struct {
Id int
Expand Down
18 changes: 12 additions & 6 deletions net/goai/goai_parameter_ref.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/os/gstructs"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)

// Parameters is specified by OpenAPI/Swagger 3.0 standard.
Expand All @@ -29,13 +30,18 @@ type ParameterRef struct {
func (oai *OpenApiV3) newParameterRefWithStructMethod(field gstructs.Field, path, method string) (*ParameterRef, error) {
var (
tagMap = field.TagMap()
parameter = &Parameter{
Name: field.TagJsonName(),
XExtensions: make(XExtensions),
}
fieldName = field.Name()
)
if parameter.Name == "" {
parameter.Name = field.Name()
for _, tagName := range gconv.StructTagPriority {
if tagValue := field.Tag(tagName); tagValue != "" {
fieldName = tagValue
break
}
}
fieldName = gstr.SplitAndTrim(fieldName, ",")[0]
var parameter = &Parameter{
Name: fieldName,
XExtensions: make(XExtensions),
}
if len(tagMap) > 0 {
if err := oai.tagMapToParameter(tagMap, parameter); err != nil {
Expand Down
1 change: 1 addition & 0 deletions net/goai/goai_shema.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) {
break
}
}
fieldName = gstr.SplitAndTrim(fieldName, ",")[0]
schemaRef, err := oai.newSchemaRefWithGolangType(
structField.Type().Type,
structField.TagMap(),
Expand Down
45 changes: 45 additions & 0 deletions net/goai/goai_z_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1001,3 +1001,48 @@ func Test_EmbeddedStructAttribute(t *testing.T) {
t.Assert(b, `{"openapi":"3.0.0","components":{"schemas":{"github.com.gogf.gf.v2.net.goai_test.CreateResourceReq":{"properties":{"Name":{"description":"This is name.","format":"string","properties":{},"type":"string"},"Embedded":{"properties":{"Age":{"description":"This is embedded age.","format":"uint","properties":{},"type":"integer"}},"type":"object"}},"type":"object"}}},"info":{"title":"","version":""},"paths":null}`)
})
}

func Test_NameFromJsonTag(t *testing.T) {
// POST
gtest.C(t, func(t *gtest.T) {
type CreateReq struct {
gmeta.Meta `path:"/CreateReq" method:"POST"`
Name string `json:"nick_name, omitempty"`
}

var (
err error
oai = goai.New()
req = new(CreateReq)
)
err = oai.Add(goai.AddInput{
Object: req,
})
t.AssertNil(err)

b, err := json.Marshal(oai)
t.AssertNil(err)
t.Assert(b, `{"openapi":"3.0.0","components":{"schemas":{"github.com.gogf.gf.v2.net.goai_test.CreateReq":{"properties":{"nick_name":{"format":"string","properties":{},"type":"string"}},"type":"object"}}},"info":{"title":"","version":""},"paths":null}`)
})
// GET
gtest.C(t, func(t *gtest.T) {
type CreateReq struct {
gmeta.Meta `path:"/CreateReq" method:"GET"`
Name string `json:"nick_name, omitempty" in:"header"`
}
var (
err error
oai = goai.New()
req = new(CreateReq)
)
err = oai.Add(goai.AddInput{
Object: req,
})
t.AssertNil(err)

b, err := json.Marshal(oai)
t.AssertNil(err)
fmt.Println(string(b))
t.Assert(b, `{"openapi":"3.0.0","components":{"schemas":{"github.com.gogf.gf.v2.net.goai_test.CreateReq":{"properties":{"nick_name":{"format":"string","properties":{},"type":"string"}},"type":"object"}}},"info":{"title":"","version":""},"paths":null}`)
})
}
Loading