diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 6987d79c..f1523790 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -1540,6 +1540,83 @@ func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) (ro return } +type ( + // ServerHandler is the interface which all server handlers should implement. + // The Iris Application implements it. + // See `Party.HandleServer` method for more. + ServerHandler interface { + ServeHTTPC(*context.Context) + } + + serverBuilder interface { + Build() error + } +) + +// HandleServer registers a route for all HTTP methods which forwards the requests to the given server. +// +// Usage: +// +// app.HandleServer("/api/identity/{first:string}/orgs/{second:string}/{p:path}", otherApp) +// +// OR +// +// app.HandleServer("/api/identity", otherApp) +func (api *APIBuilder) HandleServer(path string, server ServerHandler) { + if app, ok := server.(serverBuilder); ok { + // Do an extra check for Build() error at any case + // the end-developer didn't call Build before. + if err := app.Build(); err != nil { + panic(err) + } + } + + pathParameterName := "" + + // Check and get the last parameter name if it's a wildcard one by the end-developer. + parsedPath, err := macro.Parse(path, *api.macros) + if err != nil { + panic(err) + } + + if n := len(parsedPath.Params); n > 0 { + lastParam := parsedPath.Params[n-1] + if lastParam.IsMacro(macro.Path) { + pathParameterName = lastParam.Name + // path remains as it was defined by the end-developer. + } + } + // + + if pathParameterName == "" { + pathParameterName = fmt.Sprintf("iris_wildcard_path_parameter%d", len(api.routes.routes)) + path = fmt.Sprintf("%s/{%s:path}", path, pathParameterName) + } + + handler := makeServerHandler(pathParameterName, server.ServeHTTPC) + api.Any(path, handler) +} + +func makeServerHandler(givenPathParameter string, handler context.Handler) context.Handler { + return func(ctx *context.Context) { + pathValue := "" + if givenPathParameter == "" { + pathValue = ctx.Params().GetEntryAt(ctx.Params().Len() - 1).ValueRaw.(string) + } else { + pathValue = ctx.Params().Get(givenPathParameter) + } + + apiPath := "/" + pathValue + + r := ctx.Request() + r.URL.Path = apiPath + r.URL.RawPath = apiPath + ctx.Params().Reset() + + handler(ctx) + } +} + func (api *APIBuilder) registerResourceRoute(reqPath string, h context.Handler) *Route { api.Head(reqPath, h) return api.Get(reqPath, h) diff --git a/core/router/party.go b/core/router/party.go index 814fc272..6259cc3a 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -393,6 +393,17 @@ type Party interface { // Connect // Trace Any(registeredPath string, handlers ...context.Handler) []*Route + // HandleServer registers a route for all HTTP methods which forwards the requests to the given server. + // + // Usage: + // + // app.HandleServer("/api/identity/{first:string}/orgs/{second:string}/{p:path}", otherApp) + // + // OR + // + // app.HandleServer("/api/identity", otherApp) + HandleServer(path string, server ServerHandler) + // CreateRoutes returns a list of Party-based Routes. // It does NOT registers the route. Use `Handle, Get...` methods instead. // This method can be used for third-parties Iris helpers packages and tools diff --git a/core/router/router_test.go b/core/router/router_test.go index d1d43ceb..fb50be11 100644 --- a/core/router/router_test.go +++ b/core/router/router_test.go @@ -196,3 +196,24 @@ func TestNewSubdomainPartyRedirectHandler(t *testing.T) { e.GET("/").WithURL("http://testold.mydomain.com/notfound").Expect().Status(iris.StatusNotFound).Body().IsEqual("test 404") e.GET("/").WithURL("http://leveled.testold.mydomain.com").Expect().Status(iris.StatusOK).Body().IsEqual("leveled.testold this can be fired") } + +func TestHandleServer(t *testing.T) { + otherApp := iris.New() + otherApp.Get("/test/me/{first:string}", func(ctx iris.Context) { + ctx.HTML("

Other App: %s

", ctx.Params().Get("first")) + }) + otherApp.Build() + + app := iris.New() + + app.Get("/", func(ctx iris.Context) { + ctx.HTML("

Main App

") + }) + + app.HandleServer("/api/identity/{first:string}/orgs/{second:string}/{p:path}", otherApp) + + e := httptest.New(t, app) + e.GET("/").Expect().Status(iris.StatusOK).Body().IsEqual("

Main App

") + e.GET("/api/identity/first/orgs/second/test/me/kataras").Expect().Status(iris.StatusOK).Body().IsEqual("

Other App: kataras

") + e.GET("/api/identity/first/orgs/second/test/me").Expect().Status(iris.StatusNotFound) +} diff --git a/hero/struct.go b/hero/struct.go index 85a7e0de..b6bedcbf 100644 --- a/hero/struct.go +++ b/hero/struct.go @@ -81,8 +81,9 @@ func makeStruct(structPtr interface{}, c *Container, partyParamsCount int) *Stru singleton := true elem := v.Elem() - // fmt.Printf("makeStruct: bindings length = %d\n", len(bindings)) + // fmt.Printf("Service: %s, Bindings(%d):\n", typ, len(bindings)) for _, b := range bindings { + // fmt.Printf("* " + b.String() + "\n") if b.Dependency.Static { // Fill now. input, err := b.Dependency.Handle(nil, b.Input) diff --git a/iris.go b/iris.go index 2aaadbe2..2b5d4d63 100644 --- a/iris.go +++ b/iris.go @@ -38,7 +38,7 @@ import ( ) // Version is the current version of the Iris Web Framework. -const Version = "12.2.8" +const Version = "12.2.9" // Byte unit helpers. const ( diff --git a/macro/template.go b/macro/template.go index 066af2ac..5154670d 100644 --- a/macro/template.go +++ b/macro/template.go @@ -27,6 +27,8 @@ func (t *Template) IsTrailing() bool { // TemplateParam is the parsed macro parameter's template // they are being used to describe the param's syntax result. type TemplateParam struct { + macro *Macro // keep for reference. + Src string `json:"src"` // the unparsed param'false source // Type is not useful anywhere here but maybe // it's useful on host to decide how to convert the path template to specific router's syntax @@ -117,6 +119,11 @@ func (p *TemplateParam) Eval(paramValue string) (interface{}, bool) { return newValue, true } +// IsMacro reports whether this TemplateParam's underline macro matches the given one. +func (p *TemplateParam) IsMacro(macro *Macro) bool { + return p.macro == macro +} + // Parse takes a full route path and a macro map (macro map contains the macro types with their registered param functions) // and returns a new Template. // It builds all the parameter functions for that template @@ -138,6 +145,8 @@ func Parse(src string, macros Macros) (Template, error) { typEval := m.Evaluator tmplParam := TemplateParam{ + macro: m, + Src: p.Src, Type: p.Type, Name: p.Name,