2

Implementing a simple HTTP server in Qt, with the purpose of streaming real time data to an XMLHttpRequest object (AJAX/JavaScript).

The problem is that the design pattern requires partial transmission of data via the socket connection, changing the readyState in the XHR from '1' (Request) to '2' (Headers received), and then to '3' (Data received) - keeping the request pending. This is also known as "long-polling", or "Comet" and should be possible in most browsers.

However, it stays in request state until the connection is closed, and then readyState '2' and '4' are received. This is normal for HTTP GET, but not desired for this application.

JavaScript:

var request = new XMLHttpRequest();
request.onreadystatechange = function() {
    console.log('readyState: ' + this.readyState + ' ' + this.status)
}
request.open("get", "localhost:8080/", true);
request.send();

Qt:

connect(socket, &QTcpSocket::readyRead, [=]()
{
    QByteArray data = m_socket->read(1000);

    socket->write("HTTP/1.1 200 OK\r\n");
    socket->write("Content-Type: text/octet-stream\r\n");
    socket->write("Access-Control-Allow-Origin: *\r\n");
    socket->write("Cache-Control: no-cache, no-store, max-age=0, must-revalidate\r\n");
    socket->flush();
}

So the big question is: How can I make the network system underneath the QtTcpSocket flush pending data after writing the headers (and later, the data), without the need to disconnect first?

A side note: I originally implemented this using WebSockets, but the browser I have to use does not support this.


EDIT: The HTTP header formatting must have an extra set of "\r\n". Now it works:

connect(socket, &QTcpSocket::readyRead, [=]()
{
    QByteArray data = m_socket->read(1000);

    socket->write("HTTP/1.1 200 OK\r\n");
    socket->write("Content-Type: text/octet-stream\r\n");
    socket->write("Access-Control-Allow-Origin: *\r\n");
    socket->write("Cache-Control: no-cache, no-store, max-age=0, must-revalidate\r\n");
    socket->write("\r\n");
    socket->flush();
}
Morten Priess
  • 123
  • 1
  • 1
  • 7
  • This is more of a HTTP question. Are you sure the data you send on the wire constitute a valid HTTP reply? Have you compared them with the reply from a conforming, working HTTP server? – peppe Apr 03 '16 at 13:55
  • It might be an HTTP header problem, but I eliminated that because it works when doing a `disconnectFromHost()` after `flush()`, so that should validate the HTTP formatting - at least in terms of standard GET request/reply. – Morten Priess Apr 03 '16 at 14:06
  • Your `readyRead` implementation assumes that it will get 1000 bytes all at once. That assumption is erroneous, hopefully your real `readyRead`-attached functor assembles the incoming data from chunks of arbitrary size; see e.g. [this answer](http://stackoverflow.com/a/36319381/1329652) and the other one linked in it. – Kuba hasn't forgotten Monica Apr 04 '16 at 19:55

1 Answers1

0

Got it working now after a full day of trying different HTTP header configurations. It seems like user 'peppe' was on to something, and the only thing I needed was to add "\r\n" after the headers! (See edit).

Morten Priess
  • 123
  • 1
  • 1
  • 7
  • When writing code that speaks a given protocol it helps to read protocol's documentation, especially that it is free and is the first result when googling `http protocol` :) From [RFC7320](http://tools.ietf.org/html/rfc7230#page-19), emphasis mine: HTTP/1.1 messages consist of a start-line followed by a sequence of octets in a format similar to the Internet Message Format [RFC5322]: zero or more header fields (collectively referred to as the "headers" or the "header section"), **an empty line indicating the end of the header section**, and an optional message body. – Kuba hasn't forgotten Monica Apr 04 '16 at 19:58