Here's sample code:

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

//CORSMiddleware ...
func CORSMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost")
        c.Writer.Header().Set("Access-Control-Max-Age", "86400")
        c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
        c.Writer.Header().Set("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding, x-access-token")
        c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length")
        c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")

        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(200)
        } else {
            c.Next()
        }
    }
}

func main() {
    r := gin.Default()
    r.Use(CORSMiddleware())
    g := r.Group("/users")

    g.GET("/", func(context *gin.Context) {
        context.JSON(http.StatusOK, gin.H{"hello": "world"})
    })

    r.Run(":8081")
}

Here's normal response with CORS headers.

curl -v localhost:8081/users/                                                       <aws:loop-staging>
*   Trying ::1:8081...
* TCP_NODELAY set
* Connected to localhost (::1) port 8081 (#0)
> GET /users/ HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.65.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Headers: X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding, x-access-token
< Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE, UPDATE
< Access-Control-Allow-Origin: http://localhost
< Access-Control-Expose-Headers: Content-Length
< Access-Control-Max-Age: 86400
< Content-Type: application/json; charset=utf-8
< Date: Fri, 12 Jul 2019 06:27:16 GMT
< Content-Length: 18
<
{"hello":"world"}
* Connection #0 to host localhost left intact

But my CORS header doesn't apear in the 301 redirect response

[14:27] ➜  ~ curl -v localhost:8081/users                                                        <aws:loop-staging>
*   Trying ::1:8081...
* TCP_NODELAY set
* Connected to localhost (::1) port 8081 (#0)
> GET /users HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.65.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Content-Type: text/html; charset=utf-8
< Location: /users/
< Date: Fri, 12 Jul 2019 06:27:19 GMT
< Content-Length: 42
<
<a href="/users/">Moved Permanently</a>.

* Connection #0 to host localhost left intact

Comment From: Louis-Amas

Same here did you find a fix?

Comment From: xdays

My solution is not using api group for /users to avoid 301 redireciton.

Comment From: lolgolflol

g.GET("", func(context *gin.Context) {
        context.JSON(http.StatusOK, gin.H{"hello": "world"})
    })

Try to use this code.

Comment From: tangx

still have this problem.

Comment From: tangx

and gin, for the present, call middleware after router finding.

https://github.com/gin-gonic/gin/issues/2413

        rg.GET("/", func(c *gin.Context) {
            _url := strings.TrimRight(c.Request.URL.Path, "/") + "?" + c.Request.URL.RawQuery
            c.Redirect(301, _url)
        })

it's ugly, but it works

Comment From: timqian

Any updates on this issue?

Comment From: yashvardhan-kukreja

Raised a fix here - https://github.com/gin-gonic/gin/pull/3858

You can directly start using it in your project by running this

go mod edit -replace="github.com/gin-gonic/gin=github.com/yashvardhan-kukreja/gin@issue-3857-onredirect-middleware"  && GOPROXY=direct go get -d ./...

Comment From: jianzhiyao

still have this problem.

Comment From: WhaleFell

any update?

Comment From: yashvardhan-kukreja

You can checkout my fix here https://github.com/gin-gonic/gin/pull/3858

Rest, truth be told, it's a bit irritating to see the lack of engagement from the maintainers on such an impactful issue.

And yes I know, they're not obligated to answer us but then, why pretend to be OSS-friendly when you can't depict basic etiquettes of it.

Comment From: yashvardhan-kukreja

I would also like to share that I had e-mailed the maintainers about this issue and my PR numerous times, and I received no replies. Left the expectations of getting this across the finish line after a while.

Comment From: WhaleFell

You can checkout my fix here #3858

Rest, truth be told, it's a bit irritating to see the lack of engagement from the maintainers on such an impactful issue.

And yes I know, they're not obligated to answer us but then, why pretend to be OSS-friendly when you can't depict basic etiquettes of it.

Thank you so much for your response. Given the current state of the Gin community, I'm considering switching to another web framework, like Echo or Chi. The issues I've had with Gin seem to be resolved by some kind of strange magic, and it's pretty frustrating.