go
69 lines · 9 steps
Building a CORS middleware in Gin
A configurable Gin middleware that validates the request Origin and emits the right CORS headers, short-circuiting preflight requests.
Explained by
highlit
1package middleware
2
3import (
4 "net/http"
5 "strconv"
6 "strings"
7 "time"
8
9 "github.com/gin-gonic/gin"
10)
11
12type CORSConfig struct {
13 AllowOrigins []string
14 AllowMethods []string
15 AllowHeaders []string
16 AllowCredentials bool
17 MaxAge time.Duration
18}
19
20func CORS(cfg CORSConfig) gin.HandlerFunc {
21 allowed := make(map[string]struct{}, len(cfg.AllowOrigins))
22 wildcard := false
23 for _, o := range cfg.AllowOrigins {
24 if o == "*" {
25 wildcard = true
26 }
27 allowed[o] = struct{}{}
28 }
29
30 methods := strings.Join(cfg.AllowMethods, ", ")
31 headers := strings.Join(cfg.AllowHeaders, ", ")
32 maxAge := strconv.Itoa(int(cfg.MaxAge.Seconds()))
33
34 return func(c *gin.Context) {
35 origin := c.GetHeader("Origin")
36 if origin == "" {
37 c.Next()
38 return
39 }
40
41 _, ok := allowed[origin]
42 if !ok && !wildcard {
43 c.Next()
44 return
45 }
46
47 h := c.Writer.Header()
48 if wildcard && !cfg.AllowCredentials {
49 h.Set("Access-Control-Allow-Origin", "*")
50 } else {
51 h.Set("Access-Control-Allow-Origin", origin)
52 h.Add("Vary", "Origin")
53 }
54
55 if cfg.AllowCredentials {
56 h.Set("Access-Control-Allow-Credentials", "true")
57 }
58
59 if c.Request.Method == http.MethodOptions {
60 h.Set("Access-Control-Allow-Methods", methods)
61 h.Set("Access-Control-Allow-Headers", headers)
62 h.Set("Access-Control-Max-Age", maxAge)
63 c.AbortWithStatus(http.StatusNoContent)
64 return
65 }
66
67 c.Next()
68 }
69}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Precomputing lookup structures and joined strings once at setup keeps the per-request handler cheap.
- 2CORS requires echoing a specific Origin (plus a Vary header) rather than a wildcard whenever credentials are allowed.
- 3Preflight OPTIONS requests should be answered and aborted early with 204 instead of reaching route handlers.
Related explainers
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
go
package logging import ( "context"
Deduplicating log attributes in Go's slog
decorator-pattern
structured-logging
immutability
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-cors-middleware-in-gin-explained-go-1ab2/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.