|
|
@ -14,16 +14,22 @@ static const std::string kCRLF = "\r\n";
|
|
|
|
// NOTE:
|
|
|
|
// NOTE:
|
|
|
|
// Each header field consists of a name followed by a colon (":") and the
|
|
|
|
// Each header field consists of a name followed by a colon (":") and the
|
|
|
|
// field value. Field names are case-insensitive.
|
|
|
|
// field value. Field names are case-insensitive.
|
|
|
|
// See http://stackoverflow.com/questions/5258977/are-http-headers-case-sensitive
|
|
|
|
// See https://stackoverflow.com/a/5259004
|
|
|
|
static const std::string kFieldContentTypeName = "Content-Type";
|
|
|
|
static const std::string kFieldContentTypeName = "Content-Type";
|
|
|
|
static const std::string kFieldContentLengthName = "Content-Length";
|
|
|
|
static const std::string kFieldContentLengthName = "Content-Length";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const size_t kInvalidContentLength = std::string::npos;
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// NOTE (About Connection: keep-alive):
|
|
|
|
|
|
|
|
// Keep-alive is deprecated and no longer documented in the current HTTP/1.1
|
|
|
|
|
|
|
|
// specification.
|
|
|
|
|
|
|
|
// See https://stackoverflow.com/a/43451440
|
|
|
|
|
|
|
|
|
|
|
|
HttpRequest::HttpRequest(HttpVersion version)
|
|
|
|
HttpRequest::HttpRequest(HttpVersion version)
|
|
|
|
: version_(version)
|
|
|
|
: version_(version)
|
|
|
|
, keep_alive_(true)
|
|
|
|
, content_length_(0) {
|
|
|
|
, content_length_(std::string::npos) {
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void HttpRequest::ToString(std::string& req_string) const {
|
|
|
|
void HttpRequest::ToString(std::string& req_string) const {
|
|
|
@ -42,13 +48,17 @@ void HttpRequest::ToString(std::string& req_string) const {
|
|
|
|
|
|
|
|
|
|
|
|
// Header fields
|
|
|
|
// Header fields
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
req_string += kFieldContentTypeName;
|
|
|
|
|
|
|
|
req_string += ": ";
|
|
|
|
|
|
|
|
|
|
|
|
if (!content_type_.empty()) {
|
|
|
|
if (!content_type_.empty()) {
|
|
|
|
req_string += kFieldContentTypeName;
|
|
|
|
|
|
|
|
req_string += ": ";
|
|
|
|
|
|
|
|
req_string += content_type_;
|
|
|
|
req_string += content_type_;
|
|
|
|
req_string += kCRLF;
|
|
|
|
} else {
|
|
|
|
|
|
|
|
req_string += "text/xml; charset=utf-8";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
req_string += kCRLF;
|
|
|
|
|
|
|
|
|
|
|
|
req_string += kFieldContentLengthName;
|
|
|
|
req_string += kFieldContentLengthName;
|
|
|
|
req_string += ": ";
|
|
|
|
req_string += ": ";
|
|
|
|
req_string += LexicalCast<std::string>(content_length_, "0");
|
|
|
|
req_string += LexicalCast<std::string>(content_length_, "0");
|
|
|
@ -66,25 +76,20 @@ void HttpRequest::ToString(std::string& req_string) const {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
req_string += kCRLF;
|
|
|
|
req_string += kCRLF;
|
|
|
|
|
|
|
|
|
|
|
|
if (keep_alive_) {
|
|
|
|
req_string += kCRLF; // End of Headers.
|
|
|
|
req_string += "Connection: Keep-Alive";
|
|
|
|
|
|
|
|
req_string += kCRLF;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
req_string += kCRLF;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
HttpResponse::HttpResponse()
|
|
|
|
HttpResponse::HttpResponse()
|
|
|
|
: status_(0)
|
|
|
|
: status_(0)
|
|
|
|
, content_length_(0)
|
|
|
|
, content_length_(kInvalidContentLength)
|
|
|
|
, start_line_parsed_(false)
|
|
|
|
, start_line_parsed_(false)
|
|
|
|
, header_parsed_(false)
|
|
|
|
, header_parsed_(false)
|
|
|
|
, finished_(false) {
|
|
|
|
, finished_(false) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void HttpResponse::Parse(const char* data, size_t len) {
|
|
|
|
ErrorCode HttpResponse::Parse(const char* data, size_t len) {
|
|
|
|
if (header_parsed_) {
|
|
|
|
if (header_parsed_) {
|
|
|
|
// Add the data to the content.
|
|
|
|
// Add the data to the content.
|
|
|
|
content_.append(data, len);
|
|
|
|
content_.append(data, len);
|
|
|
@ -92,10 +97,9 @@ void HttpResponse::Parse(const char* data, size_t len) {
|
|
|
|
if (content_.length() >= content_length_) {
|
|
|
|
if (content_.length() >= content_length_) {
|
|
|
|
// All content has been read.
|
|
|
|
// All content has been read.
|
|
|
|
finished_ = true;
|
|
|
|
finished_ = true;
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
return kNoError;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pending_data_.append(data, len);
|
|
|
|
pending_data_.append(data, len);
|
|
|
@ -108,7 +112,7 @@ void HttpResponse::Parse(const char* data, size_t len) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (pos == off) { // End of headers.
|
|
|
|
if (pos == off) { // End of headers.
|
|
|
|
off = pos + 2; // Skip "\r\n".
|
|
|
|
off = pos + 2; // Skip CRLF.
|
|
|
|
header_parsed_ = true;
|
|
|
|
header_parsed_ = true;
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -116,17 +120,30 @@ void HttpResponse::Parse(const char* data, size_t len) {
|
|
|
|
std::string line = pending_data_.substr(off, pos - off);
|
|
|
|
std::string line = pending_data_.substr(off, pos - off);
|
|
|
|
|
|
|
|
|
|
|
|
if (!start_line_parsed_) {
|
|
|
|
if (!start_line_parsed_) {
|
|
|
|
ParseStartLine(line); // TODO: Error handling.
|
|
|
|
|
|
|
|
start_line_parsed_ = true;
|
|
|
|
start_line_parsed_ = true;
|
|
|
|
|
|
|
|
ErrorCode error = ParseStartLine(line);
|
|
|
|
|
|
|
|
if (error != kNoError) {
|
|
|
|
|
|
|
|
return error;
|
|
|
|
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
ParseHeaderField(line);
|
|
|
|
// Currently, only Content-Length is important to us.
|
|
|
|
|
|
|
|
// Other fields are ignored.
|
|
|
|
|
|
|
|
if (content_length_ == kInvalidContentLength) { // Not parsed yet.
|
|
|
|
|
|
|
|
ParseContentLength(line);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
off = pos + 2; // Skip "\r\n".
|
|
|
|
off = pos + 2; // Skip CRLF.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (header_parsed_) {
|
|
|
|
if (header_parsed_) {
|
|
|
|
// Headers just ended.
|
|
|
|
// Headers just ended.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (content_length_ == kInvalidContentLength) {
|
|
|
|
|
|
|
|
// No Content-Length?
|
|
|
|
|
|
|
|
return kHttpContentLengthError;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
content_ += pending_data_.substr(off);
|
|
|
|
content_ += pending_data_.substr(off);
|
|
|
|
|
|
|
|
|
|
|
|
if (content_.length() >= content_length_) {
|
|
|
|
if (content_.length() >= content_length_) {
|
|
|
@ -137,55 +154,68 @@ void HttpResponse::Parse(const char* data, size_t len) {
|
|
|
|
// Save the unparsed piece for next parsing.
|
|
|
|
// Save the unparsed piece for next parsing.
|
|
|
|
pending_data_ = pending_data_.substr(off);
|
|
|
|
pending_data_ = pending_data_.substr(off);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return kNoError;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool HttpResponse::ParseStartLine(const std::string& line) {
|
|
|
|
ErrorCode HttpResponse::ParseStartLine(const std::string& line) {
|
|
|
|
std::vector<std::string> parts;
|
|
|
|
size_t off = 0;
|
|
|
|
boost::split(parts, line, boost::is_any_of(" "), boost::token_compress_on);
|
|
|
|
|
|
|
|
|
|
|
|
size_t pos = line.find(' ');
|
|
|
|
|
|
|
|
if (pos == std::string::npos) {
|
|
|
|
|
|
|
|
return kHttpStartLineError;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// HTTP version
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
off = pos + 1; // Skip space.
|
|
|
|
|
|
|
|
|
|
|
|
if (parts.size() != 3) {
|
|
|
|
pos = line.find(' ', off);
|
|
|
|
return false;
|
|
|
|
if (pos == std::string::npos) {
|
|
|
|
|
|
|
|
return kHttpStartLineError;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Status code
|
|
|
|
|
|
|
|
std::string status_str = line.substr(off, pos - off);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
status_ = boost::lexical_cast<int>(parts[1]);
|
|
|
|
status_ = boost::lexical_cast<int>(status_str);
|
|
|
|
} catch (boost::bad_lexical_cast&) {
|
|
|
|
} catch (boost::bad_lexical_cast&) {
|
|
|
|
return false;
|
|
|
|
return kHttpStartLineError;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
reason_ = parts[2];
|
|
|
|
off = pos + 1; // Skip space.
|
|
|
|
|
|
|
|
reason_ = line.substr(off);
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
if (status_ != kHttpOK) {
|
|
|
|
|
|
|
|
return kHttpStatusError;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return kNoError;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool HttpResponse::ParseHeaderField(const std::string& line) {
|
|
|
|
void HttpResponse::ParseContentLength(const std::string& line) {
|
|
|
|
size_t pos = line.find(':');
|
|
|
|
size_t pos = line.find(':');
|
|
|
|
if (pos == std::string::npos) {
|
|
|
|
if (pos == std::string::npos) {
|
|
|
|
return false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::string name = line.substr(0, pos);
|
|
|
|
std::string name = line.substr(0, pos);
|
|
|
|
|
|
|
|
|
|
|
|
++pos; // Skip ':'.
|
|
|
|
if (boost::iequals(name, kFieldContentLengthName)) {
|
|
|
|
while (line[pos] == ' ') { // Skip spaces.
|
|
|
|
++pos; // Skip ':'.
|
|
|
|
++pos;
|
|
|
|
while (line[pos] == ' ') { // Skip spaces.
|
|
|
|
}
|
|
|
|
++pos;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::string value = line.substr(pos);
|
|
|
|
std::string value = line.substr(pos);
|
|
|
|
|
|
|
|
|
|
|
|
if (boost::iequals(name, kFieldContentTypeName)) {
|
|
|
|
|
|
|
|
content_type_ = value;
|
|
|
|
|
|
|
|
} else if (boost::iequals(name, kFieldContentLengthName)) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
content_length_ = boost::lexical_cast<size_t>(value);
|
|
|
|
content_length_ = boost::lexical_cast<size_t>(value);
|
|
|
|
} catch (boost::bad_lexical_cast&) {
|
|
|
|
} catch (boost::bad_lexical_cast&) {
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Unsupported, ignore.
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
@ -193,11 +223,11 @@ bool HttpResponse::ParseHeaderField(const std::string& line) {
|
|
|
|
HttpClient::HttpClient() {
|
|
|
|
HttpClient::HttpClient() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HttpClient::~HttpClient() {
|
|
|
|
ErrorCode HttpClient::SendRequest(const HttpRequest& request,
|
|
|
|
}
|
|
|
|
const std::string& body,
|
|
|
|
|
|
|
|
HttpResponse* response) {
|
|
|
|
|
|
|
|
assert(response != NULL);
|
|
|
|
|
|
|
|
|
|
|
|
bool HttpClient::SendRequest(const HttpRequest& request,
|
|
|
|
|
|
|
|
const std::string& body) {
|
|
|
|
|
|
|
|
using boost::asio::ip::tcp;
|
|
|
|
using boost::asio::ip::tcp;
|
|
|
|
|
|
|
|
|
|
|
|
tcp::socket socket(io_service_);
|
|
|
|
tcp::socket socket(io_service_);
|
|
|
@ -211,17 +241,17 @@ bool HttpClient::SendRequest(const HttpRequest& request,
|
|
|
|
|
|
|
|
|
|
|
|
tcp::resolver::query query(request.host(), port);
|
|
|
|
tcp::resolver::query query(request.host(), port);
|
|
|
|
|
|
|
|
|
|
|
|
boost::system::error_code error;
|
|
|
|
boost::system::error_code ec;
|
|
|
|
tcp::resolver::iterator it = resolver.resolve(query, error);
|
|
|
|
tcp::resolver::iterator it = resolver.resolve(query, ec);
|
|
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
if (ec) {
|
|
|
|
return false;
|
|
|
|
return kHostResolveError;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
socket.connect(*it, error);
|
|
|
|
socket.connect(*it, ec);
|
|
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
if (ec) {
|
|
|
|
return false;
|
|
|
|
return kEndpointConnectError;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::string request_str;
|
|
|
|
std::string request_str;
|
|
|
@ -231,23 +261,31 @@ bool HttpClient::SendRequest(const HttpRequest& request,
|
|
|
|
boost::asio::write(socket, boost::asio::buffer(request_str));
|
|
|
|
boost::asio::write(socket, boost::asio::buffer(request_str));
|
|
|
|
boost::asio::write(socket, boost::asio::buffer(body));
|
|
|
|
boost::asio::write(socket, boost::asio::buffer(body));
|
|
|
|
} catch (boost::system::system_error&) {
|
|
|
|
} catch (boost::system::system_error&) {
|
|
|
|
return false;
|
|
|
|
return kSocketWriteError;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Read and parse HTTP response.
|
|
|
|
// Read and parse HTTP response.
|
|
|
|
|
|
|
|
|
|
|
|
while (!response_.finished()) {
|
|
|
|
// We must stop trying to read some once all content has been received,
|
|
|
|
try {
|
|
|
|
// because some servers will block extra call to read_some().
|
|
|
|
size_t len = socket.read_some(boost::asio::buffer(bytes_));
|
|
|
|
while (!response->finished()) {
|
|
|
|
response_.Parse(bytes_.data(), len);
|
|
|
|
size_t len = socket.read_some(boost::asio::buffer(bytes_), ec);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (boost::system::system_error&) {
|
|
|
|
if (len == 0 || ec) {
|
|
|
|
// Should be EOF, but ...
|
|
|
|
return kSocketReadError;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Parse the response piece just read.
|
|
|
|
|
|
|
|
// If the content has been fully received, next time flag "finished_" will
|
|
|
|
|
|
|
|
// be set.
|
|
|
|
|
|
|
|
ErrorCode error = response->Parse(bytes_.data(), len);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (error != kNoError) {
|
|
|
|
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
return kNoError;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace csoap
|
|
|
|
} // namespace csoap
|
|
|
|