mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
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:
50
example/httpserver/proxy/main.go
Normal file
50
example/httpserver/proxy/main.go
Normal 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()
|
||||
}
|
||||
@ -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 (
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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}`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user