Update: Send the origin header with js websockets - go

I've been playing around with gorilla-websocket in Go, and as I implemented the basic echo example, I logged an error after deploying the server,
Origin is not found
Websocket version != 13
I found a way to bypass this by making the function that checks the origin always return true
var wsUpgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
But it doesn't feel right. Therefore, I am looking for a way to fix that issue.
Update: After having another look at the issue it seems like I'm actually looking to add the origin header to the client implementation which is the javascript websocket implementation
#benjic
Im connecting to the websocket via a javascript html5 application that isn't hosted on the same server but is run locally by me via chrome
So how do I do that.

Reading through the Gorilla WebSocket Documentation indicates that when a nil value is provided for the CheckOrigin field of an Upgrader type a safe default is used. The default checks the Origin field of the incoming request with the Host header value to confirm they are equal before allowing the request. The documentation indicates that browsers do not enforce cross origin validity and it is the responsibility of the server application to enforce. You can see exactly how this is done in the source code for the Gorilla library.
The documentation and source indicate an Upgrade function in the websocket package that acts as a factory for your example code above. The factory function takes a custom buffer size and overrides the CheckOrigin to always return true. Instead of creating a custom Upgrader you can use this factory function in the HttpHandler to upgrade your connections.
func webSocketHandler(w http.ResponseWriter, r *http.Request) {
conn, err := websocket.Upgrade(w, r, nil, 1024, 1024)
defer conn.Close()
if err != nil {
http.Error(w, err, http.StatusInternalServerError)
return
}
conn.WriteMessage(websocket.TextMessage, []byte("Hello, world!"))
}

Deploy your html to a server, e.g nginx or just use node to start. Then it will get a hostname & port in browser.
Then allow that address when create Upgrader , e.g:
var origins = []string{ "http://127.0.0.1:18081", "http://localhost:18081", "http://example.com"}
var _ = websocket.Upgrader{
// Resolve cross-domain problems
CheckOrigin: func(r *http.Request) bool {
var origin = r.Header.Get("origin")
for _, allowOrigin := range origins {
if origin == allowOrigin {
return true
}
}
return false
}}

Related

multiple CORS values being rejected in Go gin api

In my Go API, I'm using gin, and I have one value set in my Access-Control-Allow-Origin header. If I have more than one, my react UI throws an error to the effect of The Access-Control-Allow-Origin header contains multiple values 'http://value1, http://value2', but only one is allowed.... I need to set multiple values. How do I do this?
The API is a reverse proxy, and here's the relevant code:
func proxy(c *gin.Context) {
var remote = "myUrl"
proxy := httputil.NewSingleHostReverseProxy(remote)
proxy.Director = func(req *http.Request) {
req.Header.Set("Authorization", "My Auth Values")
req.Host = remote.Host
req.URL.Scheme = remote.Scheme
req.URL.Host = remote.Host
}
proxy.ModifyResponse = addCustomHeader
proxy.ServeHTTP(c.Writer, c.Request)
}
func addCustomHeader(r *http.Response) error {
r.Header["Access-Control-Allow-Origin"] = []string{"value1"}
return nil
}
A CORS header can only contain a single value. If you want to implement your own CORS middleware, you need to work around that fact.
A simple CORS middleware will add the Access-Control-Allow-Origin header with the value of the specific address of the incoming request, usually taken from the Referer or Origin header. Typically, you match this against a list or map first, to see if it's in your allow list. If so, then the address of the request is added as allowed origin (as a single value).
A simple example could look like this
allowList := map[string]bool{
"https://www.google.com": true,
"https://www.yahoo.com": true,
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if origin := r.Header.Get("Origin"); allowList[origin] {
w.Header().Set("Access-Control-Allow-Origin", origin)
}
})
Since you are using the reverse proxy, you can access the request from the response.
mod := func(allowList map[string]bool) func(r *http.Response) error {
return func(r *http.Response) error {
if origin := r.Request.Header.Get("Origin"); allowList[origin] {
r.Header.Set("Access-Control-Allow-Origin", origin)
}
return nil
}
}
proxy := &httputil.ReverseProxy{
Director: func(r *http.Request) {
r.URL.Scheme = "https"
r.URL.Host = "go.dev"
r.Host = r.URL.Host
},
ModifyResponse: mod(allowList),
}
You only need a single value for each incoming request. The usual technique is to configure trusted origins on the server, eg:
trustedOrigins: [https://www.domain1.com, https://www.domain2.com]
Then check the runtime value of the origin header, which is sent by all modern browsers. If this is a trusted origin then add CORS headers:
Access-Control-Allow-Origin: https://www.domain2.com
A wildcard could be used but that is not recommended and also will not work as intended if you are also using credentialed requests (eg those with cookies).

Response does not implement http.Hijacker

I'm using Go and trying to implement WebSocket in my project. while implementing this. I get "WebSocket: response does not implement HTTP.Hijacker" error. I'm new to this technology. Can anyone help me resolve this?
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func HandleConnections(w http.ResponseWriter, r *http.Request) {
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("err", err)
return
}
log.Println("hello client")
}
The application is using "middleware" that wraps the net/http server's ResponseWriter implementation. The middleware wrapper does not implement the Hijacker interface.
There are two fixes for the problem:
Remove the offending middleware.
Implement the Hijacker interface on the middleware wrapper by delegating through to the underlying response. The method implementation will look something like this:
func (w *wrapper) Hijack() (net.Conn, *bufio.ReadWriter, error) {
h, ok := w.underlyingResponseWriter.(http.Hijacker)
if !ok {
return nil, nil, errors.New("hijack not supported")
}
return h.Hijack()
}
If you don't know what the response writer wrapper is, add a statement to print the type from the handler:
func HandleConnections(w http.ResponseWriter, r *http.Request) {
fmt.Printf("w's type is %T\n", w)
...
To cite the docs on http.Hijacker:
~$ go doc http.Hijacker
package http // import "net/http"
type Hijacker interface {
// Hijack lets the caller take over the connection.
// After a call to Hijack the HTTP server library
// will not do anything else with the connection.
//
// It becomes the caller's responsibility to manage
// and close the connection.
//
// The returned net.Conn may have read or write deadlines
// already set, depending on the configuration of the
// Server. It is the caller's responsibility to set
// or clear those deadlines as needed.
//
// The returned bufio.Reader may contain unprocessed buffered
// data from the client.
//
// After a call to Hijack, the original Request.Body must not
// be used. The original Request's Context remains valid and
// is not canceled until the Request's ServeHTTP method
// returns.
Hijack() (net.Conn, *bufio.ReadWriter, error)
}
The Hijacker interface is implemented by ResponseWriters that allow an
HTTP handler to take over the connection.
The default ResponseWriter for HTTP/1.x connections supports Hijacker,
but HTTP/2 connections intentionally do not.
ResponseWriter wrappers may also not support Hijacker.
Handlers should always test for this ability at runtime.
So, I see several possibilities for your problem to happen:
You're trying to turn an HTTP/2 connection to a Websocket.
You're using some "middleware" which wraps whatever object the stock net/http package passes to your handler as net/http.ResponseWriter with something which does not bother to implement the proper Hijack method to support the net/http.Hijacker interface.

Make httputil.ReverseProxy Foward Response Stream Correctly

I have reverse proxies in my main web-server that are dedicated to a certain micro-service and handle forward requests to their appropriate micro-services.
func newTrimPrefixReverseProxy(target *url.URL, prefix string) *httputil.ReverseProxy {
director := func(req *http.Request) {
// ... trims prefix from request path and prepends the path of the target url
}
return &httputil.ReverseProxy{Director: director}
}
This has worked perfectly for pure JSON responses, but I have ran into issues recently when trying to serve content (stream responses) through the reverse proxy. The means for serving the content is irrelevant, the (video) content is served as intended when the service is accessed directly and not through the reverse proxy.
Serving the content:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeContent(w, r, "video.mp4", time.Now().Add(time.Hour*24*365*12*9*-1), videoReadSeeker)
})
Again, the videoReadSeeker and how the content is served is not the issue, the issue is having my response relayed as intended to the requester through the reverse proxy; when accessing the service directly, the video shows up and I can scrub it to my heart's content.
Note that the response for data the content is received (http status, headers), but the content stream in the response body is not.
How can I make sure that the reverse proxy handles streamed responses as intended for the content?
Do you get the same results when using:
package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
)
func main() {
u, err := url.Parse("http://localhost:8080/asdfasdf")
if err != nil {
log.Fatal("url.Parse: %v", err)
}
proxy := httputil.NewSingleHostReverseProxy(u)
log.Printf("Listening at :8081")
if err := http.ListenAndServe(":8081", proxy); err != nil {
log.Fatal("ListenAndServe: %v", err)
}
}
Ultimately these are the same implementation under the hood, but the director provided here ensures that some of the expected headers exist that you will need for some proxy features to function as expected.

How do you send websocket data after a page is rendered in Golang?

I am new to Golang and am trying to send data using web-sockets to a page. I have a handler and I want to be able to serve a file and after it is rendered send it a message. This is some code that I have now.
package main
import (
"github.com/gorilla/websocket"
"log"
"fmt"
"net/http"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
func serveRoot(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "views/index.html")
_, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
}
func main() {
http.HandleFunc("/", serveRoot)
fmt.Println("Started")
if err := http.ListenAndServe(":9090", nil); err != nil {
log.Fatal("ListenAndServe:", err)
}
}
The problem is that using the gorilla library I have no idea how to send data and I am getting some output when I load the page.
2018/01/23 08:35:24 http: multiple response.WriteHeader calls
2018/01/23 08:35:24 websocket: the client is not using the websocket protocol: 'upgrade' token not found in 'Connection' header
2018/01/23 08:35:24 http: multiple response.WriteHeader calls
2018/01/23 08:35:24 websocket: 'Origin' header value not allowed
Intention: Send some data after the page is rendered, then (later) hook it up to stdin/stderr
Disclaimer: I am just learning to code, so it would be a great help is you could take that into consideration and not be too vague.
So, as some of the comments mentioned, you can't upgrade a connection that has already been served html. The simple way to do this is just have one endpoint for your websockets, and one endpoint for your html.
So in your example, you might do:
http.HandleFunc("/", serveHtml)
http.HandleFunc("/somethingElse", serveWebsocket)
Where serveHtml has your http.ServeFile call, and serveWebsocket has the upgrading and wotnot.

Go & Socket.io HTTP + WSS on one port with CORS?

Brand new to Go.. Still obviously learning the syntax and the basics.. But I do have a specific goal in mind..
I'm trying to just get a simple server up on :8080 that can respond to both HTTP and socket.io (via /socket.io/ url), specificaly with CORS.
My code:
package main
import (
"log"
"net/http"
"github.com/rs/cors"
"github.com/googollee/go-socket.io"
)
func SayHelloWorld(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
}
func main() {
c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowCredentials: true,
})
server, err := socketio.NewServer(nil)
if err != nil {
log.Fatal(err)
}
server.On("connection", func(so socketio.Socket) {
log.Println("on connection")
so.Join("chat")
so.On("chat message", func(msg string) {
log.Println("emit:", so.Emit("chat message", msg))
so.BroadcastTo("chat", "chat message", msg)
})
so.On("disconnection", func() {
log.Println("on disconnect")
})
})
server.On("error", func(so socketio.Socket, err error) {
log.Println("error:", err)
})
http.Handle("/socket.io/", c.Handler(server))
http.HandleFunc("/", SayHelloWorld)
log.Println("Serving at localhost:8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
On the client side I'm still seeing:
WebSocket connection to 'wss://api.domain.com/socket.io/?EIO=3&transport=websocket&sid=xNWd9aZvwDnZOrXkOBaC' failed: WebSocket is closed before the connection is established.
(index):1 XMLHttpRequest cannot load https://api.domain.com/socket.io/?EIO=3&transport=polling&t=1420662449235-3932&sid=xNWd9aZvwDnZOrXkOBaC. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://fiddle.jshell.net' is therefore not allowed access.
EDIT #1:
So I've been banging my head away trying to understand why I can't connect.. Came across an even more confusing piece of the puzzle?
https://gist.github.com/acoyfellow/167b055da85248c94fc4
The above gist is the code of my golang server + the browser code used to connect.. This code will send 30 HTTP GET requests per second to the backend, without connecting, upgrading, or giving any errors (client or server side).. it essentially DDOS's my own backend?
Someone, please someone tell me I'm doing something stupid.. This is quite the pickle :P
EDIT #2:
I can stop the "DDOS" by simply adjusting the trailing / on the URL of the socket.io endpoint in Go.. So: mux.Handle("/socket.io", server) to mux.Handle("/socket.io/", server) will now produce error messages and connection attempts with error responses of:
WebSocket connection to 'wss://api.domain.com/socket.io/?EIO=3&transport=websocket&sid=0TzmTM_QtF1TaS4exiwF' failed: Error during WebSocket handshake: Unexpected response code: 400 socket.io-1.2.1.js:2
GET https://api.domain.com/socket.io/?EIO=3&transport=polling&t=1420743204485-62&sid=0TzmTM_QtF1TaS4exiwF 400 (Bad Request)
So I gave up using googoolee's Socket.io implementation and went with gorilla's.
I checked out their examples: https://github.com/gorilla/websocket/tree/master/examples/chat
Checked out their docs: http://www.gorillatoolkit.org/pkg/websocket
-- Under Origin Considerations I found:
An application can allow connections from any origin by specifying a function that always returns true:
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
}
I added this CheckOrigin function to the conn.go file in their example, and was able to get a CORS socket server talking to a browser.
As a first adventure into Golang, this was frustrating and fun.. +1 to anyone else learning
Don't you mean http + ws or https + wss. If you remove a s from wss, you should be able to connect.
If you want tls for web socket (wss), then you need to http.ListenAndServeTLS.
It appears that CORS does not apply to WebSockets. Per this related question "With WebSocket, there is an "origin" header, which browser MUST fill with the origin of the HTML containing the JS that opens the WS connection."
As stated here:
Cross origin websockets with Golang
How about in your SayHelloWorld func, adding something like:
w.Header().Set("Access-Control-Allow-Origin", "*")
Or, possibly better:
if origin := r.Header.Get("Origin"); origin != "" {
w.Header().Set("Access-Control-Allow-Origin", origin)
}
I get the similar problerm with normal ajax call. It require more work in both front-end and backend. I belive most popular front-end libs liek JQuery or AngularJS handle these very well.
I see you're using the https://github.com/rs/cors package but you don't include the usage of that package, here is the implement with only Go std package:
type CrossOriginServer struct {}
func (s *CrossOriginServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// you may need to add some more headers here
allowHeaders := "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization"
if origin := req.Header.Get("Origin"); validOrigin(origin) {
rw.Header().Set("Access-Control-Allow-Origin", origin)
rw.Header().Set("Access-Control-Allow-Methods", "POST, PUT, PATCH, GET, DELETE")
rw.Header().Set("Access-Control-Allow-Headers", allowHeaders)
}
if req.Method == "OPTIONS" {
return
}
// if you want, you can use gorilla/mux or any routing package here
mux := http.NewServeMux()
mux.Handle("/socket.io/", c.Handler(server))
mux.HandleFunc("/", SayHelloWorld)
mux.ServeHTTP(rw, req)
}
func validOrigin(origin string) bool {
allowOrigin := []string{
"http://localhost:8081",
"http://example.com"
}
for _, v := range allowOrigin {
if origin == v {
return true
}
}
return false
}
func main() {
// do you stuff
// ...
// ...
http.ListenAndServe(":8080", &CrossOriginServer{})
}

Resources