Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can not we set the header in the dial? #1

Open
majidbigdeli opened this issue Jun 3, 2019 · 8 comments
Open

Can not we set the header in the dial? #1

majidbigdeli opened this issue Jun 3, 2019 · 8 comments
Labels
enhancement New feature or request good first issue Good for newcomers

Comments

@majidbigdeli
Copy link

majidbigdeli commented Jun 3, 2019

I know that There is no method in the JavaScript WebSockets API for specifying additional headers for the client/browser to send. The HTTP path ("GET /xyz") and protocol header ("Sec-WebSocket-Protocol") can be specified in the WebSocket constructor.

The Sec-WebSocket-Protocol header (which is sometimes extended to be used in websocket specific authentication) is generated from the optional second argument to the WebSocket constructor:

var ws = new WebSocket("ws://example.com/path", "protocol");
var ws = new WebSocket("ws://example.com/path", ["protocol1", "protocol2"]);

The above results in the following headers:
Sec-WebSocket-Protocol: protocol
and
Sec-WebSocket-Protocol: protocol1, protocol2

but I have error

WebSocket connection to 'ws://localhost:3811/echo' failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received
(anonymous) @ neffos.js:612
dial @ neffos.js:604
runExample @ (index):37
(anonymous) @ (index):59

my index.html


<input id="input" type="text" />

<button id="sendBtn" disabled>Send</button>

<pre id="output"></pre>

<script src="./neffos.js"></script>
<script>
    var scheme = document.location.protocol == "https:" ? "wss" : "ws";
    var port = ":3811"
    var wsURL = "ws://localhost:3811/echo" 
    var outputTxt = document.getElementById("output");
    function addMessage(msg) {
        outputTxt.innerHTML += msg + "\n";
    }
    function handleError(reason) {
        console.log(reason);
        window.alert(reason);
    }
    function handleNamespaceConnectedConn(nsConn) {
        let inputTxt = document.getElementById("input");
        let sendBtn = document.getElementById("sendBtn");
        sendBtn.disabled = false;
        sendBtn.onclick = function () {
            const input = inputTxt.value;
            inputTxt.value = "";
            nsConn.emit("send", input);
            addMessage("Me: " + input);
        };
    }
    async function runExample() {
        // You can omit the "default" and simply define only Events, the namespace will be an empty string"",
        // however if you decide to make any changes on this example make sure the changes are reflecting inside the ../server.go file as well.
        try {
            const conn = await neffos.dial(wsURL, {
                default: { // "default" namespace.
                    _OnNamespaceConnected: function (nsConn, msg) {
                        handleNamespaceConnectedConn(nsConn);
                    },
                    _OnNamespaceDisconnect: function (nsConn, msg) {
                        
                    },
                    chat: function (nsConn, msg) { // "chat" event.

                    }
                }
            },["123"]);
            // You can either wait to conenct or just conn.connect("connect")
            // and put the `handleNamespaceConnectedConn` inside `_OnNamespaceConnected` callback instead.
            // const nsConn = await conn.connect("default");
            // handleNamespaceConnectedConn(nsConn);
            conn.connect("default");
        } catch (err) {
            handleError(err);
        }
    }
    runExample();
  </script>

I used Query String to solve this problem.

change wsURL to ws://localhost:3811/echo?UserId=123

and remove ["123"] protocol in the dial method.

Can not we have a custom header on dial?

function dial(endpoint: string, connHandler: any, protocols?: string[]): Promise<Conn> {
    if (endpoint.indexOf("ws") == -1) {
        endpoint = "ws://" + endpoint;
    }

    return new Promise((resolve, reject) => {
        if (!WebSocket) {
            reject("WebSocket is not accessible through this browser.");
        }


        let namespaces = resolveNamespaces(connHandler, reject);
        if (isNull(namespaces)) {
            return;
        }

        let ws = new WebSocket(endpoint, protocols);
        let conn = new Conn(ws, namespaces);
        ws.binaryType = "arraybuffer";
        ws.onmessage = ((evt: MessageEvent) => {
            let err = conn.handle(evt);
            if (!isEmpty(err)) {
                reject(err);
                return;
            }

            if (conn.isAcknowledged()) {
                resolve(conn);
            }
        });
        ws.onopen = ((evt: Event) => {
            // let b = new Uint8Array(1)
            // b[0] = 1;
            // this.conn.send(b.buffer);
            ws.send(ackBinary);
        });
        ws.onerror = ((err: Event) => {
            conn.close();
            reject(err);
        });
    });
}

Like golang client

	client, err := neffos.Dial(
		// Optional context cancelation and deadline for dialing.
		nil,
		// The underline dialer, can be also a gobwas.Dialer/DefautlDialer or a gorilla.Dialer/DefaultDialer.
		// Here we wrap a custom gobwas dialer in order to send the username among, on the handshake state,
		// see `startServer().server.IDGenerator`.
		gobwas.Dialer(gobwas.Options{Header: gobwas.Header{"X-Username": []string{username}}}),
		// The endpoint, i.e ws://localhost:8080/path.
		endpoint,
		// The namespaces and events, can be optionally shared with the server's.
		serverAndClientEvents)
@majidbigdeli majidbigdeli changed the title Can not we set the header in the dial? failed: Error during WebSocket handshake Jun 3, 2019
@majidbigdeli majidbigdeli changed the title failed: Error during WebSocket handshake Can not we set the header in the dial? Jun 3, 2019
@kataras
Copy link
Owner

kataras commented Jun 9, 2019

Oh @majidbigdeli I just saw it. It's the only one missing part of the neffos repo's javascript-equivalent example. It's already ready on my local machine but I didn't have enough time to test it yet (I am "designing" the new website for Iris too), I will push that feature tomorrow :)

@kataras kataras added enhancement New feature or request good first issue Good for newcomers labels Jun 9, 2019
@kataras
Copy link
Owner

kataras commented Jun 10, 2019

Just to give some details:

For the browser:

As you said there is no method in the JavaScript WebSockets API for specifying additional headers for the browser to send. Therefore we have 3-4 solutions:

  1. queries on the endpoint (as your comment above uses as well) ?header1=value1 (I've choose to proceed with that one which is already working locally but I am waiting for your respond to push)
  2. all modern browsers send cookies among with the websocket connection, so use document.cookies under the hoods can work.
  3. If you need those headers for basic auth something like this can work: ws://username:password@domain/endpoint_path
  4. The protocols[] parameter of the neffos.dial (and raw's new WebSocket constructor) can be used to send headers but server must be able to parse them, but there is a limitation: it must not contain a comma ,

For the nodejs side, the ws library we use can support custom headers.

kataras added a commit that referenced this issue Jun 10, 2019
…on nodejs the http.request.options.headers object can be used as usual, rel: #1
@majidbigdeli
Copy link
Author

Hello @kataras
Thank you for your answer .
I agree with the first solution To push in the examples. And I use this solution.
Please add an example for the custom header in nodejs.
Thank You.

@kataras
Copy link
Owner

kataras commented Jun 10, 2019

Updated

For the nodejs: You can simply use the http.request.options literal, which can contain a headers: { 'key': 'value'} as documented at https://nodejs.org/api/http.html#http_http_request_options_callback:

For the browser: I made it to accept the same dictionary for headers, so the same { headers: {...}} dictionary must be used.

    const conn = await neffos.dial(wsURL, {...}, {
        headers: {
          'X-Username': 'kataras',
        }
      });

Just a note for the browser-side:

The neffos.dial function will add a prefix of X-Websocket-Header-$key
and server-side will parse all url parameters that starts with X-Websocket-Header and set the url param to the http.Request.Headers field as $key, on nodejs and go client custom headers are possible by-default and are set directly.

Hope that helps @majidbigdeli

kataras added a commit that referenced this issue Jun 10, 2019
@majidbigdeli
Copy link
Author

majidbigdeli commented Jun 10, 2019

@kataras .
I saw the parseHeadersAsURLParameters method . Thank you for this solution.

Your solution is very clever

@majidbigdeli
Copy link
Author

I used this solution for myself already.

	server := neffos.New(gobwas.DefaultUpgrader, handler)
	server.IDGenerator = func(w http.ResponseWriter, r *http.Request) string {

		if userID := r.Header.Get("X-Username"); userID != "" {
			return userID
		} else if userID := r.FormValue("X-Username"); userID != "" {
			return userID
		}

		return neffos.DefaultIDGenerator(w, r)
	}

@kataras
Copy link
Owner

kataras commented Jun 10, 2019

Thank you a lot @majidbigdeli!

Yeah the solution of yours is fine and you can still use it but, you know, we needed a way to separate any user-specific url parameters to the endpoint that meant to be used as Request.URL.Query() and not as headers, that's why we have the prefix and the parse functions on both sides (I don't want to see future issues like "why my url parameters are parsed as headers!!").

@majidbigdeli
Copy link
Author

majidbigdeli commented Jun 10, 2019

Yeah the solution of yours is fine and you can still use it but, you know, we needed a way to separate any user-specific url parameters to the endpoint that meant to be used as Request.URL.Query() and not as headers, that's why we have the prefix and the parse functions on both sides (I don't want to see future issues like "why my url parameters are parsed as headers!!").

Yes you are right . That's why I say that your solution is very clever.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

2 participants