fix /* router supported for handler of package ghttp; fix json tag name issue when it contains , for package goai; add proxy example for http server (#2294)

* fix  router supported for handler of package ghttp; fix json tag name issue when it contains  for package goai

* add proxy example for http server
This commit is contained in:
John Guo
2022-11-14 19:57:39 +08:00
committed by GitHub
parent 7d1a508ea9
commit 9402cc8c6a
13 changed files with 152 additions and 46 deletions

View File

@ -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()
}

View File

@ -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 (

View File

@ -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)
}

View File

@ -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.
@ -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.
@ -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
}

View File

@ -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
@ -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)
}
}

View File

@ -142,7 +142,6 @@ func (s *Server) setHandler(ctx context.Context, in setHandlerInput) {
pattern, handler.Source, duplicatedHandler.Source,
)
}
return
}
}
}
@ -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

View File

@ -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
@ -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

View File

@ -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.
@ -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 {

View File

@ -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(),

View File

@ -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}`)
})
}

View File

@ -23,6 +23,7 @@ import (
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gtag"
)
var (
@ -36,9 +37,11 @@ var (
}
// StructTagPriority defines the default priority tags for Map*/Struct* functions.
// Note, the `gconv/param/params` tags are used by old version of package.
// Note that, the `gconv/param` tags are used by old version of package.
// It is strongly recommended using short tag `c/p` instead in the future.
StructTagPriority = []string{"gconv", "param", "params", "c", "p", "json"}
StructTagPriority = []string{
gtag.GConv, gtag.Param, gtag.GConvShort, gtag.ParamShort, gtag.Json,
}
)
// Byte converts `any` to byte.

View File

@ -541,8 +541,8 @@ func Test_StructEmbedded4(t *testing.T) {
func Test_StructEmbedded5(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type Base struct {
Pass1 string `params:"password1"`
Pass2 string `params:"password2"`
Pass1 string `param:"password1"`
Pass2 string `param:"password2"`
}
type UserWithBase1 struct {
Id int

View File

@ -41,4 +41,7 @@ const (
ExamplesShort = `egs` // Short name of Examples.
ExternalDocs = `externalDocs` // External docs for struct, always for OpenAPI in request struct.
ExternalDocsShort = `ed` // Short name of ExternalDocs.
GConv = "gconv" // GConv defines the converting target name for specified struct field.
GConvShort = "c" // GConv defines the converting target name for specified struct field.
Json = "json" // Json tag is supported by stdlib.
)