Request Rewrite in Gin
Let’s say you want to rewrite an incoming request to a gin server because maybe because you want to transparently handle a number of equivalent prefixes but only want to define one set of canonical routes. It’s a common enough problem, Apache installs typically have the mod_rewrite module installed by default. So we know what we want to accomplish, how do we do it with gin?
Redirection Methods
Easy option is to serve a redirect
func (c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "/canonical/path")
}
That might be all you need and is pretty easy to do. However, you may by required to handle those optional paths as if they were correct. No redirect response, just handle the route as if it was made using another request path. For that you can use the HandleContext
function in *gin.Engine
func rewritePath(root *gin.Engine) gin.HandlerFunc {
return func (c *gin.Context) {
c.Request.URL.Path = "/canonical/path"
root.HandleContext(c)
}
}
The documentation warns that you can busy loop yourself to death if you manage to rewrite the path to match the same handler again, so be careful with your rewrite rules.
Response behavior
There is one thing I left out of the example above. If you used the code above as is, it will work but it may write the response buffer twice, essentially concatenating the response body with a copy of itself. This can happen if the response is big enough that the HTTP server decides to chunk the response.
What’s going on is that there are two opportunities for the response buffer to be written to when using the HandleContext
method, caller’s view of the context and callee’s view of the context. HandleContext
basically starts the routing process over from the beginning, which means all the same HandlerChain
operations in the router happen again, that includes writing the response to the ResponseWriter
. HandleContext
doesn’t cancel the callers HandlerChain
, so after HandleContext
returns it proceeds with its own chain. That, in turn, will use the same context object as the callee, which will then write the same response from that context if given the chance. For small responses, the response may be flushed immediately upon being written by the callee, preventing the caller from writing to the response buffer as well. On larger responses, especially chunked ones, the caller chain has an opportunity to write to the response stream before it is closed.
How do we prevent this behavior? We abort the caller request chain using c.Abort()
.
func rewritePath(root *gin.Engine) gin.HandlerFunc {
return func (c *gin.Context) {
c.Request.URL.Path = "/canonical/path"
root.HandleContext(c)
c.Abort()
}
}
This tells the caller chain to just stop here and do not proceed with actions after the current handler. That includes writing the response. We assume that the callee context has written all the response that we needed.
That’s all that you need to handle rewrites with the gin web framework. There aren’t really any limitations on how you can rewrite the path, just make sure to not catch yourself in an infinite loop and Abort your context if you don’t want it to attempt to write to the response buffer multiple times.
Extended Example
Here is a complete example of a rewrite that removes a prefix from a route before returning a response.
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
engine := gin.Default()
engine.GET("/echo/:value", getValue)
engine.GET("/extra-prefix/*rest", rewritePath(engine))
}
func getValue (c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"echo": c.Param("value")})
}
func rewritePath(e *gin.Engine) {
return func (c *gin.Context) {
c.Request.URL.Path = "/" + c.Param("rest")
root.HandleContext(c)
c.Abort()
}
}
last-modified: 2024-03-17 21:37 CDT