1

I'm trying to implement a C++ server to generate event for a javascript EventSource, I'm building it with cpprest. From the examples I've seen in PHP or Node.js, it looked pretty straight-forward but I must be missing something since I'm getting this in the Firefox console:

Firefox can’t establish a connection to the server at http://localhost:32123/data.

With Postman, I'm correctly receiving "data : test" so I think I'm missing some continuation, probably have to do something more than just reply to the request, but I didn't find a good explanation on how this is supposed to work yet. If you have some documentation you could point me to, that would be greatly appreciated!

The html page script looks like this:

var source = new EventSource("http://localhost:32123/data");

source.onmessage = function (event) {
    document.getElementById("result1").innerHTML += event.data + "<br>";
};

The C++ server reponse :

wResponse.set_status_code(status_codes::OK);
wResponse.headers().add(U("Access-Control-Allow-Origin"), U("*"));
wResponse.set_body(U("data: test"));
iRequest.reply(wResponse);

And the request my server is receiving:

GET /data HTTP/1.1
Accept: text/event-stream
Accept-Encoding: gzip, deflate
Accept-Language: en-us, en;q=0.5
Cache-Control: no-cache
Connection: keep-alive
Host: localhost:32123
Origin: null
Pragma: no-cache
User-Agent: Mozilla/5.0 (Windows NT6.1; Win64, x64; rv:61.0) Gecko/20100101 Firefox/61.0
VincentDM
  • 469
  • 6
  • 17
  • Did you consider using open source HTTP server libraries (like [libonion](https://coralbits.com/libonion/)...) or client libraries (like [libcurl](https://curl.haxx.se/libcurl/)...) ? Since HTTP is quite a complex protocol. – Basile Starynkevitch Aug 22 '18 at 10:26
  • I did not know about libonion, I will definitly check it out! HTTP is in fact quite complex but something I like about CppRest is all they did on the async side, and the `pplx::task` continuation concept (`task1.then(task2).then(task3)`) is also something I'd like to experiment with. [CppRest](https://github.com/Microsoft/cpprestsdk) is also open source. – VincentDM Aug 22 '18 at 10:40

1 Answers1

1

Found a solution here

Here's a small proof. It's not perfect at all but it's working. Next step is to figure how to store the connections, check that they're alive, etc...

EDIT : updating answer after Darren's comment

The proper solution seems to revolve around feeding a producer_consumer_buffer<char> bound to a basic_istream<uint8_t> that is set as the http_response body.

Then once the http_request::reply is done, the connection will stay opened until the buffer is closed, which could be done with wBuffer.close(std::ios_base::out).wait();.

I'm not 100% sure, but it seems that wBuffer.sync().wait(); acts like the PHP flush command would be used in a similar event-providing-server scenario.

A working example has been added below.

This is not a complete solution, obviously. There's still more fun ahead with managing the connections and all. Instanciating some Connection with make_unique and storing them to an container visited on events would probably be my way to go...

main.cpp

#include "cpprest/uri.h"
#include "cpprest/producerconsumerstream.h"
#include "cpprest/http_listener.h"

using namespace std;
using namespace web;
using namespace http;
using namespace utility;
using namespace concurrency;
using namespace http::experimental::listener;

struct MyServer
{
  MyServer(string_t url);
  pplx::task<void> open()  { return mListener.open(); };
  pplx::task<void> close() { return mListener.close(); };

private:

  void handleGet(http_request iRequest);
  http_listener mListener;
};

MyServer::MyServer(utility::string_t url) : mListener(url)
{
  mListener.support(methods::GET, bind(&MyServer::handleGet, this, placeholders::_1));
}

void MyServer::handleGet(http_request iRequest)
{
  ucout << iRequest.to_string() << endl;

  http_response wResponse;

  // Setting headers
  wResponse.set_status_code(status_codes::OK);
  wResponse.headers().add(header_names::access_control_allow_origin, U("*"));
  wResponse.headers().add(header_names::content_type, U("text/event-stream"));

  // Preparing buffer
  streams::producer_consumer_buffer<char> wBuffer;
  streams::basic_istream<uint8_t> wStream(wBuffer);
  wResponse.set_body(wStream);

  auto wReplyTask = iRequest.reply(wResponse);

  wBuffer.putn_nocopy("data: a\n",10).wait();
  wBuffer.putn_nocopy("data: b\n\n",12).wait();
  wBuffer.sync().wait();  // seems equivalent to 'flush'

  this_thread::sleep_for(chrono::milliseconds(2000));

  wBuffer.putn_nocopy("data: c\n", 10).wait();
  wBuffer.putn_nocopy("data: d\n\n", 12).wait();
  wBuffer.sync().wait();
  // wBuffer.close(std::ios_base::out).wait();    // closes the connection
  wReplyTask.wait();      // blocking!
}

unique_ptr<MyServer> gHttp;

void onInit(const string_t iAddress)
{
  uri_builder wUri(iAddress);
  auto wAddress = wUri.to_uri().to_string();
  gHttp = unique_ptr<MyServer>(new MyServer(wAddress));

  gHttp->open().wait();
  ucout << string_t(U("Listening for requests at: ")) << wAddress << endl;

}

void onShutdown()
{
  gHttp->close().wait();
}


void main(int argc, wchar_t* argv[])
{

  onInit(U("http://*:32123"));

  cout << "Wait until connection occurs..." << endl;
  getchar();

  onShutdown();
}

sse.htm

<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
</head>
    <body>
        <div id="result"></div>
    </body>
</html>

<script>

    if (typeof (EventSource) !== undefined)
    {
        document.getElementById("result").innerHTML += "SSE supported" + "<br>";
    } 
    else
    {
        document.getElementById("result").innerHTML += "SSE NOT supported" + "<br>";
    }

    var source = new EventSource("http://localhost:32123/");

    source.onopen = function ()
    {
        document.getElementById("result").innerHTML += "open" + "<br>";
    };

    source.onerror = function ()
    {        
        document.getElementById("result").innerHTML += "error" + "<br>";
    };

    source.onmessage = function (event) {
        document.getElementById("result").innerHTML += event.data + "<br>";
    };

</script>
VincentDM
  • 469
  • 6
  • 17
  • It is better to describe or summarize what the link is saying rather than just give the link. – Darren Cook Aug 22 '18 at 08:30
  • I think you are saying that rather than giving a fixed string to `wResponse.set_body(...);` you instead give it a reference to a stream. You can then keep writing data to that stream. Is calling `wReplyTask.wait();` the way of closing the connection from the server side? – Darren Cook Aug 22 '18 at 08:38
  • 1
    Hey @DarrenCook ! You're totally right, the answer was updated. Cheers. – VincentDM Aug 22 '18 at 10:23