You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

135 lines
3.3 KiB
C++

#include "webcc/http_client.h"
8 years ago
#if 0
#include "boost/asio.hpp"
#else
#include "boost/asio/connect.hpp"
#include "boost/asio/ip/tcp.hpp"
#include "boost/asio/read.hpp"
#include "boost/asio/write.hpp"
#endif
8 years ago
#include "webcc/logger.h"
#include "webcc/http_response_parser.h"
#include "webcc/http_request.h"
#include "webcc/http_response.h"
namespace webcc {
8 years ago
////////////////////////////////////////////////////////////////////////////////
// See https://stackoverflow.com/a/9079092
static void SetTimeout(boost::asio::ip::tcp::socket& socket,
int timeout_seconds) {
#if defined _WINDOWS
int ms = timeout_seconds * 1000;
8 years ago
const char* optval = reinterpret_cast<const char*>(&ms);
std::size_t optlen = sizeof(ms);
8 years ago
setsockopt(socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, optval, optlen);
setsockopt(socket.native_handle(), SOL_SOCKET, SO_SNDTIMEO, optval, optlen);
8 years ago
#else // POSIX
8 years ago
// TODO: This doesn't work! Consider to control timeout in server side.
struct timeval tv;
tv.tv_sec = timeout_seconds;
tv.tv_usec = 0;
setsockopt(socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(socket.native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
8 years ago
#endif
8 years ago
}
////////////////////////////////////////////////////////////////////////////////
HttpClient::HttpClient()
: timeout_seconds_(15) {
8 years ago
}
Error HttpClient::SendRequest(const HttpRequest& request,
HttpResponse* response) {
assert(response != NULL);
8 years ago
using boost::asio::ip::tcp;
tcp::socket socket(io_context_);
8 years ago
tcp::resolver resolver(io_context_);
8 years ago
std::string port = request.port();
if (port.empty()) {
port = "80";
}
boost::system::error_code ec;
// tcp::resolver::results_type
auto endpoints = resolver.resolve(tcp::v4(), request.host(), port, ec);
if (ec) {
LOG_ERRO("cannot resolve host: %s, %s",
request.host().c_str(),
port.c_str());
return kHostResolveError;
8 years ago
}
boost::asio::connect(socket, endpoints, ec);
if (ec) {
return kEndpointConnectError;
8 years ago
}
SetTimeout(socket, timeout_seconds_);
// Send HTTP request.
LOG_VERB("http request:\n{\n%s}", request.Dump().c_str());
8 years ago
try {
boost::asio::write(socket, request.ToBuffers());
8 years ago
} catch (boost::system::system_error&) {
return kSocketWriteError;
8 years ago
}
// Read and parse HTTP response.
HttpResponseParser parser(response);
// NOTE:
// We must stop trying to read once all content has been received,
// because some servers will block extra call to read_some().
while (!parser.finished()) {
// read_some() will block until one or more bytes of data has been
// read successfully, or until an error occurs.
std::size_t length = socket.read_some(boost::asio::buffer(buffer_), ec);
8 years ago
if (length == 0 || ec) {
#if defined _WINDOWS
if (ec.value() == WSAETIMEDOUT) {
return kSocketTimeoutError;
}
#endif
return kSocketReadError;
}
// Parse the response piece just read.
// If the content has been fully received, next time flag "finished_"
// will be set.
Error error = parser.Parse(buffer_.data(), length);
if (error != kNoError) {
LOG_ERRO("failed to parse http response.");
return error;
8 years ago
}
}
LOG_VERB("http response:\n{\n%s}", response->Dump().c_str());
return kNoError;
}
} // namespace webcc