1 // Copyright (C) 2011 James Turner <zakalawe@mac.com>
2 // Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Library General Public
6 // License as published by the Free Software Foundation; either
7 // version 2 of the License, or (at your option) any later version.
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // Library General Public License for more details.
14 // You should have received a copy of the GNU Library General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
18 #include "HTTPRequest.hxx"
20 #include <simgear/compiler.h>
21 #include <simgear/debug/logstream.hxx>
22 #include <simgear/misc/strutils.hxx>
23 #include <simgear/props/props_io.hxx>
24 #include <simgear/structure/exception.hxx>
31 extern const int DEFAULT_HTTP_PORT;
33 //------------------------------------------------------------------------------
34 Request::Request(const std::string& url, const std::string method):
38 _responseVersion(HTTP_VERSION_UNKNOWN),
41 _receivedBodyBytes(0),
44 _connectionCloseHeader(false)
49 //------------------------------------------------------------------------------
55 //------------------------------------------------------------------------------
56 Request* Request::done(const Callback& cb)
58 if( _ready_state == DONE )
61 _cb_done.push_back(cb);
66 //------------------------------------------------------------------------------
67 Request* Request::fail(const Callback& cb)
69 if( _ready_state == FAILED )
72 _cb_fail.push_back(cb);
77 //------------------------------------------------------------------------------
78 Request* Request::always(const Callback& cb)
83 _cb_always.push_back(cb);
88 //------------------------------------------------------------------------------
89 void Request::setBodyData( const std::string& data,
90 const std::string& type )
93 _request_media_type = type;
95 if( !data.empty() && _method == "GET" )
99 //----------------------------------------------------------------------------
100 void Request::setBodyData(const SGPropertyNode* data)
105 std::stringstream buf;
106 writeProperties(buf, data, true);
108 setBodyData(buf.str(), "application/xml");
111 //------------------------------------------------------------------------------
112 void Request::setUrl(const std::string& url)
117 //------------------------------------------------------------------------------
118 void Request::requestStart()
120 setReadyState(OPENED);
123 //------------------------------------------------------------------------------
124 Request::HTTPVersion decodeHTTPVersion(const std::string& v)
126 if( v == "HTTP/1.1" ) return Request::HTTP_1_1;
127 if( v == "HTTP/1.0" ) return Request::HTTP_1_0;
128 if( strutils::starts_with(v, "HTTP/0.") ) return Request::HTTP_0_x;
129 return Request::HTTP_VERSION_UNKNOWN;
132 //------------------------------------------------------------------------------
133 void Request::responseStart(const std::string& r)
135 const int maxSplit = 2; // HTTP/1.1 nnn reason-string
136 string_list parts = strutils::split(r, NULL, maxSplit);
137 if (parts.size() != 3) {
138 throw sg_io_exception("bad HTTP response:" + r);
141 _responseVersion = decodeHTTPVersion(parts[0]);
142 _responseStatus = strutils::to_int(parts[1]);
143 _responseReason = parts[2];
145 setReadyState(STATUS_RECEIVED);
148 //------------------------------------------------------------------------------
149 void Request::responseHeader(const std::string& key, const std::string& value)
151 if( key == "connection" ) {
152 _connectionCloseHeader = (value.find("close") != std::string::npos);
153 // track willClose seperately because other conditions (abort, for
154 // example) can also set it
155 _willClose = _connectionCloseHeader;
156 } else if (key == "content-length") {
157 int sz = strutils::to_int(value);
158 setResponseLength(sz);
161 _responseHeaders[key] = value;
164 //------------------------------------------------------------------------------
165 void Request::responseHeadersComplete()
167 setReadyState(HEADERS_RECEIVED);
170 //------------------------------------------------------------------------------
171 void Request::responseComplete()
177 //------------------------------------------------------------------------------
178 void Request::gotBodyData(const char* s, int n)
180 setReadyState(LOADING);
183 //------------------------------------------------------------------------------
184 void Request::onDone()
189 //------------------------------------------------------------------------------
190 void Request::onFail()
196 "request failed:" << url() << " : "
197 << responseCode() << "/" << responseReason()
201 //------------------------------------------------------------------------------
202 void Request::onAlways()
207 //------------------------------------------------------------------------------
208 void Request::processBodyBytes(const char* s, int n)
210 _receivedBodyBytes += n;
214 //------------------------------------------------------------------------------
215 std::string Request::scheme() const
217 int firstColon = url().find(":");
218 if (firstColon > 0) {
219 return url().substr(0, firstColon);
222 return ""; // couldn't parse scheme
225 //------------------------------------------------------------------------------
226 std::string Request::path() const
228 std::string u(url());
229 int schemeEnd = u.find("://");
231 return ""; // couldn't parse scheme
234 int hostEnd = u.find('/', schemeEnd + 3);
236 // couldn't parse host, or URL looks like 'http://foo.com' (no trailing '/')
237 // fixup to root resource path: '/'
241 int query = u.find('?', hostEnd + 1);
243 // all remainder of URL is path
244 return u.substr(hostEnd);
247 return u.substr(hostEnd, query - hostEnd);
250 //------------------------------------------------------------------------------
251 std::string Request::query() const
253 std::string u(url());
254 int query = u.find('?');
256 return ""; //no query string found
259 return u.substr(query); //includes question mark
262 //------------------------------------------------------------------------------
263 std::string Request::host() const
265 std::string hp(hostAndPort());
266 int colonPos = hp.find(':');
268 return hp.substr(0, colonPos); // trim off the colon and port
270 return hp; // no port specifier
274 //------------------------------------------------------------------------------
275 unsigned short Request::port() const
277 std::string hp(hostAndPort());
278 int colonPos = hp.find(':');
280 return (unsigned short) strutils::to_int(hp.substr(colonPos + 1));
282 return DEFAULT_HTTP_PORT;
286 //------------------------------------------------------------------------------
287 std::string Request::hostAndPort() const
289 std::string u(url());
290 int schemeEnd = u.find("://");
292 return ""; // couldn't parse scheme
295 int hostEnd = u.find('/', schemeEnd + 3);
296 if (hostEnd < 0) { // all remainder of URL is host
297 return u.substr(schemeEnd + 3);
300 return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3));
303 //------------------------------------------------------------------------------
304 std::string Request::responseMime() const
306 std::string content_type = _responseHeaders.get("content-type");
307 if( content_type.empty() )
308 return "application/octet-stream";
310 return content_type.substr(0, content_type.find(';'));
313 //------------------------------------------------------------------------------
314 void Request::setResponseLength(unsigned int l)
319 //------------------------------------------------------------------------------
320 unsigned int Request::responseLength() const
322 // if the server didn't supply a content length, use the number
323 // of bytes we actually received (so far)
324 if( (_responseLength == 0) && (_receivedBodyBytes > 0) )
325 return _receivedBodyBytes;
327 return _responseLength;
330 //------------------------------------------------------------------------------
331 void Request::setFailure(int code, const std::string& reason)
333 SG_LOG(SG_IO, SG_WARN, "HTTP request: set failure:" << code << " reason " << reason);
334 _responseStatus = code;
335 _responseReason = reason;
338 setReadyState(FAILED);
341 //------------------------------------------------------------------------------
342 void Request::setReadyState(ReadyState state)
344 _ready_state = state;
347 // Finish C++ part of request to ensure everything is finished (for example
348 // files and streams are closed) before calling any callback (possibly using
355 else if( state == FAILED )
368 //------------------------------------------------------------------------------
369 void Request::abort()
371 abort("Request aborted.");
374 //----------------------------------------------------------------------------
375 void Request::abort(const std::string& reason)
377 setFailure(-1, reason);
381 //------------------------------------------------------------------------------
382 bool Request::closeAfterComplete() const
384 // for non HTTP/1.1 connections, assume server closes
385 return _willClose || (_responseVersion != HTTP_1_1);
388 //------------------------------------------------------------------------------
389 bool Request::serverSupportsPipelining() const
391 return (_responseVersion == HTTP_1_1) && !_connectionCloseHeader;
394 //------------------------------------------------------------------------------
395 bool Request::isComplete() const
397 return _ready_state == DONE || _ready_state == FAILED;
400 //------------------------------------------------------------------------------
401 bool Request::hasBodyData() const
403 return !_request_media_type.empty();
406 //------------------------------------------------------------------------------
407 std::string Request::bodyType() const
409 return _request_media_type;
412 //------------------------------------------------------------------------------
413 size_t Request::bodyLength() const
415 return _request_data.length();
418 //------------------------------------------------------------------------------
419 size_t Request::getBodyData(char* s, size_t offset, size_t max_count) const
421 size_t bytes_available = _request_data.size() - offset;
422 size_t bytes_to_read = std::min(bytes_available, max_count);
424 memcpy(s, _request_data.data() + offset, bytes_to_read);
426 return bytes_to_read;
429 } // of namespace HTTP
430 } // of namespace simgear