ninenines / cowboy

Small, fast, modern HTTP server for Erlang/OTP.

Home Page:https://ninenines.eu

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

OPTIONS not working in cowboy_rest

stephb9959 opened this issue · comments

We are seeing a problem with cowboy 2.9, erl 25, running cowboy_rest.

When doing theoptions method, the client complains that the stream was not closed cleanly, and therefore does not complete, which generates pre-flight validation errors in the browser using the app. We are using TLS and these are real certs (not self-signed). The same certs are working on the same host in a C++ version of the code. We have implemented our own options callback, so we can add CORS headers to the response. (we do the same in our C++ implementation).

This code was working about 2,5 years ago with the version of cowboy at that time. We did not keep the exact version of cowboy a that time (just kept master).

We see the same problem if we use curl to generate the options method.

Are we missing some new options in cowboy:start_tls ?

Probably not. Please provide some code (options callback and/or how the response is sent) and error messages from curl -vvv.

Here is the options callback:

options(Req, State) ->
	Req1 = utils:add_cors(Req,<<"GET, POST, OPTIONS">>),
	io:format("Doing options (2): ~p~n",[Req1]),
	{ok, Req1, State}.

( the io:format is there to see that the code is being called)

here is the code for add_cors()

add_cors(Req0, Methods) ->
	Req1 = cowboy_req:set_resp_header(<<"Access-Control-Allow-Credentials">>, <<"true">>, Req0),
	Req2 = cowboy_req:set_resp_header(<<"Access-Control-Request-Headers">>, <<"*">>, Req1),
	Req3 = cowboy_req:set_resp_header(<<"Access-Control-Allow-Origin">>, <<"*">>, Req2),
	Req4 = cowboy_req:set_resp_header(<<"Vary">>, <<"Origin, Accept-Encoding">>, Req3),
	Req5 = cowboy_req:set_resp_header(<<"Access-Control-Allow-Methods">>, Methods, Req4),
	cowboy_req:set_resp_header(<<"Access-Control-Max-Age">>, <<"20">>, Req5).

These same CORS values work on C++ using the Poco framework.

This is the result for running curl

$ curl -vvv -X OPTIONS "https://ucentral.dpaas.arilia.com:16063/api/v1/system?info"
*   Trying 2600:1f13:f7c:6501:9312:c5c6:40f:c842:16063...
* connect to 2600:1f13:f7c:6501:9312:c5c6:40f:c842 port 16063 failed: Connection refused
*   Trying 34.222.8.211:16063...
* Connected to ucentral.dpaas.arilia.com (34.222.8.211) port 16063 (#0)
* ALPN: offers h2
* ALPN: offers http/1.1
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=*.dpaas.arilia.com
*  start date: Jan 24 00:00:00 2023 GMT
*  expire date: Feb 11 23:59:59 2024 GMT
*  subjectAltName: host "ucentral.dpaas.arilia.com" matched cert's "*.dpaas.arilia.com"
*  issuer: C=GB; ST=Greater Manchester; L=Salford; O=Sectigo Limited; CN=Sectigo RSA Domain Validation Secure Server CA
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* h2h3 [:method: OPTIONS]
* h2h3 [:path: /api/v1/system?info]
* h2h3 [:scheme: https]
* h2h3 [:authority: ucentral.dpaas.arilia.com:16063]
* h2h3 [user-agent: curl/7.85.0]
* h2h3 [accept: */*]
* Using Stream ID: 1 (easy handle 0x14280a800)
> OPTIONS /api/v1/system?info HTTP/2
> Host: ucentral.dpaas.arilia.com:16063
> user-agent: curl/7.85.0
> accept: */*
> 
* Connection state changed (MAX_CONCURRENT_STREAMS == 4294967295)!
* HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)
* Connection #0 to host ucentral.dpaas.arilia.com left intact
curl: (92) HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)

Thanks for your help.

The header names have to be given as lowercase. Cowboy doesn't lowercase them, it expects you to provide them already lowercase. Since it's using HTTP/2 and uppercase header names are forbidden in HTTP/2 that's probably the issue.

Thanks @essen, changing the casing to lowercase has fixed the pre-flight. We are still seeing CORS issues but we've made progress. I was not aware of this lower-case HTTP/2 spec. I also see why our C++ framework worked: it only does HTTP1.1. So it all fits together neatly now.

I really appreciate your help.