elgcom elgcom - 3 months ago 9
C++ Question

add Access-Control-Allow-Origin on http_listener in C++ REST SDK

I am running an HTTP server with web::http::experimental::listener::http_listener from Microsoft C++ REST SDK 1.3.1 and try to write HTML&Javascript as a client to interact with the server.

almost without surprise I got ...
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ...... (Reason: CORS header 'Access-Control-Allow-Origin' missing).

How can I put Access-Control-Allow-Origin:* on the http listener side (in c++ code)??

Is it possible in C++ REST 1.3.1??
is there workaround except JSONP?

Server

#include <cpprest/http_listener.h>
#include <cpprest/json.h>
using namespace web;
using namespace web::http;
using namespace web::http::experimental::listener;

http_listener httpSrv;
httpSrv->support(methods::GET, handle_get);

void handle_get(http_request request)
{
const json::value response;
request.reply(status_codes::OK, response);
}


Client
Client with jQuery v1.12.4 (bounded to jQuery UI v1.12.0)

$("button").click(function () {
$.get(rest_url, function(data, status){
console.log(status);
console.log(data);
});
});


----------------- UPDATE -----------------------

Solution from the answer

SERVER

http_listener httpSrv;
httpSrv.support(methods::GET, handle_get);
httpSrv.support(methods::POST, handle_post);
httpSrv.support(methods::OPTIONS, handle_options);
httpSrv.open().wait();

//...........

void handle_options(http_request request)
{
http_response response(status_codes::OK);
response.headers().add(U("Allow"), U("GET, POST, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, POST, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type"));
request.reply(response);
}

void handle_get(http_request request)
{
request.reply(status_codes::OK, ...);
}

void handle_post(http_request request)
{
json::value jsonResponse;

request
.extract_json()
.then([&jsonResponse](pplx::task<json::value> task)
{
jsonResponse = process_request(task.get());
})
.wait();

http_response response(status_codes::OK);
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.set_body(jsonResponse);
request.reply(response);
}


CLIENT

function requestREST(request/*json*/,onSuccess/*callback with json response*/) {
$.ajax({
type: "POST",
url: "...",
data: JSON.stringify(request),
dataType: 'json',
crossDomain: true,
contentType: "application/json",
success: function (response) {
onSuccess(response);
},
timeout:3000,
statusCode: {
400: function (response) {
alert('Not working!');
},
0: function (response) {
alert('Not working!');
}
}
});

Answer

To add headers on the server-side (C++), you'll need to modify the code you are using to send the response back.

At the moment, you're using:

request.reply(status_codes::OK, response);

Instead of doing that in a one-liner, the idea is to compose the response yourself starting from an empty response, add the desired header, set the actual body and then, send the response back to the client.

To constructs an empty response, we can use the following function:

web::http::http_response::http_response(http::status_code code)

As described in the documentation, it will constructs a response with a given status code, no headers and no body.

To access the headers of a response, we can use the following function:

web::http::http_response::headers()

The returned object will be of the http_headers type which contains a add function:

web::http::http_headers::add(const key_type &name, const _t1 &value)

This function will add a header to the response if it's provided a name and a value for the header.

When the header is set, the only thing remaining to set is the body. To do that, a response have the set_body function:

web::http::http_response::set_body(const json::value &body_data)

At the end, the complete code replacing your one-liner to create an empty response, set the header and the body and then send it back would look like:

http_response response(status_codes::OK);
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.set_body(jsonResponse);
request.reply(response);

Please note that in the last portion of the code, I'm using the U macro in order to create a string literal of the targeted platform type. You can find more informations regarding this U macro in the C++ Rest SDK FAQ.

Regarding preflight requests using the OPTION HTTP verb, these are expected in a situation like this. By default, the C++ REST SDK includes a default implementation for these requests. The default implementation can be checked in the source code:

void details::http_listener_impl::handle_options(http_request message)
{
    http_response response(status_codes::OK);
    response.headers().add(U("Allow"), get_supported_methods());
    message.reply(response);
}

It is basically returning a 200 status code and adding a list of the supported methods your server can handle.

If you want to override the default implementation, for example to add some specific headers used by preflight requests like Access-Control-Allow-Methods or Access-Control-Allow-Headers, you will need to add a specific handler like you did for the GET and POST requests using:

web::http::experimental::listener::http_listener::support(const http::method &method, const std::function< void(http_request)> &handler)

It is not possible to use a general handler to handle OPTION request with:

web::http::experimental::listener::http_listener::support(const std::function<void(http_request)> &handler)

The reason why we can't use a general handler, if we take a look at the source code, is that if a method does not have a specific handler and is using the OPTION HTTP verb (or TRACE), the default handler implemented by the C++ REST SDK will be called:

// Specific method handler takes priority over general.
const method &mtd = msg.method();
if(m_supported_methods.count(mtd))
{
    m_supported_methods[mtd](msg);
}
else if(mtd == methods::OPTIONS)
{
    handle_options(msg);
}
Comments