I love seeing that there's a cross-platform standard for TCP/IP sockets emerging for C++ in boost. And so far I've been able to find help for all topics I've run into. But now I'm stuck on an odd behavior. I'm developing using Xcode 7.3.1 on an late-2013 iMac.
I'm developing a simple web server for a special purpose. The code below is a pared down version that demonstrates the bad behavior:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
using namespace std;
using namespace boost;
using namespace boost::asio;
using namespace boost::asio::ip;
int main(int argc, const char * argv[]) {
static asio::io_service ioService;
static tcp::acceptor tcpAcceptor(ioService, tcp::endpoint(tcp::v4(), 2080));
while (true) {
// creates a socket
tcp::socket* socket = new tcp::socket(ioService);
// wait and listen
tcpAcceptor.accept(*socket);
asio::streambuf inBuffer;
istream headerLineStream(&inBuffer);
char buffer[1];
asio::read(*socket, asio::buffer(buffer, 1)); // <--- Yuck!
asio::write(*socket, asio::buffer((string) "HTTP/1.1 200 OK\r\n\r\nYup!"));
socket->shutdown(asio::ip::tcp::socket::shutdown_both);
socket->close();
delete socket;
}
return 0;
}
When I access this service, under a certain set of conditions, the browser will choke for upwards of 20 seconds. If I pause the program running in debug mode, I can see that the asio::read() call is blocking. It's literally waiting for even a single character to appear from the browser. Why is this?
Let me clarify, because what I have to do to reproduce this on my machine is strange. Once I start the program (for debugging), I open the "page" from Chrome (as http://localhost:2080/). I can hit Refresh many times and it works just fine. But then I use Firefox (or Safari) and it hangs for maybe 20 seconds, whence the page shows up as expected. Now get this. If, during that delay in Firefox, I hit Refresh in Chrome, the Firefox page shows up immediately, too. In another experiment, I hit Refresh in Chrome (works fine) and then hit Refresh in both Firefox and Safari. Both of them hang. I hit Refresh in Chrome and all 3 show up immediately.
In a change to this experiment, as soon as I start this program, I hit Refresh in either Firefox or Safari and they work just fine. No matter how many times I refresh. And going back and forth between them. I'm literally holding down CMD-R to rapid-fire refresh these browsers. But as soon as I refresh Chrome on the same page and then try refreshing the other two browsers, they hang again.
Having done web programming since around 1993, I know the HTTP standard well. The most basic workflow is that the browser initiates a TCP connection. As soon as the web server accepts the connection, the client sends an HTTP header. Something like "GET /\r\n\r\n" for the root page ("/"). The server typically reads all the header lines and stops until it gets to the first blank line, which signals the end of the headers and beginning of the uploaded content (e.g., POSTed form content), which the web application is free to consume or ignore. The server responds when it is ready with its own HTTP headers, starting typically with "HTTP/1.1 200 OK\r\n", followed by the actual page content (or binary file contents, etc).
In my app, I'm actually using asio::read_until(*socket, inBuffer, "\r\n\r\n") to read the entire HTTP header. Since that was hanging, I thought maybe those other browsers were sending corrupt headers or something. Hence my trimming down of the sample to just reading a single character (should be the "G" in "GET /"). One single character. Nope.
As a side note, I know I'm doing this synchronously, but I really wanted a simple, linear demo to show this bad behavior. I'm assuming that's not what's causing this problem, but I know it's possible.
Any thoughts here? In my use case, this is sufferable, since the server does eventually respond, but I'd really rather understand eliminate this bad behavior.