Enabling CORS for Cowboy REST API - rest

How can I enable CORS for cowboy rest handler?
I tried to add options/2 method, like this:
options(Req, State) ->
{[
{<<"access-control-allow-origin">>, <<"*">>},
{<<"access-control-allow-methods">>, <<"GET, OPTIONS">>}
], Req, State}.
but this causes errors like:
Error in process <0.263.0> with exit value: {{case_clause,{[{<<27 bytes>>,<<1 byte>>},{<<28 bytes>>,<<12 bytes>>}],{http_req,#Port<0.2636>,ranch_tcp,keepalive,<0.263.0>,<<7 bytes>>,{1,1},{{127,0,0,1},56522},<<9 bytes>>,undefined,9090,<<8 bytes>>,undefined,<<0 bytes>>,undefined,<<0 bytes>>,[],[{<<4 bytes>>,<<14 bytes>>},{<<10 bytes>>,<<74 bytes>>},{<<6 bytes>>,<<63 bytes>>},{<<15 bytes>>,<<14 bytes>>},{<<15 bytes>>,<<13 bytes>>},{<<6 bytes>>,<<4 bytes>>},{<<29 bytes>>,<<3 bytes>>},{<<30 bytes>>,<<16 bytes>>},{<<10 bytes>>,<<10 bytes>>}],[{<<10 bytes>>,[<<10 bytes>>]}],undefined,[],waiting,undefined,<<0 bytes>>,false,waiting,[],<<0 bytes>>,undefined},undefined...
Where is my mistake?

Cowboy documentation says you need to set header by using set_resp_headers, not return a list of headers:
%% If you need to add additional headers to the response at this point,
%% you should do it directly in the options/2 call using set_resp_headers.
So your code should look like:
options(Req, State) ->
Req1 = cowboy_req:set_resp_header(<<"access-control-allow-methods">>, <<"GET, OPTIONS">>, Req),
Req2 = cowboy_req:set_resp_header(<<"access-control-allow-origin">>, <<"*">>, Req1),
{ok, Req2, State}.
You can test with
curl -H "Origin: http://example.com" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: X-Requested-With" \
-X OPTIONS --verbose \
http://localhost:8080
* About to connect() to localhost port 8080 (#0)
* Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 8080 (#0)
> OPTIONS / HTTP/1.1
> User-Agent: curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5
> Host: localhost:8080
> Accept: */*
> Origin: http://example.com
> Access-Control-Request-Method: GET
> Access-Control-Request-Headers: X-Requested-With
>
< HTTP/1.1 200 OK
< connection: keep-alive
< server: Cowboy
< date: Mon, 25 Mar 2013 15:59:11 GMT
< content-length: 0
< access-control-allow-methods: GET, OPTIONS
< access-control-allow-origin: *
<
* Connection #0 to host localhost left intact
* Closing connection #0

My REST allows acceess to resources only and only if token is provided. So I had to implement is_authorized/2 callback which is different for OPTIONS and GET requests. See below:
1) Implement options/2 as follows
options(Req0, State) ->
% cors
Req1 = cowboy_req:set_resp_header(
<<"access-control-allow-methods">>, <<"GET, OPTIONS">>, Req0),
Req2 = cowboy_req:set_resp_header(
<<"access-control-allow-origin">>, <<"*">>, Req1),
Req3 = cowboy_req:set_resp_header(
<<"access-control-allow-headers">>, <<"authorization">>, Req2),
{ok, Req3, State}.
2) Implement is_authorized/2 as follows
is_authorized(Req, State) ->
case cowboy_req:method(Req) of
<<"GET">> ->
case cowboy_req:parse_header(<<"authorization">>, Req) of
{bearer, <<Token/binary>>} ->
{true, Req, Token};
_ ->
{{false, <<"Basic realm=\"cowboy\"">>}, Req, State}
end;
<<"OPTIONS">> ->
{true, Req, State}
end.
3) to_json/2 method sending access-control-allow-origin
to_json(Req0, State) ->
R = #{ foo => [<<"bar">>] },
% cors
Req1 = cowboy_req:set_resp_header(
<<"access-control-allow-origin">>, <<"*">>, Req0),
Body = jsx:encode(R),
{Body, Req1, State}.

You can try this:
Req1 = cowboy_req:set_resp_header(<<"access-control-allow-origin">>, <<"*">>, Req0 -> Worked for me.

In addition to the answers found here, Cowboy 2.* appears to have a middleware feature which you can use to intercept all REST handler requests to a server, and add the required response headers:
https://ninenines.eu/docs/en/cowboy/2.7/guide/middlewares/
An example CORS middleware implementation can be found in this Github gist:
https://github.com/ninenines/cowboy/blob/master/examples/markdown_middleware/src/markdown_middleware_app.erl

Related

Play framework 2.5 will not stream the request into the response

Hi I have a requirement to use play framework 2.5 (scala) to receive a large request body, then transform it and then stream it straight back out.
So far I've been unable to get the request stream to be sent out correctly using a chunked response (even untransformed).
Code example:
def endpointA = EssentialAction { requestHeader =>
Accumulator.source.map { source: Source[ByteString, _] =>
Ok.chunked(source)
}
}
POSTing data to the endpoint with curl does not output the posted data as expected and just results in the error below. I confirmed with wireshark that no response body is sent.
curl -v --data 'hello' -H "Connection: Keep-Alive" -H "Keep-Alive: 300" -H "Content-type: text/plain" http://localhost:9584/binding-tariff-admin/upload-csv
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9584 (#0)
> POST /binding-tariff-admin/endpoint-a HTTP/1.1
> Host: localhost:9584
> User-Agent: curl/7.64.1
> Accept: */*
> Connection: Keep-Alive
> Keep-Alive: 300
> Content-type: text/plain
> Content-Length: 5
>
* upload completely sent off: 5 out of 5 bytes
< HTTP/1.1 200 OK
< Transfer-Encoding: chunked
< Cache-Control: no-cache,no-store,max-age=0
< Content-Security-Policy: default-src 'self' 'unsafe-inline' *.s3.amazonaws.com www.google-analytics.com data:
< X-Permitted-Cross-Domain-Policies: master-only
< Content-Type: application/octet-stream
< Date: Wed, 19 Feb 2020 10:15:36 GMT
<
* transfer closed with outstanding read data remaining
* Closing connection 0
curl: (18) transfer closed with outstanding read data remaining
Also, if I change the code to return a stream I create myself, it works fine:
val testStream: Source[ByteString, NotUsed] = Source(List("hello")).map(ByteString.apply)
Is there something fundamentally wrong with what I'm trying to do here? I have seen other stack overflow examples with people suggesting this should be possible, e.g.
Play Framework Scala: How to Stream Request Body
I also tried using the verbatimBodyParser method described in the link but got the same results.
Thanks!
NFV

Does jetty overwrite custom http status code ?

I am trying to develop a rest api for my service wherein I set custom http status code depending on authorisation failure. But when I test it out using curl I am receiving 404 instead of 403. I am perplexed as to what might be causing this? Please help.
This is what I see from curl output or swagger UI:
root#ubuntu:~# curl -X GET http://localhost:8082/mr/v1/topic/bhakk -v
Note: Unnecessary use of -X or --request, GET is already inferred.
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8082 (#0)
> GET /mr/v1/topic/bhakk HTTP/1.1
> Host: localhost:8082
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Date: Mon, 07 May 2018 22:00:10 GMT
< Exception: serviceBlockedException
< Content-Type: application/vnd.kafka.v1+json
< Content-Length: 83
< Server: Jetty(9.2.z-SNAPSHOT)
<
* Connection #0 to host localhost left intact
{"error_code":40301,"message":"This service does not have access to the resource."}
Here is the code:
public Collection<String> list(#HeaderParam("x-nssvc-serviceid") String serviceID) {
Date now = new java.util.Date();
if (! ctx.getSecurityRestrictions().isServiceAllowed(uri, httpHeaders, "Describe", "Cluster", "kafka-cluster"))
throw Errors.serviceBlockedException(ctx,httpServletResponse);
List<String> topicsCopy = new ArrayList<String>(topics);
for (Iterator<String> iterator = topicsCopy.iterator(); iterator.hasNext();) {
String topic = iterator.next();
if (! ctx.getSecurityRestrictions().hasAccess (serviceId, "Describe", "Topic", topic)) {
iterator.remove();
}
}
return topicsCopy;
}
public static RestException serviceBlockedException(Context ctx,HttpServletResponse httpServletResponse) {
httpServletResponse.setHeader("Exception","serviceBlockedException");
httpServletResponse.setStatus(Status.FORBIDDEN.getStatusCode()); <----// here i am setting status code.
return new RestNotFoundException(SERVICE_ID_BLOCKED_MESSAGE, SERVICE_ID_BLOCKED_ERROR_CODE);
}
Kafka sets the Response 404 status in its RestNotFoundException
See: https://github.com/confluentinc/rest-utils/blob/master/core/src/main/java/io/confluent/rest/exceptions/RestNotFoundException.java

Browser ignores new port from nginx redirect

I need to create a http redirect via nginx. The goal is:
http://subdomain.domain.tld/path -> https://subdomain.domain.tld/path
http://subdomain.domain.tld:8090/path -> https://subdomain.domain.tld/path
This is my nginx configuration:
server {
listen 80;
listen 8090;
server_name subdomain.domain.tld;
return 301 https://$host$request_uri;
}
server {
listen 443;
server_name subdomain.domain.tld;
[...]
}
The first case works correctly. The second case technically, but not at the client browsers. If I start a request via cURL everything looks fine:
curl -v "http://subdomain.domain.tld:8090/path"
* About to connect() to subdomain.domain.tld port 8090 (#0)
* Trying XX.XX.XX.XX...
* Adding handle: conn: 0x1c94010
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x1c94010) send_pipe: 1, recv_pipe: 0
* Connected to subdomain.domain.tld (89.15.246.188) port 8090 (#0)
> GET /oath HTTP/1.1
> User-Agent: curl/7.30.0
> Host: subdomain.domain.tld:8090
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
* Server nginx is not blacklisted
< Server: nginx
< Date: Wed, 08 Jun 2016 10:28:15 GMT
< Content-Type: text/html
< Content-Length: 178
< Connection: keep-alive
< Location: https://subdomain.domain.tld/path
<
<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx</center>
</body>
</html>
But if I open the url http://subdomain.domain.tld:8090/path in my browser I get the url https://subdomain.domain.tld:8090/path. The browser switch to https but use the old port.
I checked it with Google Chrome, Firefox, Internet Explorer and Apple Safari. The result is the same and I dont understand why?
You've got HSTS (Strict-Transport-Security header). So browsers do exactly what this policy requires:
https://www.rfc-editor.org/rfc/rfc6797#section-8.3
8.3.5 [...] The UA MUST replace the URI scheme with "https", and [...] if the URI contains an explicit port component that is not equal to "80", the port component value MUST be preserved;…

play scala 2.4.3 redicrect not working

I'm trying the Reverse routing sample code
Here are my routes
GET /hello/:name controllers.Application.hello(name)
GET /bob controllers.Application.helloBob
and my codes
def helloBob = Action {
Redirect(routes.Application.hello("Bob"))
}
def hello(name: String) = Action {
Ok("Hello " + name + "!")
}
I can get hello response
$ curl -v localhost:9001/hello/play
Hello play!
But, can't get "Bob" response after redirect?
$ curl -v localhost:9001/bob
* Trying ::1...
* Connected to localhost (::1) port 9001 (#0)
> GET /bob HTTP/1.1
> Host: localhost:9001
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 303 See Other
< Location: /hello/Bob
< Date: Fri, 18 Sep 2015 03:19:04 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact
The path component of a URI is case sensitive. Check it.
try
curl -v localhost:9001/hello/Bob
Update
You code is correct (verified on my project) and you show the correct log - it prints 303 code. I think, you just need to say curl to follow redirect, like this
curl -L localhost:9000/bob

Zend Rest Api: putAction on POST method

I got a weird issue. I hope someone could help me.
I am new to Zend and I'm writing a RESTfull API.
When I run the curl command as POST method, it calls the putAction() function.
For example, I am run a curl command:
curl -X POST http://localhost/ws/user/post -v
Here is the response:
* About to connect() to localhost port 80 (#0)
* Trying 127.0.0.1...
* connected
* Connected to localhost (127.0.0.1) port 80 (#0)
> POST /ws/user/post HTTP/1.1
> User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8r zlib/1.2.5
> Host: localhost
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Mon, 22 Oct 2012 13:37:56 GMT
< Server: Apache/2.2.14 (Ubuntu)
< X-Powered-By: PHP/5.3.2-1ubuntu4.18
< Vary: Accept-Encoding
< Content-Length: 53
< Content-Type: text/html
<
* Connection #0 to host localhost left intact
"From putAction() updating the requested article"
Method = POST
* Closing connection #0
Here is the code:
[...]
public function postAction() {
echo 'Method = ' . $_SERVER['REQUEST_METHOD'];
if (!$id = $this->_getParam('id', false)) {
$this->sendResponse("Bad request. Id is missing", 400);
}
$this->sendResponse("From postAction() creating the requested article", 201);
}
public function putAction() {
echo 'Method = ' . $_SERVER['REQUEST_METHOD'];
$this->sendResponse("From putAction() updating the requested article");
}
[...]
Any ideas ?
EDIT:
I realized that I put this code in my bootstrap:
public function _initRestRoute() {
$front = Zend_Controller_Front::getInstance();
$router = $front->getRouter();
$restRoute = new Zend_Rest_Route($front, array(), array(
'ws',
));
$router->addRoute('rest', $restRoute);
}
When I comment it, it works.
Any explanations ? Thanks!
You shouldn't be having http://localhost/ws/user/post in your request while using the Zend_Rest_Route. This route interprets /user/post as parameters and changes action from post to put. Try http://localhost/ws instead.
See Zend/Rest/Route.php line 208.