Added HttpReq class based on Boost.Asio.

This commit is contained in:
Aloshi 2013-09-15 12:56:47 -05:00
parent 8e12ff9506
commit c807c98b4a
4 changed files with 285 additions and 3 deletions

View file

@ -49,6 +49,24 @@ if(DEFINED BCMHOST)
add_definitions(-D_RPI_) add_definitions(-D_RPI_)
endif() 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) if(MSVC)
set(CMAKE_DEBUG_POSTFIX "d") set(CMAKE_DEBUG_POSTFIX "d")
add_definitions(-D_CRT_SECURE_NO_DEPRECATE) add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
@ -122,6 +140,7 @@ set(ES_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/Font.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Font.h
${CMAKE_CURRENT_SOURCE_DIR}/src/GameData.h ${CMAKE_CURRENT_SOURCE_DIR}/src/GameData.h
${CMAKE_CURRENT_SOURCE_DIR}/src/GuiComponent.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/ImageIO.h
${CMAKE_CURRENT_SOURCE_DIR}/src/InputConfig.h ${CMAKE_CURRENT_SOURCE_DIR}/src/InputConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/src/InputManager.h ${CMAKE_CURRENT_SOURCE_DIR}/src/InputManager.h
@ -167,6 +186,7 @@ set(ES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/Font.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Font.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/GameData.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/GameData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/GuiComponent.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/ImageIO.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/InputConfig.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/InputConfig.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/InputManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/InputManager.cpp

View file

@ -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. 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). 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:** **On Linux:**
All of this be easily installed with apt-get: 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: On "desktop" Linux (that is, *not* the Raspberry Pi), you'll also need OpenGL. Try installing the MESA development package with:
@ -52,7 +52,7 @@ make
**On Windows:** **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) [Eigen3](http://eigen.tuxfamily.org/index.php?title=Main_Page)

194
src/HttpReq.cpp Normal file
View file

@ -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
src/HttpReq.h Normal file
View file

@ -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;
};