1
0
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:
Gerasimos (Makis) Maropoulos
2020-04-25 02:30:19 +03:00
parent 0cf5d5a4a3
commit 5d3c96947c
21 changed files with 566 additions and 185 deletions

View File

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

View File

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

View File

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

View File

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

View File

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