mirror of
https://github.com/kataras/iris.git
synced 2026-01-10 13:35:59 +00:00
New: gRPC MVC features, new WithLowercaseRouting option and add some new context methods
read HISTORY.md Former-commit-id: 30a16cceb11f754aa32923058abeda1e736350e7
This commit is contained in:
@@ -163,20 +163,14 @@ var _ RoutesProvider = (*APIBuilder)(nil) // passed to the default request handl
|
||||
// NewAPIBuilder creates & returns a new builder
|
||||
// which is responsible to build the API and the router handler.
|
||||
func NewAPIBuilder() *APIBuilder {
|
||||
api := &APIBuilder{
|
||||
return &APIBuilder{
|
||||
macros: macro.Defaults,
|
||||
errorCodeHandlers: defaultErrorCodeHandlers(),
|
||||
errors: errgroup.New("API Builder"),
|
||||
relativePath: "/",
|
||||
routes: new(repository),
|
||||
apiBuilderDI: &APIContainer{Container: hero.New()},
|
||||
}
|
||||
|
||||
api.apiBuilderDI = &APIContainer{
|
||||
Self: api,
|
||||
Container: hero.New(),
|
||||
}
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
// ConfigureContainer accepts one or more functions that can be used
|
||||
@@ -187,12 +181,14 @@ func NewAPIBuilder() *APIBuilder {
|
||||
//
|
||||
// It returns the same `APIBuilder` featured with Dependency Injection.
|
||||
func (api *APIBuilder) ConfigureContainer(builder ...func(*APIContainer)) *APIContainer {
|
||||
for _, b := range builder {
|
||||
if b == nil {
|
||||
continue
|
||||
}
|
||||
if api.apiBuilderDI.Self == nil {
|
||||
api.apiBuilderDI.Self = api
|
||||
}
|
||||
|
||||
b(api.apiBuilderDI)
|
||||
for _, b := range builder {
|
||||
if b != nil {
|
||||
b(api.apiBuilderDI)
|
||||
}
|
||||
}
|
||||
|
||||
return api.apiBuilderDI
|
||||
@@ -463,7 +459,7 @@ func (api *APIBuilder) CreateRoutes(methods []string, relativePath string, handl
|
||||
subdomain, path := splitSubdomainAndPath(fullpath)
|
||||
|
||||
// if allowMethods are empty, then simply register with the passed, main, method.
|
||||
methods = removeDuplString(append(api.allowMethods, methods...))
|
||||
methods = removeDuplicates(append(api.allowMethods, methods...))
|
||||
|
||||
routes := make([]*Route, len(methods))
|
||||
|
||||
@@ -487,7 +483,7 @@ func (api *APIBuilder) CreateRoutes(methods []string, relativePath string, handl
|
||||
return routes
|
||||
}
|
||||
|
||||
func removeDuplString(elements []string) (result []string) {
|
||||
func removeDuplicates(elements []string) (result []string) {
|
||||
seen := make(map[string]struct{})
|
||||
|
||||
for v := range elements {
|
||||
@@ -551,15 +547,11 @@ func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) P
|
||||
allowMethods: allowMethods,
|
||||
handlerExecutionRules: api.handlerExecutionRules,
|
||||
routeRegisterRule: api.routeRegisterRule,
|
||||
}
|
||||
|
||||
// attach a new Container with correct dynamic path parameter start index for input arguments
|
||||
// based on the fullpath.
|
||||
childContainer := api.apiBuilderDI.Container.Clone()
|
||||
|
||||
childAPI.apiBuilderDI = &APIContainer{
|
||||
Self: childAPI,
|
||||
Container: childContainer,
|
||||
apiBuilderDI: &APIContainer{
|
||||
// attach a new Container with correct dynamic path parameter start index for input arguments
|
||||
// based on the fullpath.
|
||||
Container: api.apiBuilderDI.Container.Clone(),
|
||||
},
|
||||
}
|
||||
|
||||
return childAPI
|
||||
|
||||
@@ -84,7 +84,7 @@ func BenchmarkAPIBuilder(b *testing.B) {
|
||||
paths := genPaths(routesLength, 15, 42)
|
||||
|
||||
api := NewAPIBuilder()
|
||||
requestHandler := NewDefaultHandler()
|
||||
requestHandler := NewDefaultHandler(nil)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
// By-default is the router algorithm.
|
||||
type RequestHandler interface {
|
||||
// HandleRequest should handle the request based on the Context.
|
||||
HandleRequest(context.Context)
|
||||
HandleRequest(ctx context.Context)
|
||||
// Build should builds the handler, it's being called on router's BuildRouter.
|
||||
Build(provider RoutesProvider) error
|
||||
// RouteExists reports whether a particular route exists.
|
||||
@@ -25,8 +25,9 @@ type RequestHandler interface {
|
||||
}
|
||||
|
||||
type routerHandler struct {
|
||||
trees []*trie
|
||||
hosts bool // true if at least one route contains a Subdomain.
|
||||
trees []*trie
|
||||
hosts bool // true if at least one route contains a Subdomain.
|
||||
config context.ConfigurationReadOnly
|
||||
}
|
||||
|
||||
var _ RequestHandler = &routerHandler{}
|
||||
@@ -67,9 +68,10 @@ func (h *routerHandler) AddRoute(r *Route) error {
|
||||
|
||||
// NewDefaultHandler returns the handler which is responsible
|
||||
// to map the request with a route (aka mux implementation).
|
||||
func NewDefaultHandler() RequestHandler {
|
||||
h := &routerHandler{}
|
||||
return h
|
||||
func NewDefaultHandler(config context.ConfigurationReadOnly) RequestHandler {
|
||||
return &routerHandler{
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// RoutesProvider should be implemented by
|
||||
@@ -128,6 +130,11 @@ func (h *routerHandler) Build(provider RoutesProvider) error {
|
||||
})
|
||||
|
||||
for _, r := range registeredRoutes {
|
||||
if h.config != nil && h.config.GetForceLowercaseRouting() {
|
||||
// only in that state, keep everyting else as end-developer registered.
|
||||
r.Path = strings.ToLower(r.Path)
|
||||
}
|
||||
|
||||
if r.Subdomain != "" {
|
||||
h.hosts = true
|
||||
}
|
||||
@@ -188,20 +195,21 @@ func bindMultiParamTypesHandler(top *Route, r *Route) {
|
||||
func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||
method := ctx.Method()
|
||||
path := ctx.Path()
|
||||
if !ctx.Application().ConfigurationReadOnly().GetDisablePathCorrection() {
|
||||
config := h.config // ctx.Application().GetConfigurationReadOnly()
|
||||
|
||||
if !config.GetDisablePathCorrection() {
|
||||
if len(path) > 1 && strings.HasSuffix(path, "/") {
|
||||
// Remove trailing slash and client-permanent rule for redirection,
|
||||
// if confgiuration allows that and path has an extra slash.
|
||||
|
||||
// update the new path and redirect.
|
||||
r := ctx.Request()
|
||||
u := ctx.Request().URL
|
||||
// use Trim to ensure there is no open redirect due to two leading slashes
|
||||
path = "/" + strings.Trim(path, "/")
|
||||
|
||||
r.URL.Path = path
|
||||
if !ctx.Application().ConfigurationReadOnly().GetDisablePathCorrectionRedirection() {
|
||||
u.Path = path
|
||||
if !config.GetDisablePathCorrectionRedirection() {
|
||||
// do redirect, else continue with the modified path without the last "/".
|
||||
url := r.URL.String()
|
||||
url := u.String()
|
||||
|
||||
// Fixes https://github.com/kataras/iris/issues/921
|
||||
// This is caused for security reasons, imagine a payment shop,
|
||||
@@ -238,7 +246,7 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||
// localhost -> invalid
|
||||
// sub.mydomain.com -> valid
|
||||
// sub.localhost -> valid
|
||||
serverHost := ctx.Application().ConfigurationReadOnly().GetVHost()
|
||||
serverHost := config.GetVHost()
|
||||
if serverHost == requestHost {
|
||||
continue // it's not a subdomain, it's a full domain (with .com...)
|
||||
}
|
||||
@@ -266,7 +274,7 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||
break
|
||||
}
|
||||
|
||||
if ctx.Application().ConfigurationReadOnly().GetFireMethodNotAllowed() {
|
||||
if config.GetFireMethodNotAllowed() {
|
||||
for i := range h.trees {
|
||||
t := h.trees[i]
|
||||
// if `Configuration#FireMethodNotAllowed` is kept as defaulted(false) then this function will not
|
||||
|
||||
@@ -14,11 +14,12 @@ import (
|
||||
// If any of the following fields are changed then the
|
||||
// caller should Refresh the router.
|
||||
type Route struct {
|
||||
Name string `json:"name"` // "userRoute"
|
||||
Method string `json:"method"` // "GET"
|
||||
methodBckp string // if Method changed to something else (which is possible at runtime as well, via RefreshRouter) then this field will be filled with the old one.
|
||||
Subdomain string `json:"subdomain"` // "admin."
|
||||
tmpl macro.Template // Tmpl().Src: "/api/user/{id:uint64}"
|
||||
Name string `json:"name"` // "userRoute"
|
||||
Description string `json:"description"` // "lists a user"
|
||||
Method string `json:"method"` // "GET"
|
||||
methodBckp string // if Method changed to something else (which is possible at runtime as well, via RefreshRouter) then this field will be filled with the old one.
|
||||
Subdomain string `json:"subdomain"` // "admin."
|
||||
tmpl macro.Template // Tmpl().Src: "/api/user/{id:uint64}"
|
||||
// temp storage, they're appended to the Handlers on build.
|
||||
// Execution happens before Handlers, can be empty.
|
||||
beginHandlers context.Handlers
|
||||
@@ -322,7 +323,11 @@ func (r *Route) Trace() string {
|
||||
if r.Subdomain != "" {
|
||||
printfmt += fmt.Sprintf(" %s", r.Subdomain)
|
||||
}
|
||||
printfmt += fmt.Sprintf(" %s ", r.Tmpl().Src)
|
||||
printfmt += fmt.Sprintf(" %s", r.Tmpl().Src)
|
||||
|
||||
if r.Description != "" {
|
||||
printfmt += fmt.Sprintf(" (%s)", r.Description)
|
||||
}
|
||||
|
||||
mainHandlerName := r.MainHandlerName
|
||||
if !strings.HasSuffix(mainHandlerName, ")") {
|
||||
@@ -330,9 +335,9 @@ func (r *Route) Trace() string {
|
||||
}
|
||||
|
||||
if l := r.RegisteredHandlersLen(); l > 1 {
|
||||
printfmt += fmt.Sprintf("-> %s and %d more", mainHandlerName, l-1)
|
||||
printfmt += fmt.Sprintf(" -> %s and %d more", mainHandlerName, l-1)
|
||||
} else {
|
||||
printfmt += fmt.Sprintf("-> %s", mainHandlerName)
|
||||
printfmt += fmt.Sprintf(" -> %s", mainHandlerName)
|
||||
}
|
||||
|
||||
// printfmt := fmt.Sprintf("%s: %s >> %s", r.Method, r.Subdomain+r.Tmpl().Src, r.MainHandlerName)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
@@ -39,3 +41,34 @@ func TestRouteExists(t *testing.T) {
|
||||
// run the tests
|
||||
httptest.New(t, app, httptest.Debug(false)).Request("GET", "/route-test").Expect().Status(iris.StatusOK)
|
||||
}
|
||||
|
||||
func TestLowercaseRouting(t *testing.T) {
|
||||
app := iris.New()
|
||||
app.WrapRouter(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
// test bottom to begin wrapper, the last ones should execute first.
|
||||
// The ones that are registered at `Build` state, after this `WrapRouter` call.
|
||||
// So path should be already lowecased.
|
||||
if expected, got := strings.ToLower(r.URL.Path), r.URL.Path; expected != got {
|
||||
t.Fatalf("expected path: %s but got: %s", expected, got)
|
||||
}
|
||||
next(w, r)
|
||||
})
|
||||
|
||||
h := func(ctx iris.Context) { ctx.WriteString(ctx.Path()) }
|
||||
|
||||
// Register routes.
|
||||
tests := []string{"/", "/lowercase", "/UPPERCASE", "/Title", "/m1xEd2"}
|
||||
for _, tt := range tests {
|
||||
app.Get(tt, h)
|
||||
}
|
||||
|
||||
app.Configure(iris.WithLowercaseRouting)
|
||||
// Test routes.
|
||||
e := httptest.New(t, app)
|
||||
for _, tt := range tests {
|
||||
s := strings.ToLower(tt)
|
||||
e.GET(tt).Expect().Status(httptest.StatusOK).Body().Equal(s)
|
||||
e.GET(s).Expect().Status(httptest.StatusOK).Body().Equal(s)
|
||||
e.GET(strings.ToUpper(tt)).Expect().Status(httptest.StatusOK).Body().Equal(s)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user