Line data Source code
1 : // HTTP Client & Server -- classes to ease handling HTTP protocol
2 : // Copyright (c) 2012-2019 Made to Order Software Corp. All Rights Reserved
3 : //
4 : // https://snapwebsites.org/
5 : // contact@m2osw.com
6 : //
7 : // This program is free software; you can redistribute it and/or modify
8 : // it under the terms of the GNU General Public License as published by
9 : // the Free Software Foundation; either version 2 of the License, or
10 : // (at your option) any later version.
11 : //
12 : // This program is distributed in the hope that it will be useful,
13 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : // GNU General Public License for more details.
16 : //
17 : // You should have received a copy of the GNU General Public License
18 : // along with this program; if not, write to the Free Software
19 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 :
21 :
22 : // self
23 : //
24 : #include "snapwebsites/http_client_server.h"
25 :
26 :
27 : // snapwebsites
28 : //
29 : #include "snapwebsites/log.h"
30 : #include "snapwebsites/snap_uri.h"
31 : #include "snapwebsites/snapwebsites.h"
32 :
33 :
34 : // snapdev lib
35 : //
36 : #include <snapdev/not_reached.h>
37 :
38 :
39 : // C++ lib
40 : //
41 : #include <algorithm>
42 : #include <iostream>
43 : #include <sstream>
44 :
45 :
46 : // last include
47 : //
48 : #include <snapdev/poison.h>
49 :
50 :
51 : namespace http_client_server
52 : {
53 :
54 :
55 : namespace
56 : {
57 :
58 : char const g_base64[] =
59 : {
60 : // 8x8 characters
61 : 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
62 : 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
63 : 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
64 : 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
65 : 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
66 : 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
67 : 'w', 'x', 'y', 'z', '0', '1', '2', '3',
68 : '4', '5', '6', '7', '8', '9', '+', '/'
69 : };
70 :
71 : }
72 : // no name namespace
73 :
74 :
75 :
76 0 : std::string http_request::get_host() const
77 : {
78 0 : return f_host;
79 : }
80 :
81 :
82 0 : int http_request::get_port() const
83 : {
84 0 : return f_port;
85 : }
86 :
87 :
88 0 : std::string http_request::get_command() const
89 : {
90 0 : return f_command;
91 : }
92 :
93 :
94 0 : std::string http_request::get_path() const
95 : {
96 0 : return f_path;
97 : }
98 :
99 :
100 0 : std::string http_request::get_header(std::string const & name) const
101 : {
102 0 : if(f_headers.find(name) == f_headers.end())
103 : {
104 0 : return "";
105 : }
106 0 : return f_headers.at(name);
107 : }
108 :
109 :
110 0 : std::string http_request::get_post(std::string const & name) const
111 : {
112 0 : if(f_post.find(name) == f_post.end())
113 : {
114 0 : return "";
115 : }
116 0 : return f_post.at(name);
117 : }
118 :
119 :
120 0 : std::string http_request::get_body() const
121 : {
122 0 : return f_body;
123 : }
124 :
125 :
126 0 : std::string http_request::get_request(bool keep_alive) const
127 : {
128 0 : std::stringstream request;
129 :
130 : // first we generate the body, that way we define its size
131 : // and also the content type in case of a POST
132 :
133 : // we will get a copy of the body as required because this
134 : // function is constant and we do not want to modify f_body
135 0 : std::string body;
136 0 : std::string content_type;
137 :
138 0 : if(f_has_attachment)
139 : {
140 0 : request << (f_command.empty() ? "POST" : f_command.c_str())
141 0 : << " " << f_path << " HTTP/1.1\r\n";
142 :
143 0 : throw http_client_server_logic_error("http_client_server.cpp:get_request(): attachments not supported yet");
144 : }
145 0 : else if(f_has_post)
146 : {
147 : // TODO: support the case where the post variables are passed using
148 : // a GET and a query string
149 0 : request << (f_command.empty() ? "POST" : f_command.c_str())
150 0 : << " " << f_path << " HTTP/1.1\r\n";
151 0 : content_type = "application/x-www-form-urlencoded";
152 :
153 0 : body = "";
154 0 : for(auto it(f_post.begin()); it != f_post.end(); ++it)
155 : {
156 0 : if(!body.empty())
157 : {
158 : // separate parameters by ampersands
159 0 : body += "&";
160 : }
161 : // TODO: escape & and such
162 0 : body += it->first + "=" + it->second;
163 : }
164 : }
165 0 : else if(f_has_data)
166 : {
167 0 : request << (f_command.empty() ? "POST" : f_command.c_str())
168 0 : << " " << f_path << " HTTP/1.1\r\n";
169 0 : body = f_body;
170 : }
171 0 : else if(f_has_body)
172 : {
173 0 : request << (f_command.empty() ? "GET" : f_command.c_str())
174 0 : << " " << f_path << " HTTP/1.1\r\n";
175 0 : body = f_body;
176 : }
177 : else
178 : {
179 0 : request << (f_command.empty() ? "GET" : f_command.c_str())
180 0 : << " " << f_path << " HTTP/1.1\r\n";
181 : // body is empty by default
182 : //body = "";
183 : }
184 :
185 : // place Host first because some servers are that stupid
186 0 : request << "Host: " << f_host << "\r\n";
187 :
188 0 : bool found_user_agent(false);
189 0 : for(auto it(f_headers.begin()); it != f_headers.end(); ++it)
190 : {
191 : // make sure we do not output the following fields which are
192 : // managed by our code instead:
193 : //
194 : // Content-Length
195 : //
196 0 : std::string name(it->first);
197 0 : std::transform(name.begin(), name.end(), name.begin(), ::tolower);
198 0 : if((content_type.empty() || name != "content-type")
199 0 : && name != "content-length"
200 0 : && name != "host"
201 0 : && name != "connection")
202 : {
203 0 : if(name == "user-agent")
204 : {
205 0 : found_user_agent = true;
206 : }
207 0 : request << it->first
208 : << ": "
209 0 : << it->second
210 0 : << "\r\n";
211 : }
212 : }
213 :
214 : // forcing the type? (generally doing so with POSTs)
215 0 : if(!content_type.empty())
216 : {
217 0 : request << "Content-Type: " << content_type << "\r\n";
218 : }
219 0 : if(!found_user_agent)
220 : {
221 0 : request << "User-Agent: snapwebsites/" SNAPWEBSITES_VERSION_STRING "\r\n";
222 : }
223 :
224 : // force the connection valid to what the programmer asked (keep-alive by
225 : // default though)
226 : //
227 : // WARNING: according to HTTP/1.1, servers only expect "close" and not
228 : // "keep-alive"; however, it looks like many implementations
229 : // understand both (there is also an "upgrade" which we do not
230 : // support)
231 0 : request << "Connection: " << (keep_alive ? "keep-alive" : "close") << "\r\n";
232 :
233 : // end the list with the fields we control:
234 : //
235 : // Content-Length is the size of the body
236 0 : request << "Content-Length: " << body.length() << "\r\n\r\n";
237 :
238 : // TBD: will this work if 'body' includes a '\0'?
239 0 : request << body;
240 :
241 0 : return request.str();
242 : }
243 :
244 :
245 : /** \brief Set the host, port, and path at once.
246 : *
247 : * HTTP accepts full URIs in the GET, POST, etc. line
248 : * so the following would be valid:
249 : *
250 : * GET https://snapwebsites.org/some/path?a=view HTTP/1.1
251 : *
252 : * However, we break it down in a few separate parts instead, because
253 : * (a) we need the host to connect to the server, (b) we need the port
254 : * to connect to the server:
255 : *
256 : * 1. Remove protocol, this defines whether we use plain text (http)
257 : * or encryption (https/ssl)
258 : * 2. Get the port, if not specified after the domain, use the default
259 : * of the specified URI protocol
260 : * 3. Domain name is moved to the 'Host: ...' header
261 : * 4. Path and query string are kept as is
262 : *
263 : * So the example above changes to:
264 : *
265 : * GET /some/path?a=view HTTP/1.1
266 : * Host: snapwebsites.org
267 : *
268 : * We use a plain text connection (http:) and the port is the default
269 : * port for the HTTP protocol (80). That information does not appear
270 : * in the HTTP header.
271 : *
272 : * \param[in] uri The URI to save in this HTTP request.
273 : */
274 0 : void http_request::set_uri(std::string const & uri)
275 : {
276 0 : snap::snap_uri u(QString::fromUtf8(uri.c_str()));
277 0 : f_host = u.full_domain().toUtf8().data();
278 0 : f_port = u.get_port();
279 :
280 : // use set_path() to make sure we get an absolute path
281 : // (which is not the case by default)
282 0 : set_path(u.path().toUtf8().data());
283 :
284 : // keep the query string parameters if any are defined
285 0 : QString const q(u.query_string());
286 0 : if(!q.isEmpty())
287 : {
288 0 : f_path += "?";
289 0 : f_path += q.toUtf8().data();
290 : }
291 0 : }
292 :
293 :
294 0 : void http_request::set_host(std::string const & host)
295 : {
296 0 : f_host = host;
297 0 : }
298 :
299 :
300 0 : void http_request::set_port(int port)
301 : {
302 : // port will be verified by the tcp_client_server code, so no need to
303 : // do that again here
304 0 : f_port = port;
305 0 : }
306 :
307 :
308 0 : void http_request::set_command(std::string const & command)
309 : {
310 0 : f_command = command;
311 0 : }
312 :
313 :
314 0 : void http_request::set_path(std::string const & path)
315 : {
316 : // TODO: better verify path validity
317 0 : if(path.empty())
318 : {
319 0 : f_path = "/";
320 : }
321 0 : else if(path[0] == '/')
322 : {
323 0 : f_path = path;
324 : }
325 : else
326 : {
327 0 : f_path = "/" + path;
328 : }
329 0 : }
330 :
331 :
332 0 : void http_request::set_header(std::string const & name, std::string const & value)
333 : {
334 : // TODO: verify that the header name is compatible/valid
335 : // TODO: for known names, verify that the value is compatible/valid
336 : // TODO: verify the value in various other ways
337 0 : if(value.empty())
338 : {
339 : // remove headers if defined
340 0 : auto h(f_headers.find(value));
341 0 : if(h != f_headers.end())
342 : {
343 0 : f_headers.erase(h);
344 : }
345 : }
346 : else
347 : {
348 : // add header, overwrite if already defined
349 0 : f_headers[name] = value;
350 : }
351 0 : }
352 :
353 :
354 0 : void http_request::set_post(std::string const & name, std::string const & value)
355 : {
356 0 : if(f_has_body || f_has_data)
357 : {
358 0 : throw http_client_server_logic_error("you cannot use set_body(), set_data(), and set_post() on the same http_request object");
359 : }
360 :
361 : // TODO: verify that the name is a valid name for a post variable
362 0 : f_post[name] = value;
363 :
364 0 : f_has_post = true;
365 0 : }
366 :
367 :
368 0 : void http_request::set_basic_auth(std::string const & username, std::string const & secret)
369 : {
370 0 : auto encode = [](std::string const & in, std::string & out)
371 : {
372 : // reset output (just in case)
373 0 : out.clear();
374 :
375 : // WARNING: following algorithm does NOT take any line length
376 : // in account; and it is deadly well optimized
377 0 : unsigned char const *s(reinterpret_cast<unsigned char const *>(in.c_str()));
378 0 : while(*s != '\0')
379 : {
380 : // get 1 to 3 characters of input
381 0 : out += g_base64[s[0] >> 2]; // & 0x3F not required
382 0 : ++s;
383 0 : if(s[0] != '\0')
384 : {
385 0 : out += g_base64[((s[-1] << 4) & 0x30) | ((s[0] >> 4) & 0x0F)];
386 0 : ++s;
387 0 : if(s[0] != '\0')
388 : {
389 : // 24 bits of input uses 4 base64 characters
390 0 : out += g_base64[((s[-1] << 2) & 0x3C) | ((s[0] >> 6) & 0x03)];
391 0 : out += g_base64[s[0] & 0x3F];
392 0 : s++;
393 : }
394 : else
395 : {
396 : // 16 bits of input uses 3 base64 characters + 1 pad
397 0 : out += g_base64[(s[-1] << 2) & 0x3C];
398 0 : out += '=';
399 0 : break;
400 : }
401 : }
402 : else
403 : {
404 : // 8 bits of input uses 2 base64 characters + 2 pads
405 0 : out += g_base64[(s[-1] << 4) & 0x30];
406 0 : out += "==";
407 0 : break;
408 : }
409 : }
410 0 : };
411 :
412 0 : std::string const authorization_token(username + ":" + secret);
413 0 : std::string base64;
414 0 : encode(authorization_token, base64);
415 :
416 0 : set_header("Authorization", "Basic " + base64);
417 0 : }
418 :
419 :
420 0 : void http_request::set_data(std::string const & data)
421 : {
422 0 : if(f_has_post || f_has_body)
423 : {
424 0 : throw http_client_server_logic_error("you cannot use set_post(), set_data(), and set_body() on the same http_request object");
425 : }
426 :
427 0 : f_body = data;
428 :
429 0 : f_has_data = true;
430 0 : }
431 :
432 :
433 0 : void http_request::set_body(std::string const & body)
434 : {
435 0 : if(f_has_post || f_has_data)
436 : {
437 0 : throw http_client_server_logic_error("you cannot use set_post(), set_data(), and set_body() on the same http_request object");
438 : }
439 :
440 0 : f_body = body;
441 :
442 0 : f_has_body = true;
443 0 : }
444 :
445 :
446 0 : std::string http_response::get_original_header() const
447 : {
448 0 : return f_original_header;
449 : }
450 :
451 :
452 0 : http_response::protocol_t http_response::get_protocol() const
453 : {
454 0 : return f_protocol;
455 : }
456 :
457 :
458 0 : int http_response::get_response_code() const
459 : {
460 0 : return f_response_code;
461 : }
462 :
463 :
464 0 : std::string http_response::get_http_message() const
465 : {
466 0 : return f_http_message;
467 : }
468 :
469 :
470 0 : bool http_response::has_header(std::string const & name) const
471 : {
472 0 : return f_header.find(name) != f_header.end();
473 : }
474 :
475 :
476 0 : std::string http_response::get_header(std::string const & name) const
477 : {
478 0 : return f_header.at(name);
479 : }
480 :
481 :
482 0 : std::string http_response::get_response() const
483 : {
484 0 : return f_response;
485 : }
486 :
487 :
488 0 : void http_response::append_original_header(std::string const & header)
489 : {
490 0 : f_original_header += header;
491 0 : f_original_header += "\r\n";
492 0 : }
493 :
494 :
495 0 : void http_response::set_protocol(protocol_t protocol)
496 : {
497 0 : f_protocol = protocol;
498 0 : }
499 :
500 :
501 0 : void http_response::set_response_code(int code)
502 : {
503 0 : f_response_code = code;
504 0 : }
505 :
506 :
507 0 : void http_response::set_http_message(std::string const & message)
508 : {
509 0 : f_http_message = message;
510 0 : }
511 :
512 :
513 0 : void http_response::set_header(std::string const& name, std::string const & value)
514 : {
515 0 : f_header[name] = value;
516 0 : }
517 :
518 :
519 0 : void http_response::set_response(std::string const & response)
520 : {
521 0 : f_response = response;
522 0 : }
523 :
524 :
525 0 : void http_response::read_response(tcp_client_server::bio_client::pointer_t connection)
526 : {
527 0 : struct reader
528 : {
529 0 : reader(http_response * response, tcp_client_server::bio_client::pointer_t connection)
530 0 : : f_response(response)
531 0 : , f_connection(connection)
532 : {
533 0 : }
534 :
535 : reader(reader const & rhs) = delete;
536 : reader & operator = (reader const & rhs) = delete;
537 :
538 0 : void process()
539 : {
540 0 : read_protocol();
541 0 : read_header();
542 0 : read_body();
543 0 : }
544 :
545 0 : int read_line(std::string& line)
546 : {
547 0 : int r(f_connection->read_line(line));
548 0 : if(r >= 1)
549 : {
550 0 : if(*line.rbegin() == '\r')
551 : {
552 : // remove the '\r' if present (should be)
553 0 : line.erase(line.end() - 1);
554 0 : --r;
555 : }
556 : }
557 0 : return r;
558 : }
559 :
560 0 : void read_protocol()
561 : {
562 : // first check that the protocol is HTTP and get the answer code
563 0 : SNAP_LOG_TRACE("*** read the protocol line");
564 0 : std::string protocol;
565 0 : int const r(read_line(protocol));
566 0 : if(r < 0)
567 : {
568 0 : SNAP_LOG_ERROR("read I/O error while reading HTTP protocol in response");
569 0 : throw http_client_exception_io_error("read I/O error while reading HTTP protocol in response");
570 : }
571 0 : f_response->append_original_header(protocol);
572 :
573 0 : SNAP_LOG_TRACE("*** got protocol: ")(protocol);
574 0 : char const *p(protocol.c_str());
575 0 : if(strncmp(p, "HTTP/1.0 ", 9) == 0)
576 : {
577 0 : f_response->set_protocol(protocol_t::HTTP_1_0);
578 0 : p += 9; // skip protocol
579 : }
580 0 : else if(strncmp(p, "HTTP/1.1 ", 9) == 0)
581 : {
582 0 : f_response->set_protocol(protocol_t::HTTP_1_1);
583 0 : p += 9; // skip protocol
584 : }
585 : else
586 : {
587 : // HTTP/2 is in the making, but it does not seem to
588 : // be officially out yet...
589 0 : SNAP_LOG_ERROR("unknown protocol \"")(protocol)("\", we only accept HTTP/1.0 and HTTP/1.1 at this time.");
590 0 : throw http_client_exception_io_error("read I/O error while reading HTTP protocol in response");
591 : }
592 : // skip any extra spaces (should be none)
593 0 : for(; isspace(*p); ++p);
594 0 : int count(0);
595 0 : int response_code(0);
596 0 : for(; *p >= '0' && *p <= '9'; ++p, ++count)
597 : {
598 : // no overflow check necessary, we expect from 1 to 3 digits
599 0 : response_code = response_code * 10 + (*p - '0');
600 : }
601 0 : if(count != 3)
602 : {
603 0 : SNAP_LOG_ERROR("unknown response code \"")(protocol)("\", all response code are expected to be three digits (i.e. 200, 401, or 500).");
604 0 : throw http_client_exception_io_error("unknown response code, expected exactly three digits");
605 : }
606 0 : f_response->set_response_code(response_code);
607 0 : SNAP_LOG_TRACE("*** +---> code: ")(response_code);
608 : // skip any spaces after the code
609 0 : for(; isspace(*p); ++p);
610 0 : f_response->set_http_message(p);
611 0 : SNAP_LOG_TRACE("*** +---> msg: ")(p);
612 0 : }
613 :
614 0 : void read_header()
615 : {
616 : for(;;)
617 : {
618 0 : std::string field;
619 0 : int const r(read_line(field));
620 0 : if(r < 0)
621 : {
622 0 : SNAP_LOG_ERROR("read I/O error while reading header");
623 0 : throw http_client_exception_io_error("read I/O error while reading header");
624 : }
625 0 : if(r == 0)
626 : {
627 : // found the empty line after the header
628 : // we are done reading the header then
629 0 : break;
630 : }
631 0 : f_response->append_original_header(field);
632 :
633 0 : SNAP_LOG_TRACE("got a header field: ")(field);
634 0 : char const * f(field.c_str());
635 0 : char const * e(f);
636 0 : for(; *e != ':' && *e != '\0'; ++e);
637 0 : if(*e != ':')
638 : {
639 : // TODO: add support for long fields that continue on
640 : // the following line
641 0 : SNAP_LOG_ERROR("invalid header, field definition does not include a colon");
642 0 : throw http_client_exception_io_error("invalid header, field definition does not include a colon");
643 : }
644 : // get the name and make it lowercase so we can search for
645 : // it with ease (HTTP field names are case insensitive)
646 0 : std::string name(f, e - f);
647 0 : std::transform(name.begin(), name.end(), name.begin(), ::tolower);
648 :
649 : // skip the ':' and then left trimming of spaces
650 0 : for(++e; isspace(*e); ++e);
651 0 : char const * end(f + field.length());
652 0 : for(; end > e && isspace(end[-1]); --end);
653 0 : std::string const value(e, end - e);
654 :
655 0 : f_response->set_header(name, value);
656 0 : }
657 0 : }
658 :
659 0 : void read_body()
660 : {
661 0 : if(f_response->has_header("content-length"))
662 : {
663 : // server sent a content-length parameter, make use of
664 : // it and do one "large" read
665 0 : std::string const length(f_response->get_header("content-length"));
666 0 : int64_t content_length(0);
667 0 : for(char const * l(length.c_str()); *l != '\0'; ++l)
668 : {
669 0 : if(*l < '0' || *l > '9')
670 : {
671 0 : SNAP_LOG_ERROR("server returned HTTP Content-Length \"")(length)("\", which includes invalid characters");
672 0 : throw http_client_exception_io_error("server returned an HTTP Content-Length which includes invalid characters");
673 : }
674 0 : content_length = content_length * 10 + (*l - '0');
675 0 : if(content_length > 0xFFFFFFFF) // TBD: should we have a much lower limit?
676 : {
677 0 : SNAP_LOG_ERROR("server returned an HTTP Content-Length of ")(length)(", which is too large");
678 0 : throw http_client_exception_io_error("server return an HTTP Content-Length which is too large");
679 : }
680 : }
681 : // if content-length is zero, the body response is empty
682 0 : if(content_length > 0)
683 : {
684 0 : std::vector<char> buffer;
685 0 : buffer.resize(content_length);
686 0 : SNAP_LOG_TRACE("reading ")(content_length)(" bytes...");
687 0 : int const r(f_connection->read(&buffer[0], content_length));
688 0 : if(r < 0)
689 : {
690 0 : SNAP_LOG_ERROR("read I/O error while reading response body");
691 0 : throw http_client_exception_io_error("read I/O error while reading response body");
692 : }
693 0 : if(r != content_length)
694 : {
695 0 : SNAP_LOG_ERROR("read returned before the entire content buffer was read");
696 0 : throw http_client_exception_io_error("read returned before the entire content buffer was read");
697 : }
698 0 : f_response->set_response(std::string(&buffer[0], content_length));
699 0 : SNAP_LOG_TRACE("body [")(f_response->get_response())("]...");
700 : }
701 : }
702 : else
703 : {
704 : // server did not specify the content-length, this means
705 : // the request ends when the connection gets closed
706 : char buffer[BUFSIZ];
707 0 : std::string response;
708 : for(;;)
709 : {
710 0 : int const r(f_connection->read(buffer, BUFSIZ));
711 0 : if(r < 0)
712 : {
713 0 : SNAP_LOG_ERROR("read I/O error while reading response body");
714 0 : throw http_client_exception_io_error("read I/O error while reading response body");
715 : }
716 0 : response += std::string(buffer, r);
717 0 : }
718 : f_response->set_response(response);
719 : }
720 0 : }
721 :
722 : http_response * f_response = nullptr;
723 : tcp_client_server::bio_client::pointer_t f_connection = tcp_client_server::bio_client::pointer_t();
724 0 : } r(this, connection);
725 :
726 0 : r.process();
727 0 : }
728 :
729 :
730 0 : bool http_client::get_keep_alive() const
731 : {
732 0 : return f_keep_alive;
733 : }
734 :
735 :
736 0 : void http_client::set_keep_alive(bool keep_alive)
737 : {
738 0 : f_keep_alive = keep_alive;
739 0 : }
740 :
741 :
742 0 : http_response::pointer_t http_client::send_request(http_request const & request)
743 : {
744 : // we can keep a connection alive, but the host and port cannot
745 : // change between calls... if you need to make such changes, you
746 : // may want to consider using another http_client object, otherwise
747 : // we disconnect the previous connection and reconnect with a new one
748 0 : int const port(request.get_port());
749 0 : std::string const host(request.get_host());
750 0 : if(f_connection
751 0 : && (f_host != host || f_port != port))
752 : {
753 0 : f_connection.reset();
754 : }
755 :
756 : // if we have no connection, create a new one
757 0 : if(!f_connection)
758 : {
759 : // TODO: allow user to specify the security instead of using the port
760 0 : f_connection.reset(new tcp_client_server::bio_client(
761 : host,
762 : port,
763 : port == 443 ? tcp_client_server::bio_client::mode_t::MODE_ALWAYS_SECURE
764 0 : : tcp_client_server::bio_client::mode_t::MODE_PLAIN));
765 0 : f_host = host;
766 0 : f_port = port;
767 : }
768 :
769 : // build and send the request to the server
770 0 : std::string const data(request.get_request(f_keep_alive));
771 : //std::cerr << "***\n*** request = [" << data << "]\n***\n";
772 0 : f_connection->write(data.c_str(), data.length());
773 :
774 : // create a response and read the server's answer in that object
775 0 : http_response::pointer_t p(new http_response);
776 0 : p->read_response(f_connection);
777 :
778 : // keep connection for further calls?
779 0 : if(!f_keep_alive
780 0 : || p->get_header("connection") == "close")
781 : {
782 0 : f_connection.reset();
783 : }
784 :
785 0 : return p;
786 : }
787 :
788 :
789 :
790 6 : } // namespace http_client_server
791 : // vim: ts=4 sw=4 et
|