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):
37 _responseVersion(HTTP_VERSION_UNKNOWN),
40 _receivedBodyBytes(0),
43 _connectionCloseHeader(false)
48 //------------------------------------------------------------------------------
54 //------------------------------------------------------------------------------
55 Request* Request::done(const Callback& cb)
57 if( _ready_state == DONE )
60 _cb_done.push_back(cb);
65 //------------------------------------------------------------------------------
66 Request* Request::fail(const Callback& cb)
68 if( _ready_state == FAILED )
71 _cb_fail.push_back(cb);
76 //------------------------------------------------------------------------------
77 Request* Request::always(const Callback& cb)
82 _cb_always.push_back(cb);
87 //------------------------------------------------------------------------------
88 void Request::setBodyData( const std::string& data,
89 const std::string& type )
92 _request_media_type = type;
94 if( !data.empty() && _method == "GET" )
98 //----------------------------------------------------------------------------
99 void Request::setBodyData(const SGPropertyNode* data)
104 std::stringstream buf;
105 writeProperties(buf, data, true);
107 setBodyData(buf.str(), "application/xml");
110 //------------------------------------------------------------------------------
111 void Request::setUrl(const std::string& url)
116 //------------------------------------------------------------------------------
117 void Request::requestStart()
119 setReadyState(OPENED);
122 //------------------------------------------------------------------------------
123 Request::HTTPVersion decodeHTTPVersion(const std::string& v)
125 if( v == "HTTP/1.1" ) return Request::HTTP_1_1;
126 if( v == "HTTP/1.0" ) return Request::HTTP_1_0;
127 if( strutils::starts_with(v, "HTTP/0.") ) return Request::HTTP_0_x;
128 return Request::HTTP_VERSION_UNKNOWN;
131 //------------------------------------------------------------------------------
132 void Request::responseStart(const std::string& r)
134 const int maxSplit = 2; // HTTP/1.1 nnn reason-string
135 string_list parts = strutils::split(r, NULL, maxSplit);
136 if (parts.size() != 3) {
137 throw sg_io_exception("bad HTTP response:" + r);
140 _responseVersion = decodeHTTPVersion(parts[0]);
141 _responseStatus = strutils::to_int(parts[1]);
142 _responseReason = parts[2];
144 setReadyState(STATUS_RECEIVED);
147 //------------------------------------------------------------------------------
148 void Request::responseHeader(const std::string& key, const std::string& value)
150 if( key == "connection" ) {
151 _connectionCloseHeader = (value.find("close") != std::string::npos);
152 // track willClose seperately because other conditions (abort, for
153 // example) can also set it
154 _willClose = _connectionCloseHeader;
155 } else if (key == "content-length") {
156 int sz = strutils::to_int(value);
157 setResponseLength(sz);
160 _responseHeaders[key] = value;
163 //------------------------------------------------------------------------------
164 void Request::responseHeadersComplete()
166 setReadyState(HEADERS_RECEIVED);
169 //------------------------------------------------------------------------------
170 void Request::responseComplete()
176 //------------------------------------------------------------------------------
177 void Request::gotBodyData(const char* s, int n)
179 setReadyState(LOADING);
182 //------------------------------------------------------------------------------
183 void Request::onDone()
188 //------------------------------------------------------------------------------
189 void Request::onFail()
195 "request failed:" << url() << " : "
196 << responseCode() << "/" << responseReason()
200 //------------------------------------------------------------------------------
201 void Request::onAlways()
206 //------------------------------------------------------------------------------
207 void Request::processBodyBytes(const char* s, int n)
209 _receivedBodyBytes += n;
213 //------------------------------------------------------------------------------
214 std::string Request::scheme() const
216 int firstColon = url().find(":");
217 if (firstColon > 0) {
218 return url().substr(0, firstColon);
221 return ""; // couldn't parse scheme
224 //------------------------------------------------------------------------------
225 std::string Request::path() const
227 std::string u(url());
228 int schemeEnd = u.find("://");
230 return ""; // couldn't parse scheme
233 int hostEnd = u.find('/', schemeEnd + 3);
235 // couldn't parse host, or URL looks like 'http://foo.com' (no trailing '/')
236 // fixup to root resource path: '/'
240 int query = u.find('?', hostEnd + 1);
242 // all remainder of URL is path
243 return u.substr(hostEnd);
246 return u.substr(hostEnd, query - hostEnd);
249 //------------------------------------------------------------------------------
250 std::string Request::query() const
252 std::string u(url());
253 int query = u.find('?');
255 return ""; //no query string found
258 return u.substr(query); //includes question mark
261 //------------------------------------------------------------------------------
262 std::string Request::host() const
264 std::string hp(hostAndPort());
265 int colonPos = hp.find(':');
267 return hp.substr(0, colonPos); // trim off the colon and port
269 return hp; // no port specifier
273 //------------------------------------------------------------------------------
274 unsigned short Request::port() const
276 std::string hp(hostAndPort());
277 int colonPos = hp.find(':');
279 return (unsigned short) strutils::to_int(hp.substr(colonPos + 1));
281 return DEFAULT_HTTP_PORT;
285 //------------------------------------------------------------------------------
286 std::string Request::hostAndPort() const
288 std::string u(url());
289 int schemeEnd = u.find("://");
291 return ""; // couldn't parse scheme
294 int hostEnd = u.find('/', schemeEnd + 3);
295 if (hostEnd < 0) { // all remainder of URL is host
296 return u.substr(schemeEnd + 3);
299 return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3));
302 //------------------------------------------------------------------------------
303 std::string Request::responseMime() const
305 std::string content_type = _responseHeaders.get("content-type");
306 if( content_type.empty() )
307 return "application/octet-stream";
309 return content_type.substr(0, content_type.find(';'));
312 //------------------------------------------------------------------------------
313 void Request::setResponseLength(unsigned int l)
318 //------------------------------------------------------------------------------
319 unsigned int Request::responseLength() const
321 // if the server didn't supply a content length, use the number
322 // of bytes we actually received (so far)
323 if( (_responseLength == 0) && (_receivedBodyBytes > 0) )
324 return _receivedBodyBytes;
326 return _responseLength;
329 //------------------------------------------------------------------------------
330 void Request::setFailure(int code, const std::string& reason)
332 SG_LOG(SG_IO, SG_WARN, "HTTP request: set failure:" << code << " reason " << reason);
333 _responseStatus = code;
334 _responseReason = reason;
337 setReadyState(FAILED);
340 //------------------------------------------------------------------------------
341 void Request::setReadyState(ReadyState state)
343 _ready_state = state;
346 // Finish C++ part of request to ensure everything is finished (for example
347 // files and streams are closed) before calling any callback (possibly using
354 else if( state == FAILED )
367 //------------------------------------------------------------------------------
368 void Request::abort()
370 abort("Request aborted.");
373 //----------------------------------------------------------------------------
374 void Request::abort(const std::string& reason)
376 setFailure(-1, reason);
380 //------------------------------------------------------------------------------
381 bool Request::closeAfterComplete() const
383 // for non HTTP/1.1 connections, assume server closes
384 return _willClose || (_responseVersion != HTTP_1_1);
387 //------------------------------------------------------------------------------
388 bool Request::serverSupportsPipelining() const
390 return (_responseVersion == HTTP_1_1) && !_connectionCloseHeader;
393 //------------------------------------------------------------------------------
394 bool Request::isComplete() const
396 return _ready_state == DONE || _ready_state == FAILED;
399 //------------------------------------------------------------------------------
400 bool Request::hasBodyData() const
402 return !_request_media_type.empty();
405 //------------------------------------------------------------------------------
406 std::string Request::bodyType() const
408 return _request_media_type;
411 //------------------------------------------------------------------------------
412 size_t Request::bodyLength() const
414 return _request_data.length();
417 //------------------------------------------------------------------------------
418 size_t Request::getBodyData(char* s, size_t offset, size_t max_count) const
420 size_t bytes_available = _request_data.size() - offset;
421 size_t bytes_to_read = std::min(bytes_available, max_count);
423 memcpy(s, _request_data.data() + offset, bytes_to_read);
425 return bytes_to_read;
428 } // of namespace HTTP
429 } // of namespace simgear