mirror of
https://github.com/labstack/echo.git
synced 2025-04-23 12:18:53 +02:00
Implemented rewrite middleware
Signed-off-by: Vishal Rana <vr@labstack.com>
This commit is contained in:
parent
434f4d1ae8
commit
da083ffd0a
@ -33,7 +33,7 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// DefaultBodyDumpConfig is the default Gzip middleware config.
|
// DefaultBodyDumpConfig is the default BodyDump middleware config.
|
||||||
DefaultBodyDumpConfig = BodyDumpConfig{
|
DefaultBodyDumpConfig = BodyDumpConfig{
|
||||||
Skipper: DefaultSkipper,
|
Skipper: DefaultSkipper,
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ type (
|
|||||||
|
|
||||||
// Maximum allowed size for a request body, it can be specified
|
// Maximum allowed size for a request body, it can be specified
|
||||||
// as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P.
|
// as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P.
|
||||||
Limit string `json:"limit"`
|
Limit string `yaml:"limit"`
|
||||||
limit int64
|
limit int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ type (
|
|||||||
|
|
||||||
// Gzip compression level.
|
// Gzip compression level.
|
||||||
// Optional. Default value -1.
|
// Optional. Default value -1.
|
||||||
Level int `json:"level"`
|
Level int `yaml:"level"`
|
||||||
}
|
}
|
||||||
|
|
||||||
gzipResponseWriter struct {
|
gzipResponseWriter struct {
|
||||||
|
@ -16,34 +16,34 @@ type (
|
|||||||
|
|
||||||
// AllowOrigin defines a list of origins that may access the resource.
|
// AllowOrigin defines a list of origins that may access the resource.
|
||||||
// Optional. Default value []string{"*"}.
|
// Optional. Default value []string{"*"}.
|
||||||
AllowOrigins []string `json:"allow_origins"`
|
AllowOrigins []string `yaml:"allow_origins"`
|
||||||
|
|
||||||
// AllowMethods defines a list methods allowed when accessing the resource.
|
// AllowMethods defines a list methods allowed when accessing the resource.
|
||||||
// This is used in response to a preflight request.
|
// This is used in response to a preflight request.
|
||||||
// Optional. Default value DefaultCORSConfig.AllowMethods.
|
// Optional. Default value DefaultCORSConfig.AllowMethods.
|
||||||
AllowMethods []string `json:"allow_methods"`
|
AllowMethods []string `yaml:"allow_methods"`
|
||||||
|
|
||||||
// AllowHeaders defines a list of request headers that can be used when
|
// AllowHeaders defines a list of request headers that can be used when
|
||||||
// making the actual request. This in response to a preflight request.
|
// making the actual request. This in response to a preflight request.
|
||||||
// Optional. Default value []string{}.
|
// Optional. Default value []string{}.
|
||||||
AllowHeaders []string `json:"allow_headers"`
|
AllowHeaders []string `yaml:"allow_headers"`
|
||||||
|
|
||||||
// AllowCredentials indicates whether or not the response to the request
|
// AllowCredentials indicates whether or not the response to the request
|
||||||
// can be exposed when the credentials flag is true. When used as part of
|
// can be exposed when the credentials flag is true. When used as part of
|
||||||
// a response to a preflight request, this indicates whether or not the
|
// a response to a preflight request, this indicates whether or not the
|
||||||
// actual request can be made using credentials.
|
// actual request can be made using credentials.
|
||||||
// Optional. Default value false.
|
// Optional. Default value false.
|
||||||
AllowCredentials bool `json:"allow_credentials"`
|
AllowCredentials bool `yaml:"allow_credentials"`
|
||||||
|
|
||||||
// ExposeHeaders defines a whitelist headers that clients are allowed to
|
// ExposeHeaders defines a whitelist headers that clients are allowed to
|
||||||
// access.
|
// access.
|
||||||
// Optional. Default value []string{}.
|
// Optional. Default value []string{}.
|
||||||
ExposeHeaders []string `json:"expose_headers"`
|
ExposeHeaders []string `yaml:"expose_headers"`
|
||||||
|
|
||||||
// MaxAge indicates how long (in seconds) the results of a preflight request
|
// MaxAge indicates how long (in seconds) the results of a preflight request
|
||||||
// can be cached.
|
// can be cached.
|
||||||
// Optional. Default value 0.
|
// Optional. Default value 0.
|
||||||
MaxAge int `json:"max_age"`
|
MaxAge int `yaml:"max_age"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ type (
|
|||||||
Skipper Skipper
|
Skipper Skipper
|
||||||
|
|
||||||
// TokenLength is the length of the generated token.
|
// TokenLength is the length of the generated token.
|
||||||
TokenLength uint8 `json:"token_length"`
|
TokenLength uint8 `yaml:"token_length"`
|
||||||
// Optional. Default value 32.
|
// Optional. Default value 32.
|
||||||
|
|
||||||
// TokenLookup is a string in the form of "<source>:<key>" that is used
|
// TokenLookup is a string in the form of "<source>:<key>" that is used
|
||||||
@ -28,35 +28,35 @@ type (
|
|||||||
// - "header:<name>"
|
// - "header:<name>"
|
||||||
// - "form:<name>"
|
// - "form:<name>"
|
||||||
// - "query:<name>"
|
// - "query:<name>"
|
||||||
TokenLookup string `json:"token_lookup"`
|
TokenLookup string `yaml:"token_lookup"`
|
||||||
|
|
||||||
// Context key to store generated CSRF token into context.
|
// Context key to store generated CSRF token into context.
|
||||||
// Optional. Default value "csrf".
|
// Optional. Default value "csrf".
|
||||||
ContextKey string `json:"context_key"`
|
ContextKey string `yaml:"context_key"`
|
||||||
|
|
||||||
// Name of the CSRF cookie. This cookie will store CSRF token.
|
// Name of the CSRF cookie. This cookie will store CSRF token.
|
||||||
// Optional. Default value "csrf".
|
// Optional. Default value "csrf".
|
||||||
CookieName string `json:"cookie_name"`
|
CookieName string `yaml:"cookie_name"`
|
||||||
|
|
||||||
// Domain of the CSRF cookie.
|
// Domain of the CSRF cookie.
|
||||||
// Optional. Default value none.
|
// Optional. Default value none.
|
||||||
CookieDomain string `json:"cookie_domain"`
|
CookieDomain string `yaml:"cookie_domain"`
|
||||||
|
|
||||||
// Path of the CSRF cookie.
|
// Path of the CSRF cookie.
|
||||||
// Optional. Default value none.
|
// Optional. Default value none.
|
||||||
CookiePath string `json:"cookie_path"`
|
CookiePath string `yaml:"cookie_path"`
|
||||||
|
|
||||||
// Max age (in seconds) of the CSRF cookie.
|
// Max age (in seconds) of the CSRF cookie.
|
||||||
// Optional. Default value 86400 (24hr).
|
// Optional. Default value 86400 (24hr).
|
||||||
CookieMaxAge int `json:"cookie_max_age"`
|
CookieMaxAge int `yaml:"cookie_max_age"`
|
||||||
|
|
||||||
// Indicates if CSRF cookie is secure.
|
// Indicates if CSRF cookie is secure.
|
||||||
// Optional. Default value false.
|
// Optional. Default value false.
|
||||||
CookieSecure bool `json:"cookie_secure"`
|
CookieSecure bool `yaml:"cookie_secure"`
|
||||||
|
|
||||||
// Indicates if CSRF cookie is HTTP only.
|
// Indicates if CSRF cookie is HTTP only.
|
||||||
// Optional. Default value false.
|
// Optional. Default value false.
|
||||||
CookieHTTPOnly bool `json:"cookie_http_only"`
|
CookieHTTPOnly bool `yaml:"cookie_http_only"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// csrfTokenExtractor defines a function that takes `echo.Context` and returns
|
// csrfTokenExtractor defines a function that takes `echo.Context` and returns
|
||||||
|
@ -21,7 +21,7 @@ type (
|
|||||||
// - "header:<name>"
|
// - "header:<name>"
|
||||||
// - "query:<name>"
|
// - "query:<name>"
|
||||||
// - "form:<name>"
|
// - "form:<name>"
|
||||||
KeyLookup string `json:"key_lookup"`
|
KeyLookup string `yaml:"key_lookup"`
|
||||||
|
|
||||||
// AuthScheme to be used in the Authorization header.
|
// AuthScheme to be used in the Authorization header.
|
||||||
// Optional. Default value "Bearer".
|
// Optional. Default value "Bearer".
|
||||||
|
@ -46,7 +46,7 @@ type (
|
|||||||
// Example "${remote_ip} ${status}"
|
// Example "${remote_ip} ${status}"
|
||||||
//
|
//
|
||||||
// Optional. Default value DefaultLoggerConfig.Format.
|
// Optional. Default value DefaultLoggerConfig.Format.
|
||||||
Format string `json:"format"`
|
Format string `yaml:"format"`
|
||||||
|
|
||||||
// Output is a writer where logs in JSON format are written.
|
// Output is a writer where logs in JSON format are written.
|
||||||
// Optional. Default value os.Stdout.
|
// Optional. Default value os.Stdout.
|
||||||
|
@ -87,7 +87,7 @@ func TestProxy(t *testing.T) {
|
|||||||
|
|
||||||
// Rewrite
|
// Rewrite
|
||||||
e = echo.New()
|
e = echo.New()
|
||||||
e.Pre(ProxyWithConfig(ProxyConfig{
|
e.Use(ProxyWithConfig(ProxyConfig{
|
||||||
Balancer: rrb,
|
Balancer: rrb,
|
||||||
Rewrite: map[string]string{
|
Rewrite: map[string]string{
|
||||||
"/old": "/new",
|
"/old": "/new",
|
||||||
|
@ -15,16 +15,16 @@ type (
|
|||||||
|
|
||||||
// Size of the stack to be printed.
|
// Size of the stack to be printed.
|
||||||
// Optional. Default value 4KB.
|
// Optional. Default value 4KB.
|
||||||
StackSize int `json:"stack_size"`
|
StackSize int `yaml:"stack_size"`
|
||||||
|
|
||||||
// DisableStackAll disables formatting stack traces of all other goroutines
|
// DisableStackAll disables formatting stack traces of all other goroutines
|
||||||
// into buffer after the trace for the current goroutine.
|
// into buffer after the trace for the current goroutine.
|
||||||
// Optional. Default value false.
|
// Optional. Default value false.
|
||||||
DisableStackAll bool `json:"disable_stack_all"`
|
DisableStackAll bool `yaml:"disable_stack_all"`
|
||||||
|
|
||||||
// DisablePrintStack disables printing stack trace.
|
// DisablePrintStack disables printing stack trace.
|
||||||
// Optional. Default value as false.
|
// Optional. Default value as false.
|
||||||
DisablePrintStack bool `json:"disable_print_stack"`
|
DisablePrintStack bool `yaml:"disable_print_stack"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ type (
|
|||||||
|
|
||||||
// Status code to be used when redirecting the request.
|
// Status code to be used when redirecting the request.
|
||||||
// Optional. Default value http.StatusMovedPermanently.
|
// Optional. Default value http.StatusMovedPermanently.
|
||||||
Code int `json:"code"`
|
Code int `yaml:"code"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
83
middleware/rewrite.go
Normal file
83
middleware/rewrite.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/labstack/echo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// RewriteConfig defines the config for Rewrite middleware.
|
||||||
|
RewriteConfig struct {
|
||||||
|
// Skipper defines a function to skip middleware.
|
||||||
|
Skipper Skipper
|
||||||
|
|
||||||
|
// Rules defines the URL path rewrite rules. The values captured in asterisk can be
|
||||||
|
// retrieved by index e.g. $1, $2 and so on.
|
||||||
|
// Examples:
|
||||||
|
// "/old": "/new",
|
||||||
|
// "/api/*": "/$1",
|
||||||
|
// "/js/*": "/public/javascripts/$1",
|
||||||
|
// "/users/*/orders/*": "/user/$1/order/$2",
|
||||||
|
// Required.
|
||||||
|
Rules map[string]string `yaml:"rules"`
|
||||||
|
|
||||||
|
rulesRegex map[*regexp.Regexp]string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultRewriteConfig is the default Rewrite middleware config.
|
||||||
|
DefaultRewriteConfig = RewriteConfig{
|
||||||
|
Skipper: DefaultSkipper,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rewrite returns a Rewrite middleware.
|
||||||
|
//
|
||||||
|
// Rewrite middleware rewrites the URL path based on the provided rules.
|
||||||
|
func Rewrite(rules map[string]string) echo.MiddlewareFunc {
|
||||||
|
c := DefaultRewriteConfig
|
||||||
|
c.Rules = rules
|
||||||
|
return RewriteWithConfig(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RewriteWithConfig returns a Rewrite middleware with config.
|
||||||
|
// See: `Rewrite()`.
|
||||||
|
func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc {
|
||||||
|
// Defaults
|
||||||
|
if config.Rules == nil {
|
||||||
|
panic("echo: rewrite middleware requires url path rewrite rules")
|
||||||
|
}
|
||||||
|
if config.Skipper == nil {
|
||||||
|
config.Skipper = DefaultBodyDumpConfig.Skipper
|
||||||
|
}
|
||||||
|
config.rulesRegex = map[*regexp.Regexp]string{}
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
for k, v := range config.Rules {
|
||||||
|
k = strings.Replace(k, "*", "(\\S*)", -1)
|
||||||
|
config.rulesRegex[regexp.MustCompile(k)] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) (err error) {
|
||||||
|
if config.Skipper(c) {
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := c.Request()
|
||||||
|
|
||||||
|
// Rewrite
|
||||||
|
for k, v := range config.rulesRegex {
|
||||||
|
replacer := captureTokens(k, req.URL.Path)
|
||||||
|
if replacer != nil {
|
||||||
|
req.URL.Path = replacer.Replace(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
middleware/rewrite_test.go
Normal file
35
middleware/rewrite_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/labstack/echo"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRewrite(t *testing.T) {
|
||||||
|
e := echo.New()
|
||||||
|
e.Use(RewriteWithConfig(RewriteConfig{
|
||||||
|
Rules: map[string]string{
|
||||||
|
"/old": "/new",
|
||||||
|
"/api/*": "/$1",
|
||||||
|
"/js/*": "/public/javascripts/$1",
|
||||||
|
"/users/*/orders/*": "/user/$1/order/$2",
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
req := httptest.NewRequest(echo.GET, "/", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req.URL.Path = "/api/users"
|
||||||
|
e.ServeHTTP(rec, req)
|
||||||
|
assert.Equal(t, "/users", req.URL.Path)
|
||||||
|
req.URL.Path = "/js/main.js"
|
||||||
|
e.ServeHTTP(rec, req)
|
||||||
|
assert.Equal(t, "/public/javascripts/main.js", req.URL.Path)
|
||||||
|
req.URL.Path = "/old"
|
||||||
|
e.ServeHTTP(rec, req)
|
||||||
|
assert.Equal(t, "/new", req.URL.Path)
|
||||||
|
req.URL.Path = "/users/jack/orders/1"
|
||||||
|
e.ServeHTTP(rec, req)
|
||||||
|
assert.Equal(t, "/user/jack/order/1", req.URL.Path)
|
||||||
|
}
|
@ -15,12 +15,12 @@ type (
|
|||||||
// XSSProtection provides protection against cross-site scripting attack (XSS)
|
// XSSProtection provides protection against cross-site scripting attack (XSS)
|
||||||
// by setting the `X-XSS-Protection` header.
|
// by setting the `X-XSS-Protection` header.
|
||||||
// Optional. Default value "1; mode=block".
|
// Optional. Default value "1; mode=block".
|
||||||
XSSProtection string `json:"xss_protection"`
|
XSSProtection string `yaml:"xss_protection"`
|
||||||
|
|
||||||
// ContentTypeNosniff provides protection against overriding Content-Type
|
// ContentTypeNosniff provides protection against overriding Content-Type
|
||||||
// header by setting the `X-Content-Type-Options` header.
|
// header by setting the `X-Content-Type-Options` header.
|
||||||
// Optional. Default value "nosniff".
|
// Optional. Default value "nosniff".
|
||||||
ContentTypeNosniff string `json:"content_type_nosniff"`
|
ContentTypeNosniff string `yaml:"content_type_nosniff"`
|
||||||
|
|
||||||
// XFrameOptions can be used to indicate whether or not a browser should
|
// XFrameOptions can be used to indicate whether or not a browser should
|
||||||
// be allowed to render a page in a <frame>, <iframe> or <object> .
|
// be allowed to render a page in a <frame>, <iframe> or <object> .
|
||||||
@ -32,27 +32,27 @@ type (
|
|||||||
// - "SAMEORIGIN" - The page can only be displayed in a frame on the same origin as the page itself.
|
// - "SAMEORIGIN" - The page can only be displayed in a frame on the same origin as the page itself.
|
||||||
// - "DENY" - The page cannot be displayed in a frame, regardless of the site attempting to do so.
|
// - "DENY" - The page cannot be displayed in a frame, regardless of the site attempting to do so.
|
||||||
// - "ALLOW-FROM uri" - The page can only be displayed in a frame on the specified origin.
|
// - "ALLOW-FROM uri" - The page can only be displayed in a frame on the specified origin.
|
||||||
XFrameOptions string `json:"x_frame_options"`
|
XFrameOptions string `yaml:"x_frame_options"`
|
||||||
|
|
||||||
// HSTSMaxAge sets the `Strict-Transport-Security` header to indicate how
|
// HSTSMaxAge sets the `Strict-Transport-Security` header to indicate how
|
||||||
// long (in seconds) browsers should remember that this site is only to
|
// long (in seconds) browsers should remember that this site is only to
|
||||||
// be accessed using HTTPS. This reduces your exposure to some SSL-stripping
|
// be accessed using HTTPS. This reduces your exposure to some SSL-stripping
|
||||||
// man-in-the-middle (MITM) attacks.
|
// man-in-the-middle (MITM) attacks.
|
||||||
// Optional. Default value 0.
|
// Optional. Default value 0.
|
||||||
HSTSMaxAge int `json:"hsts_max_age"`
|
HSTSMaxAge int `yaml:"hsts_max_age"`
|
||||||
|
|
||||||
// HSTSExcludeSubdomains won't include subdomains tag in the `Strict Transport Security`
|
// HSTSExcludeSubdomains won't include subdomains tag in the `Strict Transport Security`
|
||||||
// header, excluding all subdomains from security policy. It has no effect
|
// header, excluding all subdomains from security policy. It has no effect
|
||||||
// unless HSTSMaxAge is set to a non-zero value.
|
// unless HSTSMaxAge is set to a non-zero value.
|
||||||
// Optional. Default value false.
|
// Optional. Default value false.
|
||||||
HSTSExcludeSubdomains bool `json:"hsts_exclude_subdomains"`
|
HSTSExcludeSubdomains bool `yaml:"hsts_exclude_subdomains"`
|
||||||
|
|
||||||
// ContentSecurityPolicy sets the `Content-Security-Policy` header providing
|
// ContentSecurityPolicy sets the `Content-Security-Policy` header providing
|
||||||
// security against cross-site scripting (XSS), clickjacking and other code
|
// security against cross-site scripting (XSS), clickjacking and other code
|
||||||
// injection attacks resulting from execution of malicious content in the
|
// injection attacks resulting from execution of malicious content in the
|
||||||
// trusted web page context.
|
// trusted web page context.
|
||||||
// Optional. Default value "".
|
// Optional. Default value "".
|
||||||
ContentSecurityPolicy string `json:"content_security_policy"`
|
ContentSecurityPolicy string `yaml:"content_security_policy"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ type (
|
|||||||
|
|
||||||
// Status code to be used when redirecting the request.
|
// Status code to be used when redirecting the request.
|
||||||
// Optional, but when provided the request is redirected using this code.
|
// Optional, but when provided the request is redirected using this code.
|
||||||
RedirectCode int `json:"redirect_code"`
|
RedirectCode int `yaml:"redirect_code"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,20 +19,20 @@ type (
|
|||||||
|
|
||||||
// Root directory from where the static content is served.
|
// Root directory from where the static content is served.
|
||||||
// Required.
|
// Required.
|
||||||
Root string `json:"root"`
|
Root string `yaml:"root"`
|
||||||
|
|
||||||
// Index file for serving a directory.
|
// Index file for serving a directory.
|
||||||
// Optional. Default value "index.html".
|
// Optional. Default value "index.html".
|
||||||
Index string `json:"index"`
|
Index string `yaml:"index"`
|
||||||
|
|
||||||
// Enable HTML5 mode by forwarding all not-found requests to root so that
|
// Enable HTML5 mode by forwarding all not-found requests to root so that
|
||||||
// SPA (single-page application) can handle the routing.
|
// SPA (single-page application) can handle the routing.
|
||||||
// Optional. Default value false.
|
// Optional. Default value false.
|
||||||
HTML5 bool `json:"html5"`
|
HTML5 bool `yaml:"html5"`
|
||||||
|
|
||||||
// Enable directory browsing.
|
// Enable directory browsing.
|
||||||
// Optional. Default value false.
|
// Optional. Default value false.
|
||||||
Browse bool `json:"browse"`
|
Browse bool `yaml:"browse"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user