Fasthttp + fasthttprouter, trying to write middleware - go

I'm currently trying to write some middleware to work with fasthttp and fasthttprouter. And I'm stuck.
func jwt(h fasthttprouter.Handle) fasthttprouter.Handle {
myfunc := func(ctx *fasthttp.RequestCtx, _ fasthttprouter.Params) {
fmt.Println(string(ctx.Request.Header.Cookie("Authorization")))
}
return myfunc
}
How do I run the actual handler now? I feel like i'm missing something very simple.
I've read through this blog post: Middleware in Golang. But i'm lost.
Any ideas?
Regards

for example, let us create a middleware function that will handle CORS using:
github.com/buaazp/fasthttprouter and github.com/valyala/fasthttp
var (
corsAllowHeaders = "authorization"
corsAllowMethods = "HEAD,GET,POST,PUT,DELETE,OPTIONS"
corsAllowOrigin = "*"
corsAllowCredentials = "true"
)
func CORS(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
ctx.Response.Header.Set("Access-Control-Allow-Credentials", corsAllowCredentials)
ctx.Response.Header.Set("Access-Control-Allow-Headers", corsAllowHeaders)
ctx.Response.Header.Set("Access-Control-Allow-Methods", corsAllowMethods)
ctx.Response.Header.Set("Access-Control-Allow-Origin", corsAllowOrigin)
next(ctx)
}
}
Now we chain this middleware function on our Index handler and register it on the router.
func Index(ctx *fasthttp.RequestCtx) {
fmt.Fprint(ctx, "some-api")
}
func main() {
router := fasthttprouter.New()
router.GET("/", Index)
if err := fasthttp.ListenAndServe(":8181", CORS(router.Handler)); err != nil {
log.Fatalf("Error in ListenAndServe: %s", err)
}
}

Example of auth middleware for fasthttp & fasthttprouter (new versions)
type Middleware func(h fasthttp.RequestHandler) fasthttp.RequestHandler
type AuthFunc func(ctx *fasthttp.RequestCtx) bool
func NewAuthMiddleware(authFunc AuthFunc) Middleware {
return func(h fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
result, err: = authFunc(ctx)
if result {
h(ctx)
} else {
ctx.Response.SetStatusCode(fasthttp.StatusUnauthorized)
}
}
}
}
func AuthCheck(ctx *fasthttp.RequestCtx)(bool, error) {
return false; // for example ;)
}
// router
authMiddleware: = middleware.NewAuthMiddleware(security.AuthCheck)
...
router.GET("/protected", authMiddleware(handlers.ProtectedHandler))

Related

Gin context variable overrridden with parallel REST api requests

go version: 1.19
gin version (or commit ref): 1.8.1
operating system: Ubuntu
I have a saas project which is based upon Rest APIs. All apis are developed in GO using gin package. When the user logs in then I set current user details in the request context so that I can access these details furthere to display some data. However I had a case in which 2 requests hits in parallel & the context values for the 1st request are override with the context values in the 2nd request. Due to this, my data is displaying wrong.
package main
import (
"fmt"
"strings"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
)
func main() {
g := gin.Default()
g.Use(ParseJWTToken)
g.GET("/hello/:name", hello)
g.Run(":9000")
}
func hello(c *gin.Context) {
c.Keys = make(map[string]interface{})
c.Keys["current_user_id"] = 10
c.Keys["current_user_name"] = c.Param("name")
fmt.Println(c.Keys)
c.String(200, "Hello %s", c.Param("name"))
}
var role, userName string
var userId float64
func ParseJWTToken(c *gin.Context) {
merchantDatabase := make(map[string]interface{})
if values, _ := c.Request.Header["Authorization"]; len(values) > 0 {
bearer := strings.Split(c.Request.Header["Authorization"][0], "Bearer")
bearerToken := strings.TrimSpace(bearer[1])
var userAgent string
var userAgentCheck bool
if values, _ := c.Request.Header["User-Agent"]; len(values) > 0 {
userAgent = values[0]
}
_ = config.InitKeys()
token, err := jwt.Parse(bearerToken, func(token *jwt.Token) (interface{}, error) {
return config.SignKey, nil
})
if err != nil {
c.Abort()
return
}
if !token.Valid {
c.Abort()
return
}
if len(token.Claims.(jwt.MapClaims)) > 0 {
for key, claim := range token.Claims.(jwt.MapClaims) {
if key == "user_agent" {
if claim == userAgent {
userAgentCheck = true
}
}
if key == "role" {
role = claim.(string)
}
if key == "id" {
userId = claim.(float64)
}
if key == "name" {
userName = claim.(string)
}
}
}
merchantDatabase["userid"] = userId
merchantDatabase["role"] = role
merchantDatabase["username"] = userName
c.Keys = merchantDatabase
if userAgentCheck {
c.Next()
} else {
c.Abort()
return
}
} else {
c.Abort()
return
}
}
This issue is not produced every time for parallel requests.
How can I fix that ?
I have used global variables for the details that were overridden. Declaring these inside the middleware fixed the issue. Find complete thread here: https://github.com/gin-gonic/gin/issues/3437

Log request id in golang logs

I am working on an existing application which is written in Go using framework such as gin, middleware. This application uses https://pkg.go.dev/log for logging.
I am trying to add a request id to the log for the API call trace.
main.go
// Creates a router without any middleware by default
r := gin.New()
r.Use(middleware.RequestID())
middleware.go
func (m *Middleware) CheckApiToken(allowWithoutAccessKey bool, validateTonce ...bool) gin.HandlerFunc {
return func(c *gin.Context) {
// Validate
.....
.....
logger.InitializeContext(c)
c.Next()
}
}
}
//RequestID is a middleware that injects a 'RequestID' into the context and header of each request.
func (m *Middleware) RequestID() gin.HandlerFunc {
return func(c *gin.Context) {
xRequestID := uuid.NewV4().String()
c.Request.Header.Set(logger.XRequestIDKey, xRequestID)
fmt.Printf("[GIN-debug] %s [%s] - \"%s %s\"\n", time.Now().Format(time.RFC3339), xRequestID, c.Request.Method, c.Request.URL.Path)
c.Next()
}
}
logger.go
const (
XRequestIDKey = "X-Request-ID"
)
var (
infoLogger *log.Logger
errorLogger *log.Logger
context *gin.Context
)
func init() {
infoLogger = log.New(os.Stdout, "", 0)
errorLogger = log.New(os.Stderr, "", 0)
}
// InitializeContext initialize golbal gin context to logger
func InitializeContext(c *gin.Context) {
context = c
}
//Check if the request id present in the context.
func getRequestID() interface{} {
if context != nil {
if context.Request != nil {
requestID := context.Request.Header.Get(XRequestIDKey)
if requestID != "" {
return requestID
}
}
}
return ""
}
func Info(entry Input) {
infoLogger.Println(getJSON(getRequestID(), msg))
}
This does not work in multi-threaded environment. How do I fix this solution to fix this in multi-threaded environment.
You cannot save the context in a global variable. Context is by definition local to that execution, and at any given moment, there will be multiple of them.
You can store the generated ID in the gin context:
func (m *Middleware) RequestID() gin.HandlerFunc {
return func(c *gin.Context) {
xRequestID := uuid.NewV4().String()
c.Set("requestId",xRequestID)
fmt.Printf("[GIN-debug] %s [%s] - \"%s %s\"\n", time.Now().Format(time.RFC3339), xRequestID, c.Request.Method, c.Request.URL.Path)
c.Next()
}
}
Then you can use the ID stored in the context with the custom log formatter:
router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
return fmt.Sprintf("%s ...",
param.Keys["requestId"],
...
)
}))
Or if you need to use a different logging library, you can write a wrapper:
func LogInfo(ctx *gin.Context,msg string) {
id:=ctx.Get("requestId")
// Log msg with ID
}
Many log libraries offer methods to get a logger with some parameters already set. For instance, if you use zerolog:
logger:=log.Info().With().Str("requestId",ctx.Get("requestId")).Logger()
logger.Info().Msg("This log msg will contain the request id")

go-echo get POST body

I have an audit log middleware which trying to log details of each grpc function call. Currently I can get request method and url. But I'm not able to get the values in Post body.
Also this middleware is use to log multiple functions which contains different post body. Is this possible?
This is what I have now:
package main
func main() {
e := echo.New()
e.Use(grpcGatewayMiddleware(mux, func(c echo.Context) bool {
return strings.HasPrefix(c.Path(), "/skip")
}))
e.Use(logMiddleware)
...
}
func Middleware(config auth.Config) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) (err error) {
message := Message {
Method: c.Request().Method,
Url: c.Request().RequestURI,
Prams: c.Request().Form.Encode(),
}
log(message)
}
}
}
func grpcGatewayMiddleware(mux *runtime.ServeMux, skipper middleware.Skipper) echo.MiddlewareFunc {
if skipper == nil {
skipper = middleware.DefaultSkipper
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if skipper(c) {
return next(c)
}
mux.ServeHTTP(c.Response(), c.Request())
return next(c)
}
}
}

Set the root filesystem of http.FileServer based on a cookie in the request

router.PathPrefix("/test/").Handler(http.StripPrefix("/test/", http.FileServer(http.Dir("/assets/"))))
In this example, the root directory of the file server is set to /assets/. My goal is to set this root directory based on the cookie in the HTTP request. I know I am able to do something like this:
type AlternativeFileServer struct { }
func AlternativeFileServerFactory() http.Handler {
return AlternativeFileServer{}
}
func (aFs AlternativeFileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
cookie, err := GetCookie(r)
if err != nil {
// handle error
}
var rootDirectory string
if cookie == "x" {
rootDirectory = "assets"
} else {
rootDirectory = "alternative"
}
path := filepath.join(rootDirectory, r.URL.Path) + ".png"
http.ServeFile(path)
}
func main() {
....
router.PathPrefix("/test/").Handler(http.StripPrefix("/test/", AlternativeFileServerFactory())
}
But I was hoping there was a better alternative where I could wrap the http.FileServer directly and dynamically set its root directory.
Is that possible?
One approach based on the comment thread:
type AltFileServer struct {
assertsFS http.Handler
altFS http.Handler
}
func NewAltFileServer() http.Handler {
return &AlternativeFileServer{
assetsFS: http.FileServer("assets"),
altFS: http.FileServer("alternative"),
}
}
func (fs *AltFileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
cookie, err := GetCookie(r)
if err != nil {
// handle error
}
if cookie == "x" {
fs.assetsFS.ServeHTTP(w, r)
return
}
fs.altFS.ServeHTTP(w, r)
}
func main() {
....
router.PathPrefix("/test/").Handler(http.StripPrefix("/test/", NewAltFileServer())
}

passing variable from middleware to end function

im using a middleware (CheckToken) to check a JWT and get the custom claim (Id) (it will be the id of the user on my DB) but i need to pass it to campaign.Attack (so i can know who is the user who is doing the "attack") but i cant find out a way to do it.
i tried to pass it as a parameter in next(w, req, claim.id) in token.go but i would need to touch the http.HandlerFunc function so this isnt a valid option.
any idea about how to pass the claim.id from CheckToken to campaign.Attack() ?
thank you
***** main.go*****
func main() {
router := mux.NewRouter()
router.HandleFunc("/attack", token.CheckToken(campaign.Attack)).Methods("GET", "OPTIONS")
log.Fatal(http.ListenAndServe(":3000", handlers.CORS(handlers.AllowedOrigins([]string{"*"}),
handlers.AllowedHeaders([]string{"Content-Type", "authorization"}))(router)))
}
******campaign.go*****
package campaign
import (
"log"
"net/http"
)
func init() {
}
func Attack(w http.ResponseWriter, req *http.Request) {
log.Println("attack")
//i need to get the claim.Id here
}
****token.go****
type MyCustomClaims struct {
Id int `json:"id"` //the Id of the user
jwt.StandardClaims
}
func CheckToken(next http.HandlerFunc) (MyCustomClaims, http.HandlerFunc) {
return MyCustomClaims{}, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
authorizationHeader := req.Header.Get("authorization")
if authorizationHeader != "" {
bearerToken := strings.Split(authorizationHeader, " ")
if len(bearerToken) == 2 {
token, err := jwt.ParseWithClaims(bearerToken[1], &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("magicword"), nil
})
if token.Valid {
if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid {
//**************************
//***********i have the claims.id here and it works.*******
//**************************
log.Println(claims.Id)
//but i need to pass it or find a way to read it in campaign.Attack()
next(w, req)
} else {
log.Println(err)
}
} else if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
// Token is either expired or not active yet
}
}
}
}
})
}
Use context's method WithValue.
Instead next(w, req) write
ctx := context.WithValue(r.Context(), "claim_id", claims.Id)
next(w, req.WithContext(ctx))
and inside attack:
claim_id, ok := r.Context().Value("claim_id").(int)
if !ok {
return // I don't have context .. sorry
}
// use claim_id
What I haven't mention, how to create an unique key ... but sometimes in the future.
Look into CheckToken .. It should be middleware, and typical midleware pass http.Handler and maybe another argument and return another http.Handler (this is bit better than using http.HandlerFunc). Returned function typically call argument and do some action before or after this call.
func CheckToken(next http.Handler) http.Handler
{
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// and body the same as you have in the answer, but instead of next(w, req) put there two lines "WithValue"
}
}
and into begin of Attack put next few lines that I've already give you

Resources