IXWebSocket icon indicating copy to clipboard operation
IXWebSocket copied to clipboard

403 forbidden wenn connecting to golang.org/x/net/websocket

Open qknight opened this issue 2 years ago • 4 comments

problem description

i've been using the example code from https://machinezone.github.io/IXWebSocket/

when running this client code i see this GET request in wireshark:

GET /matrix-appserver/v1/ts/events HTTP/1.1
Host: localhost:5000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: SGRjYmMwNGU5OTZnM0MyYw==
User-Agent: ixwebsocket/11.4.3 windows ssl/OpenSSL OpenSSL 1.1.1s  1 Nov 2022 zlib 1.2.12

but the golang server will answer with:

HTTP/1.1 403 Forbidden

but if i add

headers["Origin"] = "http://localhost:5000";

it is working. why does it require this? should i file a bug report on the golang.org/x/net/websocket side?

a connection from the webbrowsers javascript console works without the explicit origin, this is the request from javascript:

GET /matrix-appserver/v1/ts/events HTTP/1.1
Host: localhost:5000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: */*
Accept-Language: de,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Sec-WebSocket-Version: 13
Origin: http://localhost:5000
Sec-WebSocket-Protocol: protocolOne
Sec-WebSocket-Extensions: permessage-deflate
Sec-WebSocket-Key: UfNfs6NDmKF17/2Tze/Jpw==
Connection: keep-alive, Upgrade
Cookie: Goland-edb8cc46=1aed21cf-1be6-4199-8b7d-97ce44799993
Sec-Fetch-Dest: websocket
Sec-Fetch-Mode: websocket
Sec-Fetch-Site: same-origin
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket

note: i also added this so others might see this issue and know what to do.

client implementation

this is actually working code:

// Required on Windows
ix::initNetSystem();

m_webSocket = std::make_unique<ix::WebSocket>();

m_webSocket->enableAutomaticReconnection();

// Connect to a server with encryption
// See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration
std::string url("ws://localhost:5000/matrix-appserver/v1/ts/events");
m_webSocket->setUrl(url);

ix::WebSocketHttpHeaders headers;
headers["Origin"] = "http://localhost:5000";
m_webSocket->setExtraHeaders(headers);

m_webSocket->setPingInterval(45);
m_webSocket->addSubProtocol("teamspeak-appserver-protocol1");
//m_webSocket->disablePerMessageDeflate();

LOGL(<< "Connecting to " << url, LogLevel_INFO);

// Setup a callback to be fired (in a background thread, watch out for race conditions !)
// when a message or an event (open, close, error) is received
m_webSocket->setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) {
    if (msg->type == ix::WebSocketMessageType::Message) {
        LOGL(<< "received message: " << msg->str, LogLevel_INFO);
    } else if (msg->type == ix::WebSocketMessageType::Open) {
        LOGL(<< "Connection established", LogLevel_INFO);
    } else if (msg->type == ix::WebSocketMessageType::Error) {
        // Maybe SSL is not configured properly
        LOGL(<< "Connection error: " << msg->errorInfo.reason, LogLevel_INFO);
    }
});

// Now that our callback is setup, we can start our background thread and receive messages
m_webSocket->start();

server

    import "golang.org/x/net/websocket"
    ...
matrix_api := mux.NewRouter()
    ...
matrix_api.HandleFunc("/matrix-appserver/v1/ts/events", func(rw http.ResponseWriter, req *http.Request) {      // called by teamspeak
	colorlogger.Log.Info("about to create a new websocket connection")
	websocket.Handler(ws.OnConnected).ServeHTTP(rw, req)
}).Methods(http.MethodGet)

my solution

i've added this header to the request:

headers["Origin"] = "http://localhost:5000";

then it will return a working websocket connection.

qknight avatar Feb 08 '23 12:02 qknight

Thanks for the report. Maybe we could set an Origin automatically.

Does it work if you use ws://localhost:5000

instead of http://localhost:5000

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin%20

bsergean avatar Feb 10 '23 20:02 bsergean

Setting this automatically would be amazing!

And the good news is:

ws://localhost:5000

instead of

http://localhost:5000/

The log reads:

2023-02-11 00:01:34 2023-02-10 23:01:34.373743|INFO    |AppServer     |   |Connecting to ws://appserver:5000/matrix-appserver/v1/teamspeak/events
2023-02-11 00:01:34 2023-02-10 23:01:34.374065|INFO    |AppServer     |   |With origin ws://appserver:5000
2023-02-11 00:01:34 2023-02-10 23:01:34.376552|INFO    |AppServer     |   |Connection established
2023-02-11 00:01:34 2023-02-10 23:01:34.377117|INFO    |Files         |   |Connection established

for Origin header seems be working!

qknight avatar Feb 10 '23 22:02 qknight

I've created this solution:

#include <boost/url.hpp>
...
std::string appserver_websocket_url = "ws://appserver:5000/appserver/v1/events"
assert(appserver_websocket_url != "");
boost::urls::url_view u(appserver_websocket_url);

std::string portString = "";
if (u.has_port() == true) {
    portString = ":" + std::string(u.port());
}
std::string url_full   = std::string(u.scheme()) + "://" + std::string(u.host()) + portString + std::string(u.path());
std::string url_origin = std::string(u.scheme()) + "://" + std::string(u.host()) + portString;
m_webSocket->setUrl(url_full);

ix::WebSocketHttpHeaders headers;
headers["Origin"] = url_origin;
m_webSocket->setExtraHeaders(headers);

Too bad, there is no boost url function to get the origin directly. The problem with constructing it is that there could be parameters which I did not yet handle as logins and so on.

https://www.boost.org/doc/libs/1_81_0/libs/url/doc/html/url/overview.html

qknight avatar Mar 06 '23 17:03 qknight

https://github.com/machinezone/IXWebSocket/pull/455

I just added the Origin header automatically. Do you want to give it a try ?

bsergean avatar Apr 01 '23 19:04 bsergean