diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b51fffc6..9912bc314 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -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 @@ -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 diff --git a/README.md b/README.md index f0638b909..48902ea02 100644 --- a/README.md +++ b/README.md @@ -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: @@ -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) diff --git a/src/HttpReq.cpp b/src/HttpReq.cpp new file mode 100644 index 000000000..58c17bb5d --- /dev/null +++ b/src/HttpReq.cpp @@ -0,0 +1,194 @@ +#include +#include "HttpReq.h" +#include +#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 "???"; + } +} diff --git a/src/HttpReq.h b/src/HttpReq.h new file mode 100644 index 000000000..ee8d2b1f7 --- /dev/null +++ b/src/HttpReq.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +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; +};