2 #include <boost/format.hpp>
3 #include <boost/bind.hpp>
4 #include <boost/thread/once.hpp>
5 #include <Acari/ParsingHelpers.hpp>
6 #include <Scorpion/Context.h>
7 #include <Spin/Connection.h>
8 #include <Spin/Connector.h>
9 #include <Spin/Exceptions/HTTP.h>
10 #include "Private/parseURL.h"
13 using namespace Acari
;
19 template < typename C
>
22 typedef C argument_type
;
24 ValidateURL_(const std::string
& server
, unsigned short port
, std::string
C::* p
)
30 bool operator()(const C
& c
) const
32 using Private::parseURL
;
40 boost::tie(protocol
, server
, port
, resource
, username
, password
) = parseURL(c
.*p_
);
42 return (server
== server_
) && (port_
== port
);
50 template < typename C
>
51 ValidateURL_
< C
> ValidateURL(const std::string
& server
, unsigned short port
, std::string
C::* p
)
53 return ValidateURL_
< C
>(server
, port
, p
);
57 static unsigned long attribute_index__(0xFFFFFFFF);
58 static boost::once_flag
once_flag__(BOOST_ONCE_INIT
);
60 void initAttributeIndex()
62 assert(attribute_index__
== 0xFFFFFFFF);
63 attribute_index__
= Spin::Connection::allocateAttribute();
66 Request::Request(const std::string
& url
, Method method
/* = get__*/, AuthorizationMethod auth_method
/* = none__*/)
69 auth_method_(auth_method
)
75 Response
getResponse(boost::shared_ptr
< Spin::Connection
> connection
)
77 boost::call_once(initAttributeIndex
, once_flag__
);
80 /* HERE we should make the connection blocking in a scoped manner if it isn't already */
82 std::vector
< char > buffer
;
84 /* When we get here, we may have some data in the connection's
85 * attributes already, which we can get now */
86 boost::any
& connection_attribute(connection
->getAttribute(attribute_index__
));
87 std::vector
< char >::iterator where
;
88 bool end_of_headers_found(false);
89 bool end_of_buffer_found(false);
90 boost::shared_ptr
< Response
> response
;
93 if (connection_attribute
.empty() /* no existing attribute */)
94 { // parse the request, as it is new
95 if (retrying
|| end_of_buffer_found
|| buffer
.empty() || connection
->poll())
97 std::vector
< char > tmp_buffer(buffer
);
99 std::pair
< std::size_t, int > result(connection
->read(buffer
));
100 switch (result
.second
/* reason */) // this is a switch mainly for documentation purposes - it could have been an if, but I think this is clearer
102 case Spin::Connection::no_error__
:
103 case Spin::Connection::should_retry__
:
104 case Spin::Connection::should_read__
:
105 /* in any of these cases, we either have everything we came for or we
106 * should read some more. We might as well check whether we have
107 * everything we can for and retry only after that, because we will
108 * probably have either the complete response or have to allow the
109 * server a bit of time to push the rest of the data through. */
112 throw std::runtime_error("Unexpected connection state"); // be more eloquent HERE
114 const std::size_t & size(result
.first
);
116 buffer
.insert(buffer
.begin(), tmp_buffer
.begin(), tmp_buffer
.end());
119 { /* already have data to work with */ }
120 boost::tie(response
, where
) = extractResponseHeader
< Spin::Connection
, Response
, Spin::Exceptions::HTTP::UnsupportedProtocol
>(connection
, buffer
.begin(), buffer
.end());
121 if (!response
&& where
== buffer
.begin())
123 /* the extraction errored out because the first line was incomplete.
124 * If this is the case, we will store whatever we got in the connection
125 * attributes and return. */
129 { /* all is well */ }
130 assert(response
|| where
== buffer
.end());
134 std::vector
< char > temp_buffer
;
135 boost::tie( response
, temp_buffer
, end_of_headers_found
, end_of_buffer_found
) = boost::any_cast
< boost::tuple
< boost::shared_ptr
< Response
>, std::vector
< char >, bool, bool > >(connection_attribute
);
136 buffer
.insert(buffer
.begin(), temp_buffer
.begin(), temp_buffer
.end());
137 /* connection_attribute.clear(); ==> */ boost::any a
; connection_attribute
.swap(a
);
138 where
= buffer
.begin();
140 } while(!(response
|| where
== buffer
.end()));
141 /* from here on, white space is important as we need to count the number of
142 * carriage returns (13) or newlines (10). If we find two of them before
143 * finding a non-whitespace character (that is: two newlines or two carriage
144 * returns), the requests consists of only the header. If we find only one,
145 * there is a header before the end of the response, and there might be a
147 end_of_buffer_found
= false;
150 std::vector
< char >::iterator
whence(where
);
151 whence
= advanceThroughIgnorableWhiteSpace(whence
, buffer
.end());
152 if (moreHeaders(where
, whence
))
155 whence
= findHeaderEnd(where
, buffer
.end());
158 Details::Header header
;
159 boost::tie(header
.name_
, header
.value_
) = splitHeader
< Spin::Exceptions::HTTP::InvalidHeader
>(where
, whence
);
160 response
->header_fields_
.push_back(header
);
164 { /* we're at the end of the current buffer, but more headers are still
165 * to come. We'll break out of the loop (by setting end_of_buffer_found,
166 * in some corner-cases, where will not be at buffer.end() but we will
167 * still have found the end of the buffer) and store what we currently know
168 * about the response in the connection attributes (the end of the headers
169 * will not have been found, so this will happen automatically) */
170 end_of_buffer_found
= true;
173 else // we have all of the header
176 end_of_headers_found
= true;
178 } while (!end_of_headers_found
&& where
!= buffer
.end() && !end_of_buffer_found
);
179 if (end_of_headers_found
)
181 /* When we get here, we don't know whether the response has a body.
182 * If it does, there is a Content-Length header among the headers
183 * that will contain the size of the body. */
184 Details::HeaderFields::const_iterator
curr(response
->header_fields_
.begin());
185 Details::HeaderFields::const_iterator
end(response
->header_fields_
.end());
186 while (curr
!= end
&& curr
->name_
!= "Content-Length") ++curr
;
187 bool complete_body_found(false);
190 std::size_t body_size(boost::lexical_cast
< std::size_t >(curr
->value_
));
191 std::vector
< char >::iterator
whence(where
);
192 /* When we get here, the "where" and "whence" iterators should both be
193 * at the start of the body. We will advance "whence" to the end of the
194 * body - which should be within the bounds of the buffer. */
195 if (std::size_t(std::distance(whence
, buffer
.end())) < body_size
)
197 connection_attribute
= boost::make_tuple(response
, std::vector
< char >(where
, buffer
.end()), end_of_headers_found
);
201 std::advance(whence
, body_size
);
202 assert(response
->body_
.empty());
203 response
->body_
.insert(response
->body_
.end(), where
, whence
);
205 complete_body_found
= true;
209 { /* request does not have a body */
210 complete_body_found
= true;
212 if (complete_body_found
)
214 /* Now, if whence is not at the end of the buffer, more requests may be
215 * in the buffer. We need to handle those. */
216 if (where
!= buffer
.end())
218 connection_attribute
= boost::make_tuple(boost::shared_ptr
< Response
>(), std::vector
< char >(where
, buffer
.end()), false, end_of_buffer_found
);
226 connection_attribute
= boost::make_tuple(response
, std::vector
< char >(where
, buffer
.end()), end_of_headers_found
, end_of_buffer_found
);
227 retrying
= true; // force a read on the connection
228 goto retry
; // tail recursion
231 return Response(*response
);
234 /*DAMON_API */Response
send(Session
& session
, const Request
& request
)
236 using Private::parseURL
;
238 std::string protocol
;
240 boost::uint16_t port
;
241 std::string resource
;
242 std::string username
;
243 std::string password
;
244 boost::tie(protocol
, server
, port
, resource
, username
, password
) = parseURL(request
.url_
);
245 bool secured(protocol
== "https");
249 if (!session
.context_
)
251 session
.context_
= new Scorpion::Context
;
254 { /* already have a context */ }
257 { /* no security context required */ }
258 assert((secured
&& session
.context_
) || !secured
);
260 /* the part of the URL that's important for the connection cache is
261 * the server and the port. The rest of the URL (i.e. protocol and
262 * resource) isn't important as it doesn't affect with whom we talk
263 * but rather how (which should not change from one call to the same
264 * port to another) and with which resource (which may change, but
265 * we don't care about that). */
266 boost::format
cache_key("%1%:%2%");
271 Session::ConnectionCache_::iterator
cached_connection(session
.connection_cache_
.find(cache_key
.str()));
274 if (cached_connection
== session
.connection_cache_
.end())
276 cached_connection
= session
.connection_cache_
.insert(
277 Session::ConnectionCache_::value_type(
281 ? Spin::Connector::getInstance().connect(*session
.context_
, server
, port
)
282 : Spin::Connector::getInstance().connect(server
, port
)
288 { /* we've found the connection */ }
289 assert(cached_connection
!= session
.connection_cache_
.end());
290 if (!cached_connection
->second
->usable())
292 session
.connection_cache_
.erase(cached_connection
);
293 cached_connection
= session
.connection_cache_
.end();
296 { /* all is well */ }
297 } while(cached_connection
== session
.connection_cache_
.end());
299 cached_connection
->second
->write(request
);
301 return getResponse(cached_connection
->second
);
304 /*DAMON_API */Response
send(const Request
& request
)
308 return send(session
, request
);
311 /*DAMON_API */std::vector
< Response
> send(Session
& session
, const std::vector
< Request
> & requests
)
313 using Private::parseURL
;
315 if (requests
.empty())
316 return std::vector
< Response
>();
320 std::string protocol
;
322 boost::uint16_t port
;
323 std::string resource
;
324 std::string username
;
325 std::string password
;
326 boost::tie(protocol
, server
, port
, resource
, username
, password
) = parseURL(requests
[0].url_
);
327 bool secured(protocol
== "https");
331 if (!session
.context_
)
333 session
.context_
= new Scorpion::Context
;
336 { /* already have a context */ }
339 { /* no security context required */ }
340 assert((secured
&& session
.context_
) || !secured
);
342 /* the part of the URL that's important for the connection cache is
343 * the server and the port. The rest of the URL (i.e. protocol and
344 * resource) isn't important as it doesn't affect with whom we talk
345 * but rather how (which should not change from one call to the same
346 * port to another) and with which resource (which may change, but
347 * we don't care about that). */
348 boost::format
cache_key("%1%:%2%");
353 Session::ConnectionCache_::iterator
cached_connection(session
.connection_cache_
.find(cache_key
.str()));
356 if (cached_connection
== session
.connection_cache_
.end())
358 cached_connection
= session
.connection_cache_
.insert(
359 Session::ConnectionCache_::value_type(
363 ? Spin::Connector::getInstance().connect(*session
.context_
, server
, port
)
364 : Spin::Connector::getInstance().connect(server
, port
)
370 { /* we've found the connection */ }
371 assert(cached_connection
!= session
.connection_cache_
.end());
372 if (!cached_connection
->second
->usable())
374 session
.connection_cache_
.erase(cached_connection
);
375 cached_connection
= session
.connection_cache_
.end();
378 { /* all is well */ }
379 } while(cached_connection
== session
.connection_cache_
.end());
381 // validate that all of the requests use the same server and port parts in the URL
382 assert(std::find_if(requests
.begin(), requests
.end(), std::not1(ValidateURL(server
, port
, &Request::url_
))) == requests
.end());
384 std::for_each(requests
.begin(), requests
.end(), boost::bind(&Spin::Connection::write
< Request
>, cached_connection
->second
.get(), _1
));
386 std::vector
< Response
> responses
;
387 for (std::vector
< Request
>::size_type
i(0); i
< requests
.size(); ++i
)
388 responses
.push_back(getResponse(cached_connection
->second
));
392 /*DAMON_API */std::vector
< Response
> send(const std::vector
< Request
> & requests
)
396 return send(session
, requests
);
403 AppendHeader(std::string
& str
)
407 AppendHeader
& operator()(const Details::Header
& header
)
409 boost::format
fmt("%1%: %2%\r\n");
410 fmt
% header
.name_
% header
.value_
;
419 /*DAMON_API */std::string
serialize(Request request
)
421 using Private::parseURL
;
422 using Private::extractHost
;
424 std::string protocol
;
426 boost::uint16_t port
;
427 std::string resource
;
428 std::string username
;
429 std::string password
;
430 boost::tie(protocol
, server
, port
, resource
, username
, password
) = parseURL(request
.url_
);
432 switch (request
.method_
)
434 case Request::options__
:
437 case Request::get__
:
440 case Request::head__
:
443 case Request::post__
:
446 case Request::put__
:
449 case Request::delete__
:
452 case Request::trace__
:
455 case Request::connect__
:
459 throw std::runtime_error("Unknown method"); // be more eloquent HERE
462 retval
+= " HTTP/1.1\r\n";
464 if (std::find_if(request
.header_fields_
.begin(), request
.header_fields_
.end(), bind(&Details::Header::name_
, _1
) == "Host") == request
.header_fields_
.end())
465 request
.header_fields_
.push_back(Details::Header("Host", extractHost(request
.url_
)));
467 { /* Already has a Host header */ }
468 if (std::find_if(request
.header_fields_
.begin(), request
.header_fields_
.end(), bind(&Details::Header::name_
, _1
) == "Content-Length") == request
.header_fields_
.end())
469 request
.header_fields_
.push_back(Details::Header("Content-Length", boost::lexical_cast
< std::string
>(request
.body_
.size())));
471 { /* Already has a Content-Length header */ }
472 if (request
.auth_method_
== Request::basic__
&& !username
.empty())
473 request
.header_fields_
.push_back(Details::createBasicAuthorizationHeader(username
, password
));
475 { /* no authorization method, don't care about username & password */ }
476 std::for_each(request
.header_fields_
.begin(), request
.header_fields_
.end(), AppendHeader(retval
));
478 retval
.insert(retval
.end(), request
.body_
.begin(), request
.body_
.end());