mattgodbolt / seasocks

Simple, small, C++ embeddable webserver with WebSockets support
BSD 2-Clause "Simplified" License
734 stars 120 forks source link

Support for OPTIONS request for CORS reasons #152

Open kmiller15211 opened 3 years ago

kmiller15211 commented 3 years ago

Hello, We are using Seasocks on our custom built embedded Linux mobile device to serve up static endpoints that act as a remote control testing API to our device. We are mapping these REST endpoints to C++ functions. This would be something like this: PUT /arithmetic/add?param1=2&param2=4

In our code, this is done by creating a a RootPageHandler object and adding the URL paths to this.

We are looking to add CORS to our API, so that if our device is sharing a local network connection with a host PC, a web browser on that host PC can call through to our device, and keep the data private and in the local loop. However, the only ability I see to add CORS is through the web socket handers:

void Server::addWebSocketHandler(const char* endpoint, std::shared_ptr<WebSocket::Handler> handler,
        bool allowCrossOriginRequests) {
    _webSocketHandlerMap[endpoint] = { handler, allowCrossOriginRequests };
}

void Server::addPageHandler(std::shared_ptr<PageHandler> handler) {
    _pageHandlers.emplace_back(handler);
}

We attempted to add a header on our endpoints like this: writer->header("Access-Control-Allow-Origin", "*");, but this appears to be too far downstream and the browser returns the following error: "Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://xxxxx/system/serial-number. (Reason: CORS preflight response did not succeed). Uncaught (in promise) TypeError: NetworkError when attempting to fetch resource.".

Is it possible to add CORS to the prefllight traffic? Is there an easier way to do this using Seasocks? Thank you in advance

mattgodbolt commented 3 years ago

Hi there!

When you do the writer->header(...) stuff, does the header actually make it into the header of the reply? Or does it appear in the body?

You can test with curl for example:


curl -D- http://localhost:1234/path/to/your/thing
``` (assuming GET here, but there are arguments to make it PUT etc)

the `-D-` says "output the headers to stdout`, so you should see them, followed by a blank line, followed by the body.

We can definitely add CORS support, but it _should_ be "just" adding the headers. My guess is that isn't working here!
kmiller15211 commented 3 years ago

Thanks for the response! Here is my command, and the response. Seems like it's correctly returning the header, as expected:

~ # curl -D- --request GET '192.168.1.203:80/config/param?category=holter&paramName=framSizeBytes' HTTP/1.1 200 OK Server: 1.3.2 Date: Thu, 22 Apr 2021 18:50:25 GMT Access-Control-Allow-Origin: Content-type: Access-Control-Allow-Origin:

{"value":1048576}

A few things to note: Our endpoint is dangled off of the main page handler - wouldn't our endpoint returning the CORS header as shown above come AFTER the preflight failure, meaning it would never have a chance to take effect? That matches what we're seeing in practice; When any of our Seasocks REST endpoints are hit, we typically see a journal that we're servicing endpoint "X/Y/Z". We're not seeing that here, so it seems like it's dead ended before it even has a chance to return that header.

mattgodbolt commented 3 years ago

It actually looks like it's in there twice....

and yes, grepping the code we already put Access-Control-Allow-Origin: * in our messages! (Connection.cpp)...!

So, as best I can tell the CORS is working...but

my guess is the "preflight" it's talking about is doing a HEAD or something? can you wireshark capture and see if that's the case (I'm clutching at straws here; we don't support HEAD right now but could...I think. It's bee a while...)

kmiller15211 commented 3 years ago

OK, here's a Wireshark trace from what we're seeing. In this case, we have a PC running Chrome attempting to access the embedded Linux device running seasocks with page handlers - with a GET request of 'http://192.168.137.138/system/serial-number. The Chrome application returns the following error information:

Access to fetch at 'http://192.168.137.138/system/serial-number' from origin 'http://dpcpo-farga-132dtn1y09cpq-1698599008.us-east-1.elb.amazonaws.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

cors-request-packet-trace-2021-04-26.txt

mattgodbolt commented 3 years ago

Hi! Can you attach the packet capture itself instead of the text? It's much easier to debug in wireshark than in a text editor :) thanks!

mattgodbolt commented 3 years ago

ah! I was able to read enough:

Hypertext Transfer Protocol
    OPTIONS /system/serial-number HTTP/1.1\r\n
    Host: 192.168.137.138\r\n

is what was sent, which we don't support I think? Hard to tell

kmiller15211 commented 3 years ago

Sorry, here's the full trace (I hope). Unfortunately, getting this secondhand, as I'm having device/networking issues.

The request should have been a GET of this URL: 'http://192.168.137.138/system/serial-number'.

cors-request-packet-trace-wireshark.zip

kmiller15211 commented 3 years ago

We believe that the OPTIONS call is the pre flight check to ensure that the web domain is allowed to call that API. (Sorry, I'm not a HTTP expert; We believe this is part of the standard, so I'll apologize upfront if I have this wrong).

mattgodbolt commented 3 years ago

Right! That seems to be what's happening here. We'll need to support OPTIONS to get this to work. I'll rename this issue accoridngly.

mattgodbolt commented 3 years ago

Relevant docs: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#functional_overview

mattgodbolt commented 3 years ago

and https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS#preflighted_requests_in_cors