How to Implement WebSocket Support in Gin: A Complete Guide
You implement WebSocket support in Gin by leveraging the framework's c.IsWebsocket() detection method and c.Writer.Hijack() capabilities to upgrade HTTP connections using specialized Go libraries like gorilla/websocket or coder/websocket, enabling full-duplex communication while preserving Gin's middleware and routing features.
Gin is an HTTP-centric web framework that deliberately leaves the low-level connection upgrade step to the developer. While the gin-gonic/gin repository does not embed a native WebSocket server, it provides the essential plumbing in context.go and response_writer.go to safely implement WebSocket support using third-party libraries.
Why Gin Does Not Embed Native WebSocket Support
Gin maintains a strict separation of concerns by focusing exclusively on routing, middleware chains, and HTTP response rendering. WebSocket protocols involve complex variants—binary versus text frames, custom ping/pong handling, and sub-protocol negotiation—that are better served by specialized libraries.
By exposing the raw net.Conn through the standard http.Hijacker interface, Gin provides maximum flexibility. Developers can select any WebSocket library matching their performance requirements without framework bloat. This design also ensures minimal impact on the request lifecycle; the framework enforces that hijacking occurs only when no body data has been written, preventing corruption of partially-sent HTTP responses.
Core Gin Mechanisms for WebSocket Upgrades
Three specific capabilities in the gin-gonic/gin source code enable clean WebSocket integration:
-
c.IsWebsocket()— Detects client-initiated handshakes by checking theConnection: UpgradeandUpgrade: websocketrequest headers. Implemented in [context.go](https://github.com/gin-gonic/gin/blob/master/context.go#L40-L47). -
c.Writer.Hijack()— Returns the underlyingnet.Connalong with a*bufio.ReadWriter, but only if no response body has been written. This safety guard is defined in [response_writer.go](https://github.com/gin-gonic/gin/blob/master/response_writer.go#L10-L20). -
c.Writer.WriteHeaderNow()— Forces immediate flushing of HTTP status and headers. Useful when you need to ensure the handshake response is sent before the WebSocket library writes its own frames. Located in [response_writer.go](https://github.com/gin-gonic/gin/blob/master/response_writer.go#L84-L90).
Together, these allow you to detect upgrade requests, run pre-upgrade middleware (authentication, rate-limiting), and then transfer control to a dedicated WebSocket library.
Complete Implementation Examples
Basic Echo Server with gorilla/websocket
The gorilla/websocket library accepts standard http.ResponseWriter and *http.Request parameters, making it the simplest integration path. It internally calls Hijack(), so you do not need to invoke it manually.
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
func wsHandler(c *gin.Context) {
if !c.IsWebsocket() {
c.AbortWithStatus(http.StatusBadRequest)
return
}
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
defer conn.Close()
for {
typ, msg, err := conn.ReadMessage()
if err != nil {
break
}
if err = conn.WriteMessage(typ, msg); err != nil {
break
}
}
}
func main() {
r := gin.Default()
r.GET("/ws", wsHandler)
r.Run(":8080")
}
Key implementation details:
- Pass
c.Writerandc.Requestdirectly toupgrader.Upgrade. - All Gin middleware executes normally before the upgrade occurs.
- No explicit
Hijack()call is required becausegorilla/websockethandles the connection takeover internally.
Manual Hijack with coder/websocket
For libraries requiring direct net.Conn access, use Gin's explicit Hijack method. This pattern provides full control over the connection lifecycle.
package main
import (
"bufio"
"context"
"net"
"net/http"
"github.com/coder/websocket"
"github.com/gin-gonic/gin"
)
func wsManualHijack(c *gin.Context) {
if !c.IsWebsocket() {
c.AbortWithStatus(http.StatusBadRequest)
return
}
conn, rw, err := c.Writer.Hijack()
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
wsConn, err := websocket.Accept(conn, &websocket.AcceptOptions{
OriginPatterns: []string{"*"},
})
if err != nil {
conn.Close()
return
}
defer wsConn.Close()
for {
typ, data, err := wsConn.Read(context.Background())
if err != nil {
break
}
if typ == websocket.MessageText {
wsConn.Write(context.Background(), websocket.MessageText, data)
}
}
rw.Flush()
}
func main() {
r := gin.Default()
r.GET("/ws", wsManualHijack)
r.Run(":8080")
}
Critical considerations:
c.Writer.Hijack()returns the rawnet.Connonly when no body has been written, enforced by Gin'sresponseWriterimplementation.- You must manually close the hijacked connection and flush the buffered reader (
rw) to clean up resources. - After hijacking, the HTTP server no longer manages the connection, giving you full control over framing and sub-protocols.
Generic Upgrade Helper Pattern
Create a reusable helper that separates Gin-specific validation from library-specific upgrade logic:
func UpgradeIfWebsocket(c *gin.Context, upgrader func(http.ResponseWriter, *http.Request) (interface{}, error)) (interface{}, error) {
if !c.IsWebsocket() {
c.AbortWithStatus(http.StatusBadRequest)
return nil, errors.New("not a websocket request")
}
return upgrader(c.Writer, c.Request)
}
Usage with any WebSocket library:
r.GET("/ws", func(c *gin.Context) {
conn, err := UpgradeIfWebsocket(c, func(w http.ResponseWriter, r *http.Request) (interface{}, error) {
return upgrader.Upgrade(w, r, nil)
})
if err != nil {
return
}
// Handle conn as *websocket.Conn
})
This pattern isolates the Gin-specific IsWebsocket check from library implementation details, improving code reusability across different WebSocket providers.
Key Source Files for WebSocket Implementation
Understanding these specific files in the gin-gonic/gin repository helps debug upgrade issues:
| File | WebSocket Relevance |
|---|---|
context.go |
Contains IsWebsocket() implementation that checks Connection and Upgrade headers. |
response_writer.go |
Implements http.Hijacker with strict guards preventing hijacking after body writes. |
README.md |
Documents general integration patterns for combining Gin with third-party Go libraries. |
Summary
- Use
c.IsWebsocket()fromcontext.goto validate WebSocket handshake requests before attempting connection upgrades. - Access raw TCP connections via
c.Writer.Hijack()inresponse_writer.go, which enforces safe upgrade timing by blocking hijacking after response body data is written. - Combine Gin's routing and middleware chains with specialized libraries like gorilla/websocket or coder/websocket for production-grade WebSocket services.
- Manage connection lifecycle manually after hijacking, including proper cleanup of buffered readers and explicit connection closing.
Frequently Asked Questions
Does Gin have built-in WebSocket support?
No. According to the gin-gonic/gin source code, the framework intentionally avoids embedding WebSocket protocols to maintain separation of concerns. Instead, it provides c.IsWebsocket() in context.go and c.Writer.Hijack() in response_writer.go, allowing seamless integration with dedicated WebSocket libraries while preserving Gin's middleware capabilities.
Can I use Gin middleware with WebSocket connections?
Yes. Because the WebSocket upgrade occurs inside a standard Gin handler, all middleware—including authentication, rate limiting, and logging—executes normally before the connection hijacking. Once you call the upgrader or c.Writer.Hijack(), Gin releases the connection and the middleware chain terminates, transferring control to your WebSocket management code.
What happens if I try to write to the response after hijacking?
Gin's responseWriter implementation in response_writer.go explicitly blocks HTTP body writes after hijacking to prevent protocol corruption. Attempting to use c.String(), c.JSON(), or similar methods after calling c.Writer.Hijack() will result in errors, ensuring the WebSocket handshake response remains intact and unmixed with HTTP content.
Which WebSocket library works best with Gin?
The optimal choice depends on your specific requirements. gorilla/websocket integrates seamlessly with Gin's http.ResponseWriter interface without requiring manual Hijack() calls, making it ideal for rapid development. coder/websocket requires explicit c.Writer.Hijack() calls but offers a modern API with built-in context support. Both approaches are fully supported by the framework's architecture as implemented in the source code.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →