Skip to content

Commit

Permalink
Added HttpReq class based on Boost.Asio.
Browse files Browse the repository at this point in the history
  • Loading branch information
Aloshi committed Sep 15, 2013
1 parent 8e12ff9 commit c807c98
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 3 deletions.
20 changes: 20 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ if(DEFINED BCMHOST)
add_definitions(-D_RPI_)
endif()

#-------------------------------------------------------------------------------
#set up _WIN32_WINNT variable (used by boost::asio) on Windows
macro(get_WIN32_WINNT version)
if (WIN32 AND CMAKE_SYSTEM_VERSION)
set(ver ${CMAKE_SYSTEM_VERSION})
string(REPLACE "." "" ver ${ver})
string(REGEX REPLACE "([0-9])" "0\\1" ver ${ver})

set(${version} "0x${ver}")
endif()
endmacro()

if(WIN32)
get_WIN32_WINNT(ver)
add_definitions(-D_WIN32_WINNT=${ver})
endif()
#-------------------------------------------------------------------------------

if(MSVC)
set(CMAKE_DEBUG_POSTFIX "d")
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
Expand Down Expand Up @@ -122,6 +140,7 @@ set(ES_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/Font.h
${CMAKE_CURRENT_SOURCE_DIR}/src/GameData.h
${CMAKE_CURRENT_SOURCE_DIR}/src/GuiComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/HttpReq.h
${CMAKE_CURRENT_SOURCE_DIR}/src/ImageIO.h
${CMAKE_CURRENT_SOURCE_DIR}/src/InputConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/src/InputManager.h
Expand Down Expand Up @@ -167,6 +186,7 @@ set(ES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/Font.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/GameData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/GuiComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/HttpReq.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/ImageIO.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/InputConfig.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/InputManager.cpp
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ Building
EmulationStation uses some C++11 code, which means you'll need to install at least g++-4.7 on Linux, or VS2010 on Windows.
For installing and switching to g++-4.7 see [here](http://lektiondestages.blogspot.de/2013/05/installing-and-switching-gccg-versions.html). You can also just use `export CXX=g++-4.7` to explicitly specify the compiler for CMake (make sure you delete your CMake cache files if it's not working).

EmulationStation has a few dependencies. For building, you'll need SDL2, Boost.System, Boost.Filesystem, FreeImage, FreeType, and Eigen3. You'll also need the DejaVu TrueType font on Linux to run ES.
EmulationStation has a few dependencies. For building, you'll need SDL2, Boost.System, Boost.Filesystem, Boost.Asio, FreeImage, FreeType, and Eigen3. You'll also need the DejaVu TrueType font on Linux to run ES.

**On Linux:**
All of this be easily installed with apt-get:
```
sudo apt-get install libsdl2-dev libboost-system-dev libboost-filesystem-dev libfreeimage-dev libfreetype6-dev libeigen3-dev ttf-dejavu libasound2-dev
sudo apt-get install libsdl2-dev libboost-dev libboost-system-dev libboost-filesystem-dev libfreeimage-dev libfreetype6-dev libeigen3-dev ttf-dejavu libasound2-dev
```

On "desktop" Linux (that is, *not* the Raspberry Pi), you'll also need OpenGL. Try installing the MESA development package with:
Expand All @@ -52,7 +52,7 @@ make

**On Windows:**

[Boost](http://www.boost.org/users/download/) (you'll need to compile for Boost.Filesystem)
[Boost](http://www.boost.org/users/download/) (you'll need to compile for Boost.Filesystem and Boost.System)

[Eigen3](http://eigen.tuxfamily.org/index.php?title=Main_Page)

Expand Down
194 changes: 194 additions & 0 deletions src/HttpReq.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
#include <iostream>
#include "HttpReq.h"
#include <boost/bind.hpp>
#include "Log.h"

boost::asio::io_service HttpReq::io_service;

HttpReq::HttpReq(const std::string& server, const std::string& path)
: mResolver(io_service), mSocket(io_service), mStatus(REQ_IN_PROGRESS)
{
std::ostream req_str(&mRequest);
req_str << "GET " << path << " HTTP/1.0\r\n";
req_str << "Host: " << server << "\r\n";
req_str << "Accept: */*\r\n";
req_str << "Connection: close\r\n\r\n";

tcp::resolver::query query(server, "http");
mResolver.async_resolve(query,
boost::bind(&HttpReq::handleResolve, this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator));
}


void HttpReq::handleResolve(const boost::system::error_code& err, tcp::resolver::iterator endpoint_iterator)
{
if (!err)
{
// Attempt a connection to each endpoint in the list until we
// successfully establish a connection.
boost::asio::async_connect(mSocket, endpoint_iterator,
boost::bind(&HttpReq::handleConnect, this,
boost::asio::placeholders::error));
}
else
{
onError(err);
}
}

void HttpReq::handleConnect(const boost::system::error_code& err)
{
if (!err)
{
// The connection was successful. Send the request.
boost::asio::async_write(mSocket, mRequest,
boost::bind(&HttpReq::handleWriteRequest, this,
boost::asio::placeholders::error));
}
else
{
onError(err);
}
}

void HttpReq::handleWriteRequest(const boost::system::error_code& err)
{
if (!err)
{
// Read the response status line. The response_ streambuf will
// automatically grow to accommodate the entire line. The growth may be
// limited by passing a maximum size to the streambuf constructor.
boost::asio::async_read_until(mSocket, mResponse, "\r\n",
boost::bind(&HttpReq::handleReadStatusLine, this,
boost::asio::placeholders::error));
}
else
{
onError(err);
}
}

void HttpReq::handleReadStatusLine(const boost::system::error_code& err)
{
if (!err)
{
// Check that response is OK.
std::istream response_stream(&mResponse);
std::string http_version;
response_stream >> http_version;
response_stream >> mResponseStatusCode;
std::string status_message;
std::getline(response_stream, status_message);
if(!response_stream || http_version.substr(0, 5) != "HTTP/")
{
mStatus = REQ_INVALID_RESPONSE;
return;
}
if(mResponseStatusCode != 200)
{
mStatus = REQ_BAD_STATUS_CODE;
return;
}

// Read the response headers, which are terminated by a blank line.
boost::asio::async_read_until(mSocket, mResponse, "\r\n\r\n",
boost::bind(&HttpReq::handleReadHeaders, this,
boost::asio::placeholders::error));
}
else
{
onError(err);
}
}

void HttpReq::handleReadHeaders(const boost::system::error_code& err)
{
if (!err)
{
// Process the response headers.
std::istream response_stream(&mResponse);
std::string header;
while (std::getline(response_stream, header) && header != "\r"); //and by process we mean ignore

// Write whatever content we already have to output.
if (mResponse.size() > 0)
mContent << &mResponse;

// Start reading remaining data until EOF.
boost::asio::async_read(mSocket, mResponse,
boost::asio::transfer_at_least(1),
boost::bind(&HttpReq::handleReadContent, this,
boost::asio::placeholders::error));
}
else
{
onError(err);
}
}

void HttpReq::handleReadContent(const boost::system::error_code& err)
{
if (!err)
{
// Write all of the data that has been read so far.
mContent << &mResponse;

// Continue reading remaining data until EOF.
boost::asio::async_read(mSocket, mResponse,
boost::asio::transfer_at_least(1),
boost::bind(&HttpReq::handleReadContent, this,
boost::asio::placeholders::error));
}else{
if (err != boost::asio::error::eof)
{
onError(err);
}else{
mStatus = REQ_SUCCESS;
}
}
}

HttpReq::Status HttpReq::status()
{
io_service.poll();
return mStatus;
}

std::string HttpReq::getContent()
{
if(mStatus != REQ_SUCCESS)
{
LOG(LogError) << "Called getContent() on an unsuccessful HttpReq!";
return "";
}

return mContent.str();
}

//only called for boost-level errors (REQ_IO_ERROR)
void HttpReq::onError(const boost::system::error_code& err)
{
mError = err;
mStatus = REQ_IO_ERROR;
}

std::string HttpReq::getErrorMsg()
{
switch(mStatus)
{
case REQ_BAD_STATUS_CODE:
return "Bad status code";
case REQ_INVALID_RESPONSE:
return "Invalid response from server";
case REQ_IO_ERROR:
return mError.message();
case REQ_IN_PROGRESS:
return "Not done yet";
case REQ_SUCCESS:
return "No error";
default:
return "???";
}
}
68 changes: 68 additions & 0 deletions src/HttpReq.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#pragma once

#include <boost/asio.hpp>

using boost::asio::ip::tcp;

//Based on: http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/example/http/client/async_client.cpp

/* Usage:
* HttpReq myRequest("www.google.com", "/index.html");
* //for blocking behavior: while(myRequest.status() == HttpReq::REQ_IN_PROGRESS);
* //for non-blocking behavior: check if(myRequest.status() != HttpReq::REQ_IN_PROGRESS) in some sort of update method
*
* //once one of those completes, the request is ready
* if(myRequest.status() != REQ_SUCCESS)
* {
* //an error occured
* LOG(LogError) << "HTTP request error - " << myRequest.getErrorMessage();
* return;
* }
*
* std::string content = myRequest.getContent();
* //process contents...
*/

class HttpReq
{
public:
HttpReq(const std::string& server, const std::string& path);

enum Status
{
REQ_IN_PROGRESS, //request is in progress
REQ_SUCCESS, //request completed successfully, get it with getContent()

REQ_IO_ERROR, //some boost::asio error happened, get it with getErrorMsg()
REQ_BAD_STATUS_CODE, //some invalid HTTP response status code happened (non-200)
REQ_INVALID_RESPONSE //the HTTP response was invalid
};

Status status(); //process any received data and return the status afterwards

std::string getErrorMsg();

std::string getContent();

private:
static boost::asio::io_service io_service;

void handleResolve(const boost::system::error_code& err, tcp::resolver::iterator endpoint_iterator);
void handleConnect(const boost::system::error_code& err);
void handleWriteRequest(const boost::system::error_code& err);
void handleReadStatusLine(const boost::system::error_code& err);
void handleReadHeaders(const boost::system::error_code& err);
void handleReadContent(const boost::system::error_code& err);

void onError(const boost::system::error_code& error);

tcp::resolver mResolver;
tcp::socket mSocket;
boost::asio::streambuf mRequest;
boost::asio::streambuf mResponse;

Status mStatus;
std::stringstream mContent;
unsigned int mResponseStatusCode;
boost::system::error_code mError;
};

0 comments on commit c807c98

Please sign in to comment.