Search code examples
gowebsocket

Websocket server implementation with x/net library trowing 403


I am trying to implement a websocket server using x/net/websocket standard library.

My attempt so far looks like this:

package main

import (
    "fmt"
    "net/http"

    "golang.org/x/net/websocket"
)

type Server struct {
    baseUri     string
    connections map[string][]*websocket.Conn
}

func initServer(baseUri string) *Server {
    return &Server{
        baseUri: baseUri,
    }
}

func (server *Server) handleConnections() {
    http.Handle("/ws", websocket.Handler(server.listenConnections))
    http.ListenAndServe(":3000", nil)
}

func (server *Server) listenConnections(ws *websocket.Conn) {
    fmt.Println("New connection established")
    for {
        fmt.Println("FOO")
    }
}

func main() {
    server := initServer("/ws")
    server.handleConnections()
}

Attempting to connect to ws://localhost:3000/ws using multiple ws clients, I always get the same error: a 403-Forbidden. I even tried the official doc's example and am still getting the it. Am I missing something obvious? Like a default port blocking or similar?

Thank you in advance.

Edit: You may want to use a different port to reproduce the issue. Using 3000 as in my example, if not available, just breaks the program's execution.

Edit 2: You can use a client like websocat and do websocat 'ws://localhost:3000/ws' to attempt to connect to the server


Solution

  • I gave up, but with nice insights: If like myself you were following Anthony GG's walkthrough on creating a websocket server on Go from scratch, please don't. Video is outdated and although it gives a great intuition on how to create one, is better to (and no shame on) learn using gorilla's websocket library.

    package main
    
    import (
        "fmt"
        "net/http"
        "time"
    
        "github.com/gorilla/mux"
        "github.com/gorilla/websocket"
    )
    
    var upgrader = websocket.Upgrader{
        ReadBufferSize:  1024,
        WriteBufferSize: 1024,
    }
    
    type Server struct {
        baseUri     string
        connections map[string][]*websocket.Conn
        router      *mux.Router
        setup       *http.Server
    }
    
    func initServer(baseUri string) *Server {
        router := mux.NewRouter()
        return &Server{
            baseUri: baseUri,
            router:  router,
            setup: &http.Server{
                Handler:      router,
                Addr:         "127.0.0.1:8000",
                WriteTimeout: 15 * time.Second,
                ReadTimeout:  15 * time.Second,
            },
        }
    }
    
    func (server *Server) handleConnections() {
        server.router.HandleFunc("/ws/{var}", server.listenConnections)
        server.setup.ListenAndServe()
    }
    
    func (server *Server) listenConnections(w http.ResponseWriter, r *http.Request) {
        connection, err := upgrader.Upgrade(w, r, nil)
        if err != nil {
            fmt.Println(err)
            return
        }
        for {
            _, message, err := connection.ReadMessage()
            if err != nil {
                break
            }
    
            connection.WriteMessage(websocket.TextMessage, message)
            go messageHandler(message)
        }
        fmt.Println("Out of loop")
    }
    
    func messageHandler(message []byte) {
        fmt.Println(string(message))
    }
    
    func main() {
        server := initServer("/ws")
        server.handleConnections()
    }
    
    

    I'm also using gorilla/mux to be able to use path parameters (not sure why http handler could not). Notice how I changed http.Handle to mux.Router.HandleFunc. As user @Cerise pointed in the comments, x/net/websocket package is not in the standard library, but the original issue was not resolved just adding the Origin header either.

    Hopefully this skips a few headaches of other people learning Go like myself.