1 module requests.http;
2 
3 private:
4 import std.algorithm;
5 import std.array;
6 import std.conv;
7 import std.datetime;
8 import std.exception;
9 import std.format;
10 import std.stdio;
11 import std.range;
12 import std.socket;
13 import std.string;
14 import std.traits;
15 import std.typecons;
16 import std.experimental.logger;
17 import core.thread;
18 import core.stdc.errno;
19 import requests.streams;
20 import requests.uri;
21 import requests.utils;
22 
23 static this() {
24     globalLogLevel(LogLevel.error);
25 }
26 
27 extern(C) {
28     int SSL_library_init();
29     void OpenSSL_add_all_ciphers();
30     void OpenSSL_add_all_digests();
31     void SSL_load_error_strings();
32     
33     struct SSL {}
34     struct SSL_CTX {}
35     struct SSL_METHOD {}
36     
37     SSL_CTX* SSL_CTX_new(const SSL_METHOD* method);
38     SSL* SSL_new(SSL_CTX*);
39     int SSL_set_fd(SSL*, int);
40     int SSL_connect(SSL*);
41     int SSL_write(SSL*, const void*, int);
42     int SSL_read(SSL*, void*, int);
43     int SSL_shutdown(SSL*) @trusted @nogc nothrow;
44     void SSL_free(SSL*);
45     void SSL_CTX_free(SSL_CTX*);
46     
47     long    SSL_CTX_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg);
48     
49     long SSL_CTX_set_mode(SSL_CTX *ctx, long mode);
50     long SSL_set_mode(SSL *ssl, long mode);
51     
52     long SSL_CTX_get_mode(SSL_CTX *ctx);
53     long SSL_get_mode(SSL *ssl);
54     
55     SSL_METHOD* SSLv3_client_method();
56     SSL_METHOD* TLSv1_2_client_method();
57     SSL_METHOD* TLSv1_client_method();
58 }
59 
60 pragma(lib, "crypto");
61 pragma(lib, "ssl");
62 
63 shared static this() {
64     SSL_library_init();
65     OpenSSL_add_all_ciphers();
66     OpenSSL_add_all_digests();
67     SSL_load_error_strings();
68 }
69 
70 class OpenSslSocket : Socket {
71     enum SSL_MODE_RELEASE_BUFFERS = 0x00000010L;
72     private SSL* ssl;
73     private SSL_CTX* ctx;
74     private void initSsl() {
75         //ctx = SSL_CTX_new(SSLv3_client_method());
76         ctx = SSL_CTX_new(TLSv1_client_method());
77         assert(ctx !is null);
78         
79         //SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS);
80         //SSL_CTX_ctrl(ctx, 33, SSL_MODE_RELEASE_BUFFERS, null);
81         ssl = SSL_new(ctx);
82         SSL_set_fd(ssl, this.handle);
83     }
84     
85     @trusted
86     override void connect(Address to) {
87         super.connect(to);
88         if(SSL_connect(ssl) == -1)
89             throw new Exception("ssl connect failed");
90     }
91     
92     @trusted
93     override ptrdiff_t send(const(void)[] buf, SocketFlags flags) {
94         return SSL_write(ssl, buf.ptr, cast(uint) buf.length);
95     }
96     override ptrdiff_t send(const(void)[] buf) {
97         return send(buf, SocketFlags.NONE);
98     }
99     @trusted
100     override ptrdiff_t receive(void[] buf, SocketFlags flags) {
101         return SSL_read(ssl, buf.ptr, cast(int)buf.length);
102     }
103     override ptrdiff_t receive(void[] buf) {
104         return receive(buf, SocketFlags.NONE);
105     }
106     this(AddressFamily af, SocketType type = SocketType.STREAM) {
107         super(af, type);
108         initSsl();
109     }
110     
111     this(socket_t sock, AddressFamily af) {
112         super(sock, af);
113         initSsl();
114     }
115     override void close() {
116         //SSL_shutdown(ssl);
117         super.close();
118     }
119     ~this() {
120         SSL_free(ssl);
121         SSL_CTX_free(ctx);
122     }
123 }
124 
125 unittest {
126     struct S {
127         private {
128             int    __i;
129             string __s;
130             bool   __b;
131         }
132         mixin(getter("i"));
133         mixin(setter("i"));
134         mixin(getter("b"));
135     }
136     S s;
137     assert(s.i == 0);
138     s.i = 1;
139     assert(s.i == 1);
140     assert(s.b == false);
141 }
142 
143 public class RequestException: Exception {
144     this(string msg, string file = __FILE__, size_t line = __LINE__) @safe pure {
145         super(msg, file, line);
146     }
147 }
148 
149 public class ConnectError: Exception {
150     this(string msg, string file = __FILE__, size_t line = __LINE__) @safe pure {
151         super(msg, file, line);
152     }
153 }
154 
155 public class TimeoutException: Exception {
156     this(string msg, string file = __FILE__, size_t line = __LINE__) @safe pure {
157         super(msg, file, line);
158     }
159 }
160 
161 interface Auth {
162     string[string] authHeaders(string domain);
163 }
164 /**
165  * Basic authentication.
166  * Adds $(B Authorization: Basic) header to request.
167  */
168 public class BasicAuthentication: Auth {
169     private {
170         string   _username, _password;
171         string[] _domains;
172     }
173     /// Constructor.
174     /// Params:
175     /// username = username
176     /// password = password
177     /// domains = not used now
178     /// 
179     this(string username, string password, string[] domains = []) {
180         _username = username;
181         _password = password;
182         _domains = domains;
183     }
184     override string[string] authHeaders(string domain) {
185         import std.base64;
186         string[string] auth;
187         auth["Authorization"] = "Basic " ~ to!string(Base64.encode(cast(ubyte[])"%s:%s".format(_username, _password)));
188         return auth;
189     }
190 }
191 
192 
193 abstract class SocketStream {
194     private {
195         Duration timeout;
196         Socket   s;
197         bool     __isOpen;
198         bool     __isConnected;
199     }
200     void open(AddressFamily fa) {
201     }
202     @property bool isOpen() @safe @nogc pure const {
203         return s && __isOpen;
204     }
205     @property bool isConnected() @safe @nogc pure const {
206         return s && __isConnected;
207     }
208     void close() {
209         tracef("Close socket");
210         if ( isOpen ) {
211             s.close();
212             __isOpen = false;
213             __isConnected = false;
214         }
215         s = null;
216     }
217 
218     auto connect(string host, ushort port, Duration timeout = 10.seconds) {
219         tracef(format("Create connection to %s:%d", host, port));
220         Address[] addresses;
221         __isConnected = false;
222         try {
223             addresses = getAddress(host, port);
224         } catch (Exception e) {
225             errorf("Failed to connect: can't resolve %s - %s", host, e.msg);
226             throw new ConnectError("Can't connect to %s:%d: %s".format(host, port, e.msg));
227         }
228         foreach(a; addresses) {
229             tracef("Trying %s", a);
230             try {
231                 open(a.addressFamily);
232                 s.setOption(SocketOptionLevel.SOCKET, SocketOption.SNDTIMEO, timeout);
233                 s.connect(a);
234                 tracef("Connected to %s", a);
235                 __isConnected = true;
236                 break;
237             } catch (SocketException e) {
238                 warningf("Failed to connect to %s:%d(%s): %s", host, port, a, e.msg);
239                 s.close();
240             }
241         }
242         if ( !__isConnected ) {
243             throw new ConnectError("Can't connect to %s:%d".format(host, port));
244         }
245         return this;
246     }
247 
248     ptrdiff_t send(const(void)[] buff)
249     in {assert(isConnected);}
250     body {
251         return s.send(buff);
252     }
253 
254     ptrdiff_t receive(void[] buff) {
255         auto r = s.receive(buff);
256         if ( r > 0 ) {
257             buff.length = r;
258         }
259         return r;
260     }
261 }
262 
263 class SSLSocketStream: SocketStream {
264     override void open(AddressFamily fa) {
265         if ( s !is null ) {
266             s.close();
267         }
268         s = new OpenSslSocket(fa);
269         assert(s !is null, "Can't create socket");
270         __isOpen = true;
271     }
272 }
273 class TCPSocketStream : SocketStream {
274     override void open(AddressFamily fa) {
275         if ( s !is null ) {
276             s.close();
277         }
278         s = new Socket(fa, SocketType.STREAM, ProtocolType.TCP);
279         assert(s !is null, "Can't create socket");
280         __isOpen = true;
281     }
282 }
283 
284 /**
285  * Call GET, and return response content.
286  * This is the simplest case, when all you need is the response body.
287  * Returns:
288  * Buffer!ubyte which you can use as ForwardRange or DirectAccessRange, or extract data with .data() method.
289  */
290 public auto getContent(A...)(A args) {
291     auto rq = Request();
292     rq.addHeaders(["Accept-Encoding":"gzip, deflate"]);
293     auto rs = rq.exec!"GET"(args);
294     return rs.responseBody;
295 }
296 ///
297 public unittest {
298     globalLogLevel(LogLevel.info);
299     auto r = getContent("https://httpbin.org/stream/20");
300     assert(r.splitter('\n').filter!("a.length>0").count == 20);
301 }
302 
303 /**
304  * Call post and return response content.
305  */
306 public auto postContent(A...)(A args) {
307     auto rq = Request();
308     rq.addHeaders(["Accept-Encoding":"gzip, deflate"]);
309     auto rs = rq.exec!"POST"(args);
310     return rs.responseBody;
311 }
312 ///
313 public unittest {
314     import std.json;
315     globalLogLevel(LogLevel.info);
316     auto r = postContent("http://httpbin.org/post", `{"a":"b", "c":1}`, "application/json");
317     assert(parseJSON(r.data).object["json"].object["c"].integer == 1);
318 }
319 
320 ///
321 /// Response - result of request execution.
322 ///
323 /// Response.code - response code
324 /// Response.status_line - received status line
325 /// Response.responseBody - container for received body
326 /// Response.history - for redirected responses contain all history
327 /// 
328 public struct Response {
329     private {
330         ushort         __code;
331         string         __status_line;
332         string[string] __responseHeaders;
333         Buffer!ubyte   __responseBody;
334         Response[]     __history; // redirects history
335         SysTime        __startedAt, __connectedAt, __requestSentAt, __finishedAt;
336     }
337    ~this() {
338         __responseHeaders = null;
339         __history.length = 0;
340     }
341     mixin(getter("code"));
342     mixin(getter("status_line"));
343     mixin(getter("responseHeaders"));
344     @property auto responseBody() inout pure @safe {
345         return __responseBody;
346     }
347     mixin(getter("history"));
348     private {
349         mixin(setter("code"));
350         mixin(setter("status_line"));
351         mixin(setter("responseHeaders"));
352         mixin(setter("responseBody"));
353     }
354     @property auto getStats() const pure @safe {
355         alias statTuple = Tuple!(Duration, "connectTime",
356                                  Duration, "sendTime",
357                                  Duration, "recvTime");
358         statTuple stat;
359         stat.connectTime = __connectedAt - __startedAt;
360         stat.sendTime = __requestSentAt - __connectedAt;
361         stat.recvTime = __finishedAt - __requestSentAt;
362         return stat;
363     }
364 }
365 
366 template rank(R) {
367     static if ( isInputRange!R ) {
368         enum size_t rank = 1 + rank!(ElementType!R);
369     } else {
370         enum size_t rank = 0;
371     }
372 }
373 unittest {
374     assert(rank!(char) == 0);
375     assert(rank!(string) == 1);
376     assert(rank!(ubyte[][]) == 2);
377 }
378 
379 static immutable ushort[] redirectCodes = [301, 302, 303];
380 
381 static string urlEncoded(string p) pure @safe {
382     immutable string[dchar] translationTable = [
383         ' ':  "%20", '!': "%21", '*': "%2A", '\'': "%27", '(': "%28", ')': "%29",
384         ';':  "%3B", ':': "%3A", '@': "%40", '&':  "%26", '=': "%3D", '+': "%2B",
385         '$':  "%24", ',': "%2C", '/': "%2F", '?':  "%3F", '#': "%23", '[': "%5B",
386         ']':  "%5D", '%': "%25",
387     ];
388     return p.translate(translationTable);
389 }
390 unittest {
391     assert(urlEncoded(`abc !#$&'()*+,/:;=?@[]`) == "abc%20%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D");
392 }
393 /**
394  * Struct to send multiple files in POST request.
395  */
396 public struct PostFile {
397     /// Path to the file to send.
398     string fileName;
399     /// Name of the field (if empty - send file base name)
400     string fieldName;
401     /// contentType of the file if not empty
402     string contentType;
403 }
404 ///
405 /// Request.
406 /// Configurable parameters:
407 /// $(B headers) - add any additional headers you'd like to send.
408 /// $(B authenticator) - class to send auth headers.
409 /// $(B keepAlive) - set true for keepAlive requests. default false.
410 /// $(B maxRedirects) - maximum number of redirects. default 10.
411 /// $(B maxHeadersLength) - maximum length of server response headers. default = 32KB.
412 /// $(B maxContentLength) - maximun content length. delault = 5MB.
413 /// $(B bufferSize) - send and receive buffer size. default = 16KB.
414 /// $(B verbosity) - level of verbosity(0 - nothing, 1 - headers, 2 - headers and body progress). default = 0.
415 /// $(B proxy) - set proxy url if needed. default - null.
416 /// 
417 public struct Request {
418     private {
419         string         __method = "GET";
420         URI            __uri;
421         string[string] __headers;
422         Auth           __authenticator;
423         bool           __keepAlive;
424         uint           __maxRedirects = 10;
425         size_t         __maxHeadersLength = 32 * 1024; // 32 KB
426         size_t         __maxContentLength = 5 * 1024 * 1024; // 5MB
427         ptrdiff_t      __contentLength;
428         SocketStream   __stream;
429         Duration       __timeout = 30.seconds;
430         Response       __response;
431         Response[]     __history; // redirects history
432         size_t         __bufferSize = 16*1024; // 16k
433         uint           __verbosity = 0;  // 0 - no output, 1 - headers, 2 - headers+body info
434         DataPipe!ubyte __bodyDecoder;
435         DecodeChunked  __unChunker;
436         string         __proxy;
437     }
438 
439     mixin(getter("keepAlive"));
440     mixin(setter("keepAlive"));
441     mixin(getter("method"));
442     mixin(setter("method"));
443     mixin(getter("timeout"));
444     mixin(setter("timeout"));
445     mixin(setter("authenticator"));
446     mixin(getter("maxContentLength"));
447     mixin(setter("maxContentLength"));
448     mixin(getter("maxRedirects"));
449     mixin(setter("maxRedirects"));
450     mixin(getter("maxHeadersLength"));
451     mixin(setter("maxHeadersLength"));
452     mixin(getter("bufferSize"));
453     mixin(setter("bufferSize"));
454     mixin(getter("verbosity"));
455     mixin(setter("verbosity"));
456     mixin(setter("proxy"));
457 
458     this(string uri) {
459         __uri = URI(uri);
460     }
461    ~this() {
462         if ( __stream && __stream.isConnected) {
463             __stream.close();
464         }
465         __stream = null;
466         __headers = null;
467         __authenticator = null;
468         __history = null;
469     }
470     /// Add headers to request
471     /// Params:
472     /// headers = headers to send.
473     void addHeaders(in string[string] headers) {
474         foreach(pair; headers.byKeyValue) {
475             __headers[pair.key] = pair.value;
476         }
477     }
478     ///
479     /// compose headers to send
480     /// 
481     private @property string[string] headers() {
482         string[string] generatedHeaders;
483         if ( __authenticator ) {
484             foreach(pair; __authenticator.authHeaders(__uri.host).byKeyValue) {
485                 generatedHeaders[pair.key] = pair.value;
486             }
487         }
488 
489         generatedHeaders["Connection"] = __keepAlive?"Keep-Alive":"Close";
490         generatedHeaders["Host"] = __uri.host;
491         if ( __uri.scheme !in standard_ports || __uri.port != standard_ports[__uri.scheme] ) {
492             generatedHeaders["Host"] ~= ":%d".format(__uri.port);
493         }
494         foreach(pair; __headers.byKeyValue) {
495             generatedHeaders[pair.key] = pair.value;
496         }
497         return generatedHeaders;
498     }
499     ///
500     /// Build request string.
501     /// Handle proxy and query parameters.
502     /// 
503     private @property string requestString(string[string] params = null) {
504         if ( __proxy ) {
505             return "%s %s HTTP/1.1\r\n".format(__method, __uri.uri);
506         }
507         auto query = __uri.query.dup;
508         if ( params ) {
509             query ~= params2query(params);
510             if ( query[0] != '?' ) {
511                 query = "?" ~ query;
512             }
513         }
514         return "%s %s%s HTTP/1.1\r\n".format(__method, __uri.path, query);
515     }
516     ///
517     /// encode parameters and build query part of the url
518     /// 
519     private static string params2query(string[string] params) {
520         auto m = params.keys.
521                         sort().
522                         map!(a=>urlEncoded(a) ~ "=" ~ urlEncoded(params[a])).
523                         join("&");
524         return m;
525     }
526     unittest {
527         assert(Request.params2query(["c ":"d", "a":"b"])=="a=b&c%20=d");
528     }
529     ///
530     /// Analyze received headers, take appropriate actions:
531     /// check content length, attach unchunk and uncompress
532     /// 
533     private void analyzeHeaders(in string[string] headers) {
534 
535         __contentLength = -1;
536         __unChunker = null;
537         auto contentLength = "content-length" in headers;
538         if ( contentLength ) {
539             try {
540                 __contentLength = to!ptrdiff_t(*contentLength);
541                 if ( __contentLength > maxContentLength) {
542                     throw new RequestException("ContentLength > maxContentLength (%d>%d)".
543                                 format(__contentLength, __maxContentLength));
544                 }
545             } catch (ConvException e) {
546                 throw new RequestException("Can't convert Content-Length from %s".format(*contentLength));
547             }
548         }
549         auto transferEncoding = "transfer-encoding" in headers;
550         if ( transferEncoding ) {
551             tracef("transferEncoding: %s", *transferEncoding);
552             if ( *transferEncoding == "chunked") {
553                 __unChunker = new DecodeChunked();
554                 __bodyDecoder.insert(__unChunker);
555             }
556         }
557         auto contentEncoding = "content-encoding" in headers;
558         if ( contentEncoding ) switch (*contentEncoding) {
559             default:
560                 throw new RequestException("Unknown content-encoding " ~ *contentEncoding);
561             case "gzip":
562             case "deflate":
563                 __bodyDecoder.insert(new Decompressor!ubyte);
564         }
565     }
566     ///
567     /// Called when we know that all headers already received in buffer
568     /// 1. Split headers on lines
569     /// 2. store status line, store response code
570     /// 3. unfold headers if needed
571     /// 4. store headers
572     /// 
573     private void parseResponseHeaders(ref Buffer!ubyte buffer) {
574         string lastHeader;
575         foreach(line; buffer.data!(string).split("\n").map!(l => l.stripRight)) {
576             if ( ! __response.status_line.length ) {
577                 tracef("statusLine: %s", line);
578                 __response.status_line = line;
579                 if ( __verbosity >= 1 ) {
580                     writefln("< %s", line);
581                 }
582                 auto parsed = line.split(" ");
583                 if ( parsed.length >= 3 ) {
584                     __response.code = parsed[1].to!ushort;
585                 }
586                 continue;
587             }
588             if ( line[0] == ' ' || line[0] == '\t' ) {
589                 // unfolding https://tools.ietf.org/html/rfc822#section-3.1
590                 auto stored = lastHeader in __response.__responseHeaders;
591                 if ( stored ) {
592                     *stored ~= line;
593                 }
594                 continue;
595             }
596             auto parsed = line.findSplit(":");
597             auto header = parsed[0].toLower;
598             auto value = parsed[2].strip;
599             auto stored = __response.responseHeaders.get(header, null);
600             if ( stored ) {
601                 value = stored ~ ", " ~ value;
602             }
603             __response.__responseHeaders[header] = value;
604             if ( __verbosity >= 1 ) {
605                 writefln("< %s: %s", parsed[0], value);
606             }
607 
608             tracef("Header %s = %s", header, value);
609             lastHeader = header;
610         }
611     }
612 
613     ///
614     /// Do we received \r\n\r\n?
615     /// 
616     private bool headersHaveBeenReceived(in ubyte[] data, ref Buffer!ubyte buffer, out string separator) pure const @safe {
617         foreach(s; ["\r\n\r\n", "\n\n"]) {
618             if ( data.canFind(s) || buffer.canFind(s) ) {
619                 separator = s;
620                 return true;
621             }
622         }
623         return false;
624     }
625 
626     private bool followRedirectResponse() {
627         __history ~= __response;
628         if ( __history.length >= __maxRedirects ) {
629             return false;
630         }
631         auto location = "location" in __response.responseHeaders;
632         if ( !location ) {
633             return false;
634         }
635         auto connection = "connection" in __response.__responseHeaders;
636         if ( !connection || *connection == "close" ) {
637             tracef("Closing connection because of 'Connection: close' or no 'Connection' header");
638             __stream.close();
639         }
640         URI oldURI = __uri;
641         URI newURI = oldURI;
642         try {
643             newURI = URI(*location);
644         } catch (UriException e) {
645             trace("Can't parse Location:, try relative uri");
646             newURI.path = *location;
647             newURI.uri = newURI.recalc_uri;
648         }
649         handleURLChange(oldURI, newURI);
650         __uri = newURI;
651         __response = Response.init;
652         return true;
653     }
654     ///
655     /// If uri changed so that we have to change host or port, then we have to close socket stream
656     /// 
657     private void handleURLChange(in URI from, in URI to) {
658         if ( __stream !is null && __stream.isConnected && 
659             ( from.scheme != to.scheme || from.host != to.host || from.port != to.port) ) {
660             tracef("Have to reopen stream");
661             __stream.close();
662         }
663     }
664     
665     private void checkURL(string url, string file=__FILE__, size_t line=__LINE__) {
666         if (url is null && __uri.uri == "" ) {
667             throw new RequestException("No url configured", file, line);
668         }
669         
670         if ( url !is null ) {
671             URI newURI = URI(url);
672             handleURLChange(__uri, newURI);
673             __uri = newURI;
674         }
675     }
676     ///
677     /// Setup connection. Handle proxy and https case
678     /// 
679     private void setupConnection() {
680         if ( !__stream || !__stream.isConnected ) {
681             tracef("Set up new connection");
682             URI   uri;
683             if ( __proxy ) {
684                 // use proxy uri to connect
685                 uri.uri_parse(__proxy);
686             } else {
687                 // use original uri
688                 uri = __uri;
689             }
690             final switch (uri.scheme) {
691                 case "http":
692                     __stream = new TCPSocketStream().connect(uri.host, uri.port, __timeout);
693                     break;
694                 case "https":
695                     __stream = new SSLSocketStream().connect(uri.host, uri.port, __timeout);
696                     break;
697             }
698         } else {
699             tracef("Use old connection");
700         }
701     }
702     ///
703     /// Receive response after request we sent.
704     /// Find headers, split on headers and body, continue to receive body
705     /// 
706     private void receiveResponse() {
707 
708         __stream.s.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, timeout);
709         scope(exit) {
710             __stream.s.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, 0.seconds);
711         }
712 
713         __bodyDecoder = new DataPipe!ubyte();
714         auto b = new ubyte[__bufferSize];
715         scope(exit) {
716             __bodyDecoder = null;
717             __unChunker = null;
718             b = null;
719         }
720 
721         auto buffer = Buffer!ubyte();
722         Buffer!ubyte ResponseHeaders, partialBody;
723         size_t receivedBodyLength;
724         ptrdiff_t read;
725         string separator;
726         
727         while(true) {
728             read = __stream.receive(b);
729             tracef("read: %d", read);
730             if ( read < 0 ) {
731                 if ( errno == EAGAIN ) {
732                     throw new TimeoutException("Timeout receiving headers");
733                 }
734                 throw new ErrnoException("receiving Headers");
735             }
736             if ( read == 0 ) {
737                 break;
738             }
739             
740             auto data = b[0..read];
741             buffer.put(data);
742             if ( buffer.length > maxHeadersLength ) {
743                 throw new RequestException("Headers length > maxHeadersLength (%d > %d)".format(buffer.length, maxHeadersLength));
744             }
745             if ( headersHaveBeenReceived(data, buffer, separator) ) {
746                 auto s = buffer.data!(ubyte[]).findSplit(separator);
747                 ResponseHeaders = Buffer!ubyte(s[0]);
748                 partialBody = Buffer!ubyte(s[2]);
749                 receivedBodyLength += partialBody.length;
750                 parseResponseHeaders(ResponseHeaders);
751                 break;
752             }
753         }
754         
755         analyzeHeaders(__response.__responseHeaders);
756         __bodyDecoder.put(partialBody);
757 
758         if ( __verbosity >= 2 ) {
759             writefln("< %d bytes of body received", partialBody.length);
760         }
761 
762         if ( __method == "HEAD" ) {
763             // HEAD response have ContentLength, but have no body
764             return;
765         }
766 
767         while( true ) {
768             if ( __contentLength >= 0 && receivedBodyLength >= __contentLength ) {
769                 trace("Body received.");
770                 break;
771             }
772             if ( __unChunker && __unChunker.done ) {
773                 break;
774             }
775             read = __stream.receive(b);
776             if ( read < 0 ) {
777                 if ( errno == EAGAIN ) {
778                     throw new TimeoutException("Timeout receiving body");
779                 }
780                 throw new ErrnoException("receiving body");
781             }
782             if ( __verbosity >= 2 ) {
783                 writefln("< %d bytes of body received", read);
784             }
785             tracef("read: %d", read);
786             if ( read == 0 ) {
787                 trace("read done");
788                 break;
789             }
790             receivedBodyLength += read;
791             __bodyDecoder.put(b[0..read].dup);
792             __response.__responseBody.put(__bodyDecoder.get());
793             tracef("receivedTotal: %d, contentLength: %d, bodyLength: %d", receivedBodyLength, __contentLength, __response.__responseBody.length);
794         }
795         __bodyDecoder.flush();
796         __response.__responseBody.put(__bodyDecoder.get());
797     }
798     ///
799     /// execute POST request.
800     /// Send form-urlencoded data
801     /// 
802     /// Parameters:
803     ///     url = url to request
804     ///     rqData = data to send
805     ///  Returns:
806     ///     Response
807     ///  Examples:
808     ///  ------------------------------------------------------------------
809     ///  rs = rq.exec!"POST"("http://httpbin.org/post", ["a":"b", "c":"d"]);
810     ///  ------------------------------------------------------------------
811     ///
812     Response exec(string method)(string url, string[string] rqData) if (method=="POST") {
813         //
814         // application/x-www-form-urlencoded
815         //
816         __method = method;
817 
818         __response = Response.init;
819         checkURL(url);
820     connect:
821         __response.__startedAt = Clock.currTime;
822         setupConnection();
823         
824         if ( !__stream.isConnected() ) {
825             return __response;
826         }
827         __response.__connectedAt = Clock.currTime;
828 
829         string encoded = params2query(rqData);
830         auto h = headers;
831         h["Content-Type"] = "application/x-www-form-urlencoded";
832         h["Content-Length"] = to!string(encoded.length);
833 
834         Appender!string req;
835         req.put(requestString());
836         h.byKeyValue.
837             map!(kv => kv.key ~ ": " ~ kv.value ~ "\r\n").
838                 each!(h => req.put(h));
839         req.put("\r\n");
840         req.put(encoded);
841         trace(req.data);
842 
843         if ( __verbosity >= 1 ) {
844             req.data.splitLines.each!(a => writeln("> " ~ a));
845         }
846 
847         auto rc = __stream.send(req.data());
848         if ( rc == -1 ) {
849             errorf("Error sending request: ", lastSocketError);
850             return __response;
851         }
852         __response.__requestSentAt = Clock.currTime;
853 
854         receiveResponse();
855 
856         __response.__finishedAt = Clock.currTime;
857 
858         auto connection = "connection" in __response.__responseHeaders;
859         if ( !connection || *connection == "close" ) {
860             tracef("Closing connection because of 'Connection: close' or no 'Connection' header");
861             __stream.close();
862         }
863         if ( canFind(redirectCodes, __response.__code) && followRedirectResponse() ) {
864             if ( __method != "GET" ) {
865                 return this.get();
866             }
867             goto connect;
868         }
869         __response.__history = __history;
870         return __response;
871     }
872     ///
873     /// send file(s) using POST
874     /// Parameters:
875     ///     url = url
876     ///     files = array of PostFile structures
877     /// Returns:
878     ///     Response
879     /// Example:
880     /// ---------------------------------------------------------------
881     ///    PostFile[] files = [
882     ///                   {fileName:"tests/abc.txt", fieldName:"abc", contentType:"application/octet-stream"}, 
883     ///                   {fileName:"tests/test.txt"}
884     ///               ];
885     ///    rs = rq.exec!"POST"("http://httpbin.org/post", files);
886     /// ---------------------------------------------------------------
887     /// 
888     Response exec(string method="POST")(string url, PostFile[] files) {
889         import std.uuid;
890         import std.file;
891         //
892         // application/json
893         //
894         bool restartedRequest = false;
895         
896         __method = method;
897         
898         __response = Response.init;
899         checkURL(url);
900     connect:
901         __response.__startedAt = Clock.currTime;
902         setupConnection();
903         
904         if ( !__stream.isConnected() ) {
905             return __response;
906         }
907         __response.__connectedAt = Clock.currTime;
908 
909         Appender!string req;
910         req.put(requestString());
911         
912         string   boundary = randomUUID().toString;
913         string[] partHeaders;
914         size_t   contentLength;
915 
916         foreach(part; files) {
917             string fieldName = part.fieldName ? part.fieldName : part.fileName;
918             string h = "--" ~ boundary ~ "\r\n";
919             h ~= `Content-Disposition: form-data; name="%s"; filename="%s"`.
920                 format(fieldName, part.fileName) ~ "\r\n";
921             if ( part.contentType ) {
922                 h ~= "Content-Type: " ~ part.contentType ~ "\r\n";
923             }
924             h ~= "\r\n";
925             partHeaders ~= h;
926             contentLength += h.length + getSize(part.fileName) + "\r\n".length;
927         }
928         contentLength += "--".length + boundary.length + "--\r\n".length;
929 
930         auto h = headers;
931         h["Content-Type"] = "multipart/form-data; boundary=" ~ boundary;
932         h["Content-Length"] = to!string(contentLength);
933         h.byKeyValue.
934             map!(kv => kv.key ~ ": " ~ kv.value ~ "\r\n").
935             each!(h => req.put(h));
936         req.put("\r\n");
937         
938         trace(req.data);
939         if ( __verbosity >= 1 ) {
940             req.data.splitLines.each!(a => writeln("> " ~ a));
941         }
942 
943         auto rc = __stream.send(req.data());
944         if ( rc == -1 ) {
945             errorf("Error sending request: ", lastSocketError);
946             return __response;
947         }
948         foreach(hdr, f; zip(partHeaders, files)) {
949             tracef("sending part headers <%s>", hdr);
950             __stream.send(hdr);
951             auto file = File(f.fileName, "rb");
952             scope(exit) {
953                 file.close();
954             }
955             foreach(chunk; file.byChunk(16*1024)) {
956                 __stream.send(chunk);
957             }
958             __stream.send("\r\n");
959         }
960         __stream.send("--" ~ boundary ~ "--\r\n");
961         __response.__requestSentAt = Clock.currTime;
962 
963         receiveResponse();
964 
965         if ( __response.__responseHeaders.length == 0 
966             && __keepAlive
967             && !restartedRequest
968             && __method == "GET"
969             ) {
970             tracef("Server closed keepalive connection");
971             __stream.close();
972             restartedRequest = true;
973             goto connect;
974         }
975 
976         __response.__finishedAt = Clock.currTime;
977         ///
978         auto connection = "connection" in __response.__responseHeaders;
979         if ( !connection || *connection == "close" ) {
980             tracef("Closing connection because of 'Connection: close' or no 'Connection' header");
981             __stream.close();
982         }
983         if ( canFind(redirectCodes, __response.__code) && followRedirectResponse() ) {
984             if ( __method != "GET" ) {
985                 return this.get();
986             }
987             goto connect;
988         }
989         __response.__history = __history;
990         ///
991         return __response;
992     }
993     ///
994     /// POST data from some string(with Content-Length), or from range of strings (use Transfer-Encoding: chunked)
995     /// 
996     /// Parameters:
997     ///    url = url
998     ///    content = string or input range
999     ///    contentType = content type
1000     ///  Returns:
1001     ///     Response
1002     ///  Examples:
1003     ///  ---------------------------------------------------------------------------------------------------------
1004     ///      rs = rq.exec!"POST"("http://httpbin.org/post", "привiт, свiт!", "application/octet-stream");
1005     ///      
1006     ///      auto s = lineSplitter("one,\ntwo,\nthree.");
1007     ///      rs = rq.exec!"POST"("http://httpbin.org/post", s, "application/octet-stream");
1008     ///      
1009     ///      auto s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1010     ///      rs = rq.exec!"POST"("http://httpbin.org/post", s.representation.chunks(10), "application/octet-stream");
1011     ///
1012     ///      auto f = File("tests/test.txt", "rb");
1013     ///      rs = rq.exec!"POST"("http://httpbin.org/post", f.byChunk(3), "application/octet-stream");
1014     ///  --------------------------------------------------------------------------------------------------------
1015     Response exec(string method="POST", R)(string url, R content, string contentType="text/html")
1016         if ( isSomeString!R
1017             || (rank!R == 2 && isSomeChar!(Unqual!(typeof(content.front.front)))) 
1018             || (rank!R == 2 && (is(Unqual!(typeof(content.front.front)) == ubyte)))
1019             )
1020     {
1021         //
1022         // application/json
1023         //
1024         bool restartedRequest = false;
1025         
1026         __method = method;
1027         
1028         __response = Response.init;
1029         checkURL(url);
1030     connect:
1031         __response.__startedAt = Clock.currTime;
1032         setupConnection();
1033         
1034         if ( !__stream.isConnected() ) {
1035             return __response;
1036         }
1037         __response.__connectedAt = Clock.currTime;
1038 
1039         Appender!string req;
1040         req.put(requestString());
1041 
1042         auto h = headers;
1043         h["Content-Type"] = contentType;
1044         static if ( isSomeString!R ) {
1045             h["Content-Length"] = to!string(content.length);
1046         } else {
1047             h["Transfer-Encoding"] = "chunked";
1048         }
1049         h.byKeyValue.
1050             map!(kv => kv.key ~ ": " ~ kv.value ~ "\r\n").
1051             each!(h => req.put(h));
1052         req.put("\r\n");
1053 
1054         trace(req.data);
1055         if ( __verbosity >= 1 ) {
1056             req.data.splitLines.each!(a => writeln("> " ~ a));
1057         }
1058 
1059         auto rc = __stream.send(req.data());
1060         if ( rc == -1 ) {
1061             errorf("Error sending request: ", lastSocketError);
1062             return __response;
1063         }
1064 
1065         static if ( isSomeString!R ) {
1066             __stream.send(content);
1067         } else {
1068             while ( !content.empty ) {
1069                 auto chunk = content.front;
1070                 auto chunkHeader = "%x\r\n".format(chunk.length);
1071                 tracef("sending %s%s", chunkHeader, chunk);
1072                 __stream.send(chunkHeader);
1073                 __stream.send(chunk);
1074                 __stream.send("\r\n");
1075                 content.popFront;
1076             }
1077             tracef("sent");
1078             __stream.send("0\r\n\r\n");
1079         }
1080         __response.__requestSentAt = Clock.currTime;
1081 
1082         receiveResponse();
1083 
1084         if ( __response.__responseHeaders.length == 0 
1085             && __keepAlive
1086             && !restartedRequest
1087             && __method == "GET"
1088             ) {
1089             tracef("Server closed keepalive connection");
1090             __stream.close();
1091             restartedRequest = true;
1092             goto connect;
1093         }
1094 
1095         __response.__finishedAt = Clock.currTime;
1096 
1097         ///
1098         auto connection = "connection" in __response.__responseHeaders;
1099         if ( !connection || *connection == "close" ) {
1100             tracef("Closing connection because of 'Connection: close' or no 'Connection' header");
1101             __stream.close();
1102         }
1103         if ( canFind(redirectCodes, __response.__code) && followRedirectResponse() ) {
1104             if ( __method != "GET" ) {
1105                 return this.get();
1106             }
1107             goto connect;
1108         }
1109         ///
1110         __response.__history = __history;
1111         return __response;
1112     }
1113     ///
1114     /// Send request without data
1115     /// Request parameters will be encoded into request string
1116     /// Parameters:
1117     ///     url = url
1118     ///     params = request parameters
1119     ///  Returns:
1120     ///     Response
1121     ///  Examples:
1122     ///  ---------------------------------------------------------------------------------
1123     ///     rs = Request().exec!"GET"("http://httpbin.org/get", ["c":"d", "a":"b"]);
1124     ///  ---------------------------------------------------------------------------------
1125     ///     
1126     Response exec(string method="GET")(string url = null, string[string] params = null) if (method != "POST")
1127     {
1128 
1129         __method = method;
1130         __response = Response.init;
1131         __history.length = 0;
1132         bool restartedRequest = false; // True if this is restarted keepAlive request
1133 
1134         checkURL(url);
1135 
1136     connect:
1137         __response.__startedAt = Clock.currTime;
1138         setupConnection();
1139 
1140         if ( !__stream.isConnected() ) {
1141             return __response;
1142         }
1143         __response.__connectedAt = Clock.currTime;
1144 
1145         Appender!string req;
1146         req.put(requestString(params));
1147         headers.byKeyValue.
1148             map!(kv => kv.key ~ ": " ~ kv.value ~ "\r\n").
1149             each!(h => req.put(h));
1150         req.put("\r\n");
1151         trace(req.data);
1152 
1153         if ( __verbosity >= 1 ) {
1154             req.data.splitLines.each!(a => writeln("> " ~ a));
1155         }
1156         auto rc = __stream.send(req.data());
1157         if ( rc == -1 ) {
1158             errorf("Error sending request: ", lastSocketError);
1159             return __response;
1160         }
1161         __response.__requestSentAt = Clock.currTime;
1162 
1163         receiveResponse();
1164 
1165         if ( __response.__responseHeaders.length == 0 
1166             && __keepAlive
1167             && !restartedRequest
1168             && __method == "GET"
1169         ) {
1170             tracef("Server closed keepalive connection");
1171             __stream.close();
1172             restartedRequest = true;
1173             goto connect;
1174         }
1175         __response.__finishedAt = Clock.currTime;
1176 
1177         ///
1178         auto connection = "connection" in __response.__responseHeaders;
1179         if ( !connection || *connection == "close" ) {
1180             tracef("Closing connection because of 'Connection: close' or no 'Connection' header");
1181             __stream.close();
1182         }
1183         if ( __verbosity >= 1 ) {
1184             writeln(">> Connect time: ", __response.__connectedAt - __response.__startedAt);
1185             writeln(">> Request send time: ", __response.__requestSentAt - __response.__connectedAt);
1186             writeln(">> Response recv time: ", __response.__finishedAt - __response.__requestSentAt);
1187         }
1188         if ( canFind(redirectCodes, __response.__code) && followRedirectResponse() ) {
1189             if ( __method != "GET" ) {
1190                 return this.get();
1191             }
1192             goto connect;
1193         }
1194         ///
1195         __response.__history = __history;
1196         return __response;
1197     }
1198     ///
1199     /// GET request. Simple wrapper over exec!"GET"
1200     /// Params:
1201     /// args = request parameters. see exec docs.
1202     ///
1203     Response get(A...)(A args) {
1204         return exec!"GET"(args);
1205     }
1206     ///
1207     /// POST request. Simple wrapper over exec!"POST"
1208     /// Params:
1209     /// args = request parameters. see exec docs.
1210     ///
1211     Response post(A...)(A args) {
1212         return exec!"POST"(args);
1213     }
1214 }
1215 
1216 ///
1217 public unittest {
1218     import std.json;
1219     globalLogLevel(LogLevel.info);
1220     tracef("http tests - start");
1221 
1222     auto rq = Request();
1223     auto rs = rq.get("https://httpbin.org/");
1224     assert(rs.code==200);
1225     assert(rs.responseBody.length > 0);
1226     rs = Request().get("http://httpbin.org/get", ["c":" d", "a":"b"]);
1227     assert(rs.code == 200);
1228     auto json = parseJSON(rs.responseBody.data).object["args"].object;
1229     assert(json["c"].str == " d");
1230     assert(json["a"].str == "b");
1231 
1232     globalLogLevel(LogLevel.info);
1233     rq = Request();
1234     rq.keepAlive = true;
1235     // handmade json
1236     info("Check POST json");
1237     rs = rq.post("http://httpbin.org/post?b=x", `{"a":"☺ ", "c":[1,2,3]}`, "application/json");
1238     assert(rs.code==200);
1239     json = parseJSON(rs.responseBody.data).object["args"].object;
1240     assert(json["b"].str == "x");
1241     json = parseJSON(rs.responseBody.data).object["json"].object;
1242     assert(json["a"].str == "☺ ");
1243     assert(json["c"].array.map!(a=>a.integer).array == [1,2,3]);
1244     {
1245         // files
1246         globalLogLevel(LogLevel.info);
1247         info("Check POST files");
1248         PostFile[] files = [
1249                         {fileName:"tests/abc.txt", fieldName:"abc", contentType:"application/octet-stream"}, 
1250                         {fileName:"tests/test.txt"}
1251                     ];
1252         rs = rq.post("http://httpbin.org/post", files);
1253         assert(rs.code==200);
1254     }
1255     {
1256         // string
1257         info("Check POST utf8 string");
1258         rs = rq.post("http://httpbin.org/post", "привiт, свiт!", "application/octet-stream");
1259         assert(rs.code==200);
1260         auto data = parseJSON(rs.responseBody.data).object["data"].str;
1261         assert(data=="привiт, свiт!");
1262     }
1263     // ranges
1264     {
1265         info("Check POST chunked from lineSplitter");
1266         auto s = lineSplitter("one,\ntwo,\nthree.");
1267         rs = rq.exec!"POST"("http://httpbin.org/post", s, "application/octet-stream");
1268         assert(rs.code==200);
1269         auto data = parseJSON(rs.responseBody.toString).object["data"].str;
1270         assert(data=="one,two,three.");
1271     }
1272     {
1273         info("Check POST chunked from array");
1274         auto s = ["one,", "two,", "three."];
1275         rs = rq.post("http://httpbin.org/post", s, "application/octet-stream");
1276         assert(rs.code==200);
1277         auto data = parseJSON(rs.responseBody.data).object["data"].str;
1278         assert(data=="one,two,three.");
1279     }
1280     {
1281         info("Check POST chunked using std.range.chunks()");
1282         auto s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1283         rs = rq.post("http://httpbin.org/post", s.representation.chunks(10), "application/octet-stream");
1284         assert(rs.code==200);
1285         auto data = parseJSON(rs.responseBody.data).object["data"].str;
1286         assert(data==s);
1287     }
1288     {
1289         info("Check POST chunked from file.byChunk");
1290         auto f = File("tests/test.txt", "rb");
1291         rs = rq.post("http://httpbin.org/post", f.byChunk(3), "application/octet-stream");
1292         assert(rs.code==200);
1293         auto data = parseJSON(rs.responseBody.data).object["data"].str;
1294         assert(data=="abcdefgh\n12345678\n");
1295         f.close();
1296     }
1297     // associative array
1298     rs = rq.post("http://httpbin.org/post", ["a":"b ", "c":"d"]);
1299     assert(rs.code==200);
1300     auto form = parseJSON(rs.responseBody.data).object["form"].object;
1301     assert(form["a"].str == "b ");
1302     assert(form["c"].str == "d");
1303     info("Check HEAD");
1304     rs = rq.exec!"HEAD"("http://httpbin.org/");
1305     assert(rs.code==200);
1306     info("Check DELETE");
1307     rs = rq.exec!"DELETE"("http://httpbin.org/delete");
1308     assert(rs.code==200);
1309     info("Check PUT");
1310     rs = rq.exec!"PUT"("http://httpbin.org/put",  `{"a":"b", "c":[1,2,3]}`, "application/json");
1311     assert(rs.code==200);
1312     info("Check PATCH");
1313     rs = rq.exec!"PATCH"("http://httpbin.org/patch", "привiт, свiт!", "application/octet-stream");
1314     assert(rs.code==200);
1315 
1316     info("Check compressed content");
1317     globalLogLevel(LogLevel.info);
1318     rq = Request();
1319     rq.keepAlive = true;
1320     rq.addHeaders(["Accept-Encoding":"gzip"]);
1321     rs = rq.get("http://httpbin.org/gzip");
1322     assert(rs.code==200);
1323     info("gzip - ok");
1324     rq.addHeaders(["Accept-Encoding":"deflate"]);
1325     rs = rq.get("http://httpbin.org/deflate");
1326     assert(rs.code==200);
1327     info("deflate - ok");
1328 
1329     info("Check redirects");
1330     globalLogLevel(LogLevel.info);
1331     rq = Request();
1332     rq.keepAlive = true;
1333     rs = rq.get("http://httpbin.org/relative-redirect/2");
1334     assert(rs.history.length == 2);
1335     assert(rs.code==200);
1336 //    rq = Request();
1337 //    rq.keepAlive = true;
1338 //    rq.proxy = "http://localhost:8888/";
1339     rs = rq.get("http://httpbin.org/absolute-redirect/2");
1340     assert(rs.history.length == 2);
1341     assert(rs.code==200);
1342 //    rq = Request();
1343     rq.maxRedirects = 2;
1344     rq.keepAlive = false;
1345     rs = rq.get("https://httpbin.org/absolute-redirect/3");
1346     assert(rs.history.length == 2);
1347     assert(rs.code==302);
1348 
1349     info("Check utf8 content");
1350     globalLogLevel(LogLevel.info);
1351     rq = Request();
1352     rs = rq.get("http://httpbin.org/encoding/utf8");
1353     assert(rs.code==200);
1354 
1355     info("Check chunked content");
1356     globalLogLevel(LogLevel.info);
1357     rq = Request();
1358     rq.keepAlive = true;
1359     rq.bufferSize = 16*1024;
1360     rs = rq.get("http://httpbin.org/range/1024");
1361     assert(rs.code==200);
1362     assert(rs.responseBody.length==1024);
1363 
1364     info("Check basic auth");
1365     globalLogLevel(LogLevel.info);
1366     rq = Request();
1367     rq.authenticator = new BasicAuthentication("user", "passwd");
1368     rs = rq.get("http://httpbin.org/basic-auth/user/passwd");
1369     assert(rs.code==200);
1370  
1371     globalLogLevel(LogLevel.info);
1372     info("Check exception handling, error messages are OK");
1373     rq = Request();
1374     rq.timeout = 1.seconds;
1375     assertThrown!TimeoutException(rq.get("http://httpbin.org/delay/3"));
1376     assertThrown!ConnectError(rq.get("http://0.0.0.0:65000/"));
1377     assertThrown!ConnectError(rq.get("http://1.1.1.1/"));
1378     assertThrown!ConnectError(rq.get("http://gkhgkhgkjhgjhgfjhgfjhgf/"));
1379 
1380     globalLogLevel(LogLevel.info);
1381     info("Check limits");
1382     rq = Request();
1383     rq.maxContentLength = 1;
1384     assertThrown!RequestException(rq.get("http://httpbin.org/"));
1385     rq = Request();
1386     rq.maxHeadersLength = 1;
1387     assertThrown!RequestException(rq.get("http://httpbin.org/"));
1388     tracef("http tests - ok");
1389 }