X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=simgear%2Fio%2FHTTPRequest.cxx;h=6b06785ad00bf57d79fb12e77b971a9475362138;hb=72bb9f4d5d6d861902a5779381e4ebe977db1df1;hp=84855a4c7d290c879fca3160b18e4a8744437346;hpb=69ef461e6debc526ce66493af2c6da9119a6f8c2;p=simgear.git diff --git a/simgear/io/HTTPRequest.cxx b/simgear/io/HTTPRequest.cxx index 84855a4c..6b06785a 100644 --- a/simgear/io/HTTPRequest.cxx +++ b/simgear/io/HTTPRequest.cxx @@ -1,224 +1,424 @@ +// Copyright (C) 2011 James Turner +// Copyright (C) 2013 Thomas Geymayer +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + #include "HTTPRequest.hxx" -#include #include #include - -using std::string; -using std::map; +#include +#include +#include namespace simgear { - namespace HTTP { extern const int DEFAULT_HTTP_PORT; -Request::Request(const string& url, const string method) : - _method(method), - _url(url), - _responseVersion(HTTP_VERSION_UNKNOWN), - _responseStatus(0), - _responseLength(0), - _receivedBodyBytes(0), - _willClose(false) +//------------------------------------------------------------------------------ +Request::Request(const std::string& url, const std::string method): + _client(0), + _method(method), + _url(url), + _responseVersion(HTTP_VERSION_UNKNOWN), + _responseStatus(0), + _responseLength(0), + _receivedBodyBytes(0), + _ready_state(UNSENT), + _willClose(false), + _connectionCloseHeader(false) { - + } +//------------------------------------------------------------------------------ Request::~Request() { - + +} + +//------------------------------------------------------------------------------ +Request* Request::done(const Callback& cb) +{ + if( _ready_state == DONE ) + cb(this); + else + _cb_done.push_back(cb); + + return this; } -void Request::setUrl(const string& url) +//------------------------------------------------------------------------------ +Request* Request::fail(const Callback& cb) { - _url = url; + if( _ready_state == FAILED ) + cb(this); + else + _cb_fail.push_back(cb); + + return this; } -string_list Request::requestHeaders() const +//------------------------------------------------------------------------------ +Request* Request::always(const Callback& cb) { - string_list r; - return r; + if( isComplete() ) + cb(this); + else + _cb_always.push_back(cb); + + return this; } -string Request::header(const std::string& name) const +//------------------------------------------------------------------------------ +void Request::setBodyData( const std::string& data, + const std::string& type ) { - return string(); + _request_data = data; + _request_media_type = type; + + if( !data.empty() && _method == "GET" ) + _method = "POST"; } -void Request::responseStart(const string& r) +//---------------------------------------------------------------------------- +void Request::setBodyData(const SGPropertyNode* data) +{ + if( !data ) + setBodyData(""); + + std::stringstream buf; + writeProperties(buf, data, true); + + setBodyData(buf.str(), "application/xml"); +} + +//------------------------------------------------------------------------------ +void Request::setUrl(const std::string& url) +{ + _url = url; +} + +//------------------------------------------------------------------------------ +void Request::requestStart() +{ + setReadyState(OPENED); +} + +//------------------------------------------------------------------------------ +Request::HTTPVersion decodeHTTPVersion(const std::string& v) +{ + if( v == "HTTP/1.1" ) return Request::HTTP_1_1; + if( v == "HTTP/1.0" ) return Request::HTTP_1_0; + if( strutils::starts_with(v, "HTTP/0.") ) return Request::HTTP_0_x; + return Request::HTTP_VERSION_UNKNOWN; +} + +//------------------------------------------------------------------------------ +void Request::responseStart(const std::string& r) { const int maxSplit = 2; // HTTP/1.1 nnn reason-string string_list parts = strutils::split(r, NULL, maxSplit); if (parts.size() != 3) { - SG_LOG(SG_IO, SG_WARN, "HTTP::Request: malformed response start:" << r); - setFailure(400, "malformed HTTP response header"); - return; + throw sg_io_exception("bad HTTP response:" + r); } - - _responseVersion = decodeVersion(parts[0]); + + _responseVersion = decodeHTTPVersion(parts[0]); _responseStatus = strutils::to_int(parts[1]); _responseReason = parts[2]; + + setReadyState(STATUS_RECEIVED); } -void Request::responseHeader(const string& key, const string& value) +//------------------------------------------------------------------------------ +void Request::responseHeader(const std::string& key, const std::string& value) { - if (key == "connection") { - _willClose = (value.find("close") != string::npos); - } - - _responseHeaders[key] = value; + if( key == "connection" ) { + _connectionCloseHeader = (value.find("close") != std::string::npos); + // track willClose seperately because other conditions (abort, for + // example) can also set it + _willClose = _connectionCloseHeader; + } else if (key == "content-length") { + int sz = strutils::to_int(value); + setResponseLength(sz); + } + + _responseHeaders[key] = value; } +//------------------------------------------------------------------------------ void Request::responseHeadersComplete() { - // no op + setReadyState(HEADERS_RECEIVED); } -void Request::processBodyBytes(const char* s, int n) +//------------------------------------------------------------------------------ +void Request::responseComplete() { - _receivedBodyBytes += n; - gotBodyData(s, n); + if( !isComplete() ) + setReadyState(DONE); } +//------------------------------------------------------------------------------ void Request::gotBodyData(const char* s, int n) +{ + setReadyState(LOADING); +} + +//------------------------------------------------------------------------------ +void Request::onDone() { } -void Request::responseComplete() +//------------------------------------------------------------------------------ +void Request::onFail() +{ + SG_LOG + ( + SG_IO, + SG_INFO, + "request failed:" << url() << " : " + << responseCode() << "/" << responseReason() + ); +} + +//------------------------------------------------------------------------------ +void Request::onAlways() { - + } - -string Request::scheme() const + +//------------------------------------------------------------------------------ +void Request::processBodyBytes(const char* s, int n) +{ + _receivedBodyBytes += n; + gotBodyData(s, n); +} + +//------------------------------------------------------------------------------ +std::string Request::scheme() const { int firstColon = url().find(":"); if (firstColon > 0) { return url().substr(0, firstColon); } - + return ""; // couldn't parse scheme } - -string Request::path() const + +//------------------------------------------------------------------------------ +std::string Request::path() const { - string u(url()); + std::string u(url()); int schemeEnd = u.find("://"); if (schemeEnd < 0) { return ""; // couldn't parse scheme } - + int hostEnd = u.find('/', schemeEnd + 3); - if (hostEnd < 0) { - return ""; // couldn't parse host + if (hostEnd < 0) { +// couldn't parse host, or URL looks like 'http://foo.com' (no trailing '/') +// fixup to root resource path: '/' + return "/"; } - + int query = u.find('?', hostEnd + 1); if (query < 0) { // all remainder of URL is path return u.substr(hostEnd); } - + return u.substr(hostEnd, query - hostEnd); } - -string Request::query() const +//------------------------------------------------------------------------------ +std::string Request::query() const { - string u(url()); + std::string u(url()); int query = u.find('?'); if (query < 0) { return ""; //no query string found } - + return u.substr(query); //includes question mark } - - -string Request::host() const +//------------------------------------------------------------------------------ +std::string Request::host() const { - string hp(hostAndPort()); - int colonPos = hp.find(':'); - if (colonPos >= 0) { - return hp.substr(0, colonPos); // trim off the colon and port - } else { - return hp; // no port specifier - } + std::string hp(hostAndPort()); + int colonPos = hp.find(':'); + if (colonPos >= 0) { + return hp.substr(0, colonPos); // trim off the colon and port + } else { + return hp; // no port specifier + } } +//------------------------------------------------------------------------------ unsigned short Request::port() const { - string hp(hostAndPort()); - int colonPos = hp.find(':'); - if (colonPos >= 0) { - return (unsigned short) strutils::to_int(hp.substr(colonPos + 1)); - } else { - return DEFAULT_HTTP_PORT; - } + std::string hp(hostAndPort()); + int colonPos = hp.find(':'); + if (colonPos >= 0) { + return (unsigned short) strutils::to_int(hp.substr(colonPos + 1)); + } else { + return DEFAULT_HTTP_PORT; + } } -string Request::hostAndPort() const +//------------------------------------------------------------------------------ +std::string Request::hostAndPort() const { - string u(url()); - int schemeEnd = u.find("://"); - if (schemeEnd < 0) { - return ""; // couldn't parse scheme - } - - int hostEnd = u.find('/', schemeEnd + 3); - if (hostEnd < 0) { // all remainder of URL is host - return u.substr(schemeEnd + 3); - } - - return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3)); + std::string u(url()); + int schemeEnd = u.find("://"); + if (schemeEnd < 0) { + return ""; // couldn't parse scheme + } + + int hostEnd = u.find('/', schemeEnd + 3); + if (hostEnd < 0) { // all remainder of URL is host + return u.substr(schemeEnd + 3); + } + + return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3)); } +//------------------------------------------------------------------------------ +std::string Request::responseMime() const +{ + std::string content_type = _responseHeaders.get("content-type"); + if( content_type.empty() ) + return "application/octet-stream"; + + return content_type.substr(0, content_type.find(';')); +} + +//------------------------------------------------------------------------------ void Request::setResponseLength(unsigned int l) { - _responseLength = l; + _responseLength = l; } +//------------------------------------------------------------------------------ unsigned int Request::responseLength() const { -// if the server didn't supply a content length, use the number -// of bytes we actually received (so far) - if ((_responseLength == 0) && (_receivedBodyBytes > 0)) { - return _receivedBodyBytes; - } - - return _responseLength; + // if the server didn't supply a content length, use the number + // of bytes we actually received (so far) + if( (_responseLength == 0) && (_receivedBodyBytes > 0) ) + return _receivedBodyBytes; + + return _responseLength; } +//------------------------------------------------------------------------------ void Request::setFailure(int code, const std::string& reason) { - _responseStatus = code; - _responseReason = reason; - failed(); + SG_LOG(SG_IO, SG_WARN, "HTTP request: set failure:" << code << " reason " << reason); + _responseStatus = code; + _responseReason = reason; + + if( !isComplete() ) + setReadyState(FAILED); } -void Request::failed() +//------------------------------------------------------------------------------ +void Request::setReadyState(ReadyState state) { - // no-op in base class + _ready_state = state; + if( state == DONE ) + { + // Finish C++ part of request to ensure everything is finished (for example + // files and streams are closed) before calling any callback (possibly using + // such files) + onDone(); + onAlways(); + + _cb_done(this); + } + else if( state == FAILED ) + { + onFail(); + onAlways(); + + _cb_fail(this); + } + else + return; + + _cb_always(this); } -Request::HTTPVersion Request::decodeVersion(const string& v) +//------------------------------------------------------------------------------ +bool Request::closeAfterComplete() const { - if (v == "HTTP/1.1") return HTTP_1_1; - if (v == "HTTP/1.0") return HTTP_1_0; - if (strutils::starts_with(v, "HTTP/0.")) return HTTP_0_x; - return HTTP_VERSION_UNKNOWN; + // for non HTTP/1.1 connections, assume server closes + return _willClose || (_responseVersion != HTTP_1_1); } -bool Request::closeAfterComplete() const +//------------------------------------------------------------------------------ + +void Request::setCloseAfterComplete() { -// for non HTTP/1.1 connections, assume server closes - return _willClose || (_responseVersion != HTTP_1_1); + _willClose = true; } -} // of namespace HTTP +//------------------------------------------------------------------------------ +bool Request::serverSupportsPipelining() const +{ + return (_responseVersion == HTTP_1_1) && !_connectionCloseHeader; +} + +//------------------------------------------------------------------------------ +bool Request::isComplete() const +{ + return _ready_state == DONE || _ready_state == FAILED; +} + +//------------------------------------------------------------------------------ +bool Request::hasBodyData() const +{ + return !_request_media_type.empty(); +} + +//------------------------------------------------------------------------------ +std::string Request::bodyType() const +{ + return _request_media_type; +} + +//------------------------------------------------------------------------------ +size_t Request::bodyLength() const +{ + return _request_data.length(); +} +//------------------------------------------------------------------------------ +size_t Request::getBodyData(char* s, size_t offset, size_t max_count) const +{ + size_t bytes_available = _request_data.size() - offset; + size_t bytes_to_read = std::min(bytes_available, max_count); + + memcpy(s, _request_data.data() + offset, bytes_to_read); + + return bytes_to_read; +} + +} // of namespace HTTP } // of namespace simgear