]> git.mxchange.org Git - simgear.git/blob - simgear/io/HTTPRequest.cxx
Disable HTTP pipelining if the connection closes.
[simgear.git] / simgear / io / HTTPRequest.cxx
1 // Copyright (C) 2011  James Turner <zakalawe@mac.com>
2 // Copyright (C) 2013  Thomas Geymayer <tomgey@gmail.com>
3 //
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.
8 //
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.
13 //
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
17
18 #include "HTTPRequest.hxx"
19
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>
25
26 namespace simgear
27 {
28 namespace HTTP
29 {
30
31 extern const int DEFAULT_HTTP_PORT;
32
33 //------------------------------------------------------------------------------
34 Request::Request(const std::string& url, const std::string method):
35   _method(method),
36   _url(url),
37   _responseVersion(HTTP_VERSION_UNKNOWN),
38   _responseStatus(0),
39   _responseLength(0),
40   _receivedBodyBytes(0),
41   _ready_state(UNSENT),
42   _willClose(false),
43   _connectionCloseHeader(false)
44 {
45
46 }
47
48 //------------------------------------------------------------------------------
49 Request::~Request()
50 {
51
52 }
53
54 //------------------------------------------------------------------------------
55 Request* Request::done(const Callback& cb)
56 {
57   if( _ready_state == DONE )
58     cb(this);
59   else
60     _cb_done.push_back(cb);
61
62   return this;
63 }
64
65 //------------------------------------------------------------------------------
66 Request* Request::fail(const Callback& cb)
67 {
68   if( _ready_state == FAILED )
69     cb(this);
70   else
71     _cb_fail.push_back(cb);
72
73   return this;
74 }
75
76 //------------------------------------------------------------------------------
77 Request* Request::always(const Callback& cb)
78 {
79   if( isComplete() )
80     cb(this);
81   else
82     _cb_always.push_back(cb);
83
84   return this;
85 }
86
87 //------------------------------------------------------------------------------
88 void Request::setBodyData( const std::string& data,
89                            const std::string& type )
90 {
91   _request_data = data;
92   _request_media_type = type;
93
94   if( !data.empty() && _method == "GET" )
95     _method = "POST";
96 }
97
98 //----------------------------------------------------------------------------
99 void Request::setBodyData(const SGPropertyNode* data)
100 {
101   if( !data )
102     setBodyData("");
103
104   std::stringstream buf;
105   writeProperties(buf, data, true);
106
107   setBodyData(buf.str(), "application/xml");
108 }
109
110 //------------------------------------------------------------------------------
111 void Request::setUrl(const std::string& url)
112 {
113   _url = url;
114 }
115
116 //------------------------------------------------------------------------------
117 void Request::requestStart()
118 {
119   setReadyState(OPENED);
120 }
121
122 //------------------------------------------------------------------------------
123 Request::HTTPVersion decodeHTTPVersion(const std::string& v)
124 {
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;
129 }
130
131 //------------------------------------------------------------------------------
132 void Request::responseStart(const std::string& r)
133 {
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);
138     }
139
140     _responseVersion = decodeHTTPVersion(parts[0]);
141     _responseStatus = strutils::to_int(parts[1]);
142     _responseReason = parts[2];
143
144     setReadyState(STATUS_RECEIVED);
145 }
146
147 //------------------------------------------------------------------------------
148 void Request::responseHeader(const std::string& key, const std::string& value)
149 {
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);
158   }
159
160   _responseHeaders[key] = value;
161 }
162
163 //------------------------------------------------------------------------------
164 void Request::responseHeadersComplete()
165 {
166   setReadyState(HEADERS_RECEIVED);
167 }
168
169 //------------------------------------------------------------------------------
170 void Request::responseComplete()
171 {
172   if( !isComplete() )
173     setReadyState(DONE);
174 }
175
176 //------------------------------------------------------------------------------
177 void Request::gotBodyData(const char* s, int n)
178 {
179   setReadyState(LOADING);
180 }
181
182 //------------------------------------------------------------------------------
183 void Request::onDone()
184 {
185
186 }
187
188 //------------------------------------------------------------------------------
189 void Request::onFail()
190 {
191   SG_LOG
192   (
193     SG_IO,
194     SG_INFO,
195     "request failed:" << url() << " : "
196                       << responseCode() << "/" << responseReason()
197   );
198 }
199
200 //------------------------------------------------------------------------------
201 void Request::onAlways()
202 {
203
204 }
205
206 //------------------------------------------------------------------------------
207 void Request::processBodyBytes(const char* s, int n)
208 {
209   _receivedBodyBytes += n;
210   gotBodyData(s, n);
211 }
212
213 //------------------------------------------------------------------------------
214 std::string Request::scheme() const
215 {
216     int firstColon = url().find(":");
217     if (firstColon > 0) {
218         return url().substr(0, firstColon);
219     }
220
221     return ""; // couldn't parse scheme
222 }
223
224 //------------------------------------------------------------------------------
225 std::string Request::path() const
226 {
227     std::string u(url());
228     int schemeEnd = u.find("://");
229     if (schemeEnd < 0) {
230         return ""; // couldn't parse scheme
231     }
232
233     int hostEnd = u.find('/', schemeEnd + 3);
234     if (hostEnd < 0) {
235 // couldn't parse host, or URL looks like 'http://foo.com' (no trailing '/')
236 // fixup to root resource path: '/'
237         return "/";
238     }
239
240     int query = u.find('?', hostEnd + 1);
241     if (query < 0) {
242         // all remainder of URL is path
243         return u.substr(hostEnd);
244     }
245
246     return u.substr(hostEnd, query - hostEnd);
247 }
248
249 //------------------------------------------------------------------------------
250 std::string Request::query() const
251 {
252   std::string u(url());
253   int query = u.find('?');
254   if (query < 0) {
255     return "";  //no query string found
256   }
257
258   return u.substr(query);   //includes question mark
259 }
260
261 //------------------------------------------------------------------------------
262 std::string Request::host() const
263 {
264   std::string hp(hostAndPort());
265   int colonPos = hp.find(':');
266   if (colonPos >= 0) {
267       return hp.substr(0, colonPos); // trim off the colon and port
268   } else {
269       return hp; // no port specifier
270   }
271 }
272
273 //------------------------------------------------------------------------------
274 unsigned short Request::port() const
275 {
276   std::string hp(hostAndPort());
277   int colonPos = hp.find(':');
278   if (colonPos >= 0) {
279       return (unsigned short) strutils::to_int(hp.substr(colonPos + 1));
280   } else {
281       return DEFAULT_HTTP_PORT;
282   }
283 }
284
285 //------------------------------------------------------------------------------
286 std::string Request::hostAndPort() const
287 {
288   std::string u(url());
289   int schemeEnd = u.find("://");
290   if (schemeEnd < 0) {
291       return ""; // couldn't parse scheme
292   }
293
294   int hostEnd = u.find('/', schemeEnd + 3);
295   if (hostEnd < 0) { // all remainder of URL is host
296       return u.substr(schemeEnd + 3);
297   }
298
299   return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3));
300 }
301
302 //------------------------------------------------------------------------------
303 std::string Request::responseMime() const
304 {
305   std::string content_type = _responseHeaders.get("content-type");
306   if( content_type.empty() )
307     return "application/octet-stream";
308
309   return content_type.substr(0, content_type.find(';'));
310 }
311
312 //------------------------------------------------------------------------------
313 void Request::setResponseLength(unsigned int l)
314 {
315   _responseLength = l;
316 }
317
318 //------------------------------------------------------------------------------
319 unsigned int Request::responseLength() const
320 {
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;
325
326   return _responseLength;
327 }
328
329 //------------------------------------------------------------------------------
330 void Request::setFailure(int code, const std::string& reason)
331 {
332     SG_LOG(SG_IO, SG_WARN, "HTTP request: set failure:" << code << " reason " << reason);
333   _responseStatus = code;
334   _responseReason = reason;
335
336   if( !isComplete() )
337     setReadyState(FAILED);
338 }
339
340 //------------------------------------------------------------------------------
341 void Request::setReadyState(ReadyState state)
342 {
343   _ready_state = state;
344   if( state == DONE )
345   {
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
348     // such files)
349     onDone();
350     onAlways();
351
352     _cb_done(this);
353   }
354   else if( state == FAILED )
355   {
356     onFail();
357     onAlways();
358
359     _cb_fail(this);
360   }
361   else
362     return;
363
364   _cb_always(this);
365 }
366
367 //------------------------------------------------------------------------------
368 void Request::abort()
369 {
370   abort("Request aborted.");
371 }
372
373 //----------------------------------------------------------------------------
374 void Request::abort(const std::string& reason)
375 {
376   setFailure(-1, reason);
377   _willClose = true;
378 }
379
380 //------------------------------------------------------------------------------
381 bool Request::closeAfterComplete() const
382 {
383   // for non HTTP/1.1 connections, assume server closes
384   return _willClose || (_responseVersion != HTTP_1_1);
385 }
386
387 //------------------------------------------------------------------------------
388 bool Request::serverSupportsPipelining() const
389 {
390     return (_responseVersion == HTTP_1_1) && !_connectionCloseHeader;
391 }
392
393 //------------------------------------------------------------------------------
394 bool Request::isComplete() const
395 {
396   return _ready_state == DONE || _ready_state == FAILED;
397 }
398
399 //------------------------------------------------------------------------------
400 bool Request::hasBodyData() const
401 {
402   return !_request_media_type.empty();
403 }
404
405 //------------------------------------------------------------------------------
406 std::string Request::bodyType() const
407 {
408   return _request_media_type;
409 }
410
411 //------------------------------------------------------------------------------
412 size_t Request::bodyLength() const
413 {
414   return _request_data.length();
415 }
416
417 //------------------------------------------------------------------------------
418 size_t Request::getBodyData(char* s, size_t offset, size_t max_count) const
419 {
420   size_t bytes_available = _request_data.size() - offset;
421   size_t bytes_to_read = std::min(bytes_available, max_count);
422
423   memcpy(s, _request_data.data() + offset, bytes_to_read);
424
425   return bytes_to_read;
426 }
427
428 } // of namespace HTTP
429 } // of namespace simgear