The OpenD Programming Language

1 module requests.http;
2 
3 private:
4 import std.algorithm;
5 import std.array;
6 import std.ascii;
7 import std.conv;
8 import std.datetime;
9 import std.exception;
10 import std.format;
11 import std.stdio;
12 import std.range;
13 import std.string;
14 import std.traits;
15 import std.typecons;
16 import std.experimental.logger;
17 import core.thread;
18 
19 import requests.streams;
20 import requests.uri;
21 import requests.utils;
22 import requests.base;
23 import requests.connmanager;
24 import requests.rangeadapter;
25 
26 static immutable ushort[] redirectCodes = [301, 302, 303, 307, 308];
27 
28 enum   HTTP11 = 101;
29 enum   HTTP10 = 100;
30 
31 static immutable string[string] proxies;
32 shared static this() {
33     import std.process;
34     import std.string;
35     foreach(p; ["http", "https", "all"])
36     {
37         auto v = environment.get(p ~ "_proxy", environment.get(p.toUpper() ~ "_PROXY"));
38         if ( v !is null && v.length > 0 )
39         {
40             debug(requests) tracef("will use %s for %s as proxy", v, p);
41             proxies[p] = v;
42         }
43     }
44 }
45 
46 public class MaxRedirectsException: Exception {
47     this(string message, string file = __FILE__, size_t line = __LINE__, Throwable next = null) @safe pure nothrow {
48         super(message, file, line, next);
49     }
50 }
51 
52 ///
53 ///
54 ///
55 //public auto queryParams(T...)(T params) pure nothrow @safe
56 //{
57 //    static assert (T.length % 2 == 0, "wrong args count");
58 //
59 //    QueryParam[] output;
60 //    output.reserve = T.length / 2;
61 //
62 //    void queryParamsHelper(T...)(T params, ref QueryParam[] output)
63 //    {
64 //        static if (T.length > 0)
65 //        {
66 //            output ~= QueryParam(params[0].to!string, params[1].to!string);
67 //            queryParamsHelper(params[2..$], output);
68 //        }
69 //    }
70 //
71 //    queryParamsHelper(params, output);
72 //    return output;
73 //}
74 
75 ///
76 /// Response - result of request execution.
77 ///
78 /// Response.code - response HTTP code.
79 /// Response.status_line - received HTTP status line.
80 /// Response.responseHeaders - received headers.
81 /// Response.responseBody - container for received body
82 /// Response.history - for redirected responses contain all history
83 ///
84 public class HTTPResponse : Response {
85     private {
86         string              _status_line;
87 
88         HTTPResponse[]      _history; // redirects history
89 
90         mixin(Setter!string("status_line"));
91 
92         int                 _version;
93     }
94 
95     ~this() {
96         _responseHeaders = null;
97         _history.length = 0;
98     }
99 
100     mixin(Getter("status_line"));
101 
102     @property final string[string] responseHeaders() @safe @nogc nothrow {
103         return _responseHeaders;
104     }
105     @property final HTTPResponse[] history() @safe @nogc nothrow {
106         return _history;
107     }
108 
109     private int parse_version(in string v) pure const nothrow @safe {
110         // try to parse HTTP/1.x to version
111         try if ( v.length > 5 ) {
112             return (v[5..$].split(".").map!"to!int(a)".array[0..2].reduce!((a,b) => a*100 + b));
113         } catch (Exception e) {
114         }
115         return 0;
116     }
117     unittest {
118         auto r = new HTTPResponse();
119         assert(r.parse_version("HTTP/1.1") == 101);
120         assert(r.parse_version("HTTP/1.0") == 100);
121         assert(r.parse_version("HTTP/0.9") == 9);
122         assert(r.parse_version("HTTP/xxx") == 0);
123     }
124 }
125 
126 ///
127 /// Request.
128 /// Configurable parameters:
129 /// $(B method) - string, method to use (GET, POST, ...)
130 /// $(B headers) - string[string], add any additional headers you'd like to send.
131 /// $(B authenticator) - class Auth, class to send auth headers.
132 /// $(B keepAlive) - bool, set true for keepAlive requests. default true.
133 /// $(B maxRedirects) - uint, maximum number of redirects. default 10.
134 /// $(B maxHeadersLength) - size_t, maximum length of server response headers. default = 32KB.
135 /// $(B maxContentLength) - size_t, maximun content length. delault - 0 = unlimited.
136 /// $(B bufferSize) - size_t, send and receive buffer size. default = 16KB.
137 /// $(B verbosity) - uint, level of verbosity(0 - nothing, 1 - headers, 2 - headers and body progress). default = 0.
138 /// $(B proxy) - string, set proxy url if needed. default - null.
139 /// $(B cookie) - Tuple Cookie, Read/Write cookie You can get cookie setted by server, or set cookies before doing request.
140 /// $(B timeout) - Duration, Set timeout value for connect/receive/send.
141 ///
142 public struct HTTPRequest {
143     private {
144         string         _method = "GET";
145         URI            _uri;
146         string[string] _headers;
147         string[]       _filteredHeaders;
148         Auth           _authenticator;
149         bool           _keepAlive = true;
150         uint           _maxRedirects = 10;
151         size_t         _maxHeadersLength = 32 * 1024;   // 32 KB
152         size_t         _maxContentLength;               // 0 - Unlimited
153         string         _proxy;
154         uint           _verbosity = 0;                  // 0 - no output, 1 - headers, 2 - headers+body info
155         Duration       _timeout = 30.seconds;
156         size_t         _bufferSize = 16*1024; // 16k
157         bool           _useStreaming;                   // return iterator instead of completed request
158 
159         HTTPResponse[] _history;                        // redirects history
160         DataPipe!ubyte _bodyDecoder;
161         DecodeChunked  _unChunker;
162         long           _contentLength;
163         long           _contentReceived;
164         SSLOptions     _sslOptions;
165         string         _bind;
166         _UH            _userHeaders;
167 
168         RefCounted!ConnManager      _cm;
169         RefCounted!Cookies          _cookie;
170         string[URI]                 _permanent_redirects;            // cache 301 redirects for GET requests
171         MultipartForm               _multipartForm;
172 
173         NetStreamFactory  _socketFactory;
174 
175         QueryParam[]        _params;
176         string              _contentType;
177         InputRangeAdapter   _postData;
178     }
179     package HTTPResponse   _response;
180 
181     mixin(Getter_Setter!string     ("method"));
182     mixin(Getter_Setter!bool       ("keepAlive"));
183     mixin(Getter_Setter!size_t     ("maxContentLength"));
184     mixin(Getter_Setter!size_t     ("maxHeadersLength"));
185     mixin(Getter_Setter!size_t     ("bufferSize"));
186     mixin(Getter_Setter!uint       ("maxRedirects"));
187     mixin(Getter_Setter!uint       ("verbosity"));
188     mixin(Getter                   ("proxy"));
189     mixin(Getter_Setter!Duration   ("timeout"));
190     mixin(Setter!Auth              ("authenticator"));
191     mixin(Getter_Setter!bool       ("useStreaming"));
192     mixin(Getter                   ("contentLength"));
193     mixin(Getter                   ("contentReceived"));
194     mixin(Getter_Setter!SSLOptions ("sslOptions"));
195     mixin(Getter_Setter!string     ("bind"));
196     mixin(Setter!NetStreamFactory  ("socketFactory"));
197 
198     @property void sslSetVerifyPeer(bool v) pure @safe nothrow @nogc {
199         _sslOptions.setVerifyPeer(v);
200     }
201     @property void sslSetKeyFile(string p, SSLOptions.filetype t = SSLOptions.filetype.pem) pure @safe nothrow @nogc {
202         _sslOptions.setKeyFile(p, t);
203     }
204     @property void sslSetCertFile(string p, SSLOptions.filetype t = SSLOptions.filetype.pem) pure @safe nothrow @nogc {
205         _sslOptions.setCertFile(p, t);
206     }
207     @property void sslSetCaCert(string path) pure @safe nothrow @nogc {
208         _sslOptions.setCaCert(path);
209     }
210     //@property final void cookie(Cookie[] s) pure @safe @nogc nothrow {
211     //    _cookie = s;
212     //}
213     @property final void proxy(string v) {
214         if ( v != _proxy ) {
215             _cm.clear();
216         }
217         _proxy = v;
218     }
219     //@property final Cookie[] cookie() pure @safe @nogc nothrow {
220     //    return _cookie;
221     //}
222 
223     this(string uri) {
224         _uri = URI(uri);
225         _cm = ConnManager(10);
226     }
227     ~this() {
228         _headers = null;
229         _authenticator = null;
230         _history = null;
231         _bodyDecoder = null;
232         _unChunker = null;
233         //if ( _cm ) {
234         //    _cm.clear();
235         //}
236     }
237     string toString() const {
238         return "HTTPRequest(%s, %s)".format(_method, _uri.uri());
239     }
240     string format(string fmt) const {
241         import std.array;
242         import std.stdio;
243         auto a = appender!string();
244         auto f = FormatSpec!char(fmt);
245         while (f.writeUpToNextSpec(a)) {
246             switch(f.spec) {
247                 case 'h':
248                     // Remote hostname.
249                     a.put(_uri.host);
250                     break;
251                 case 'm':
252                     // method.
253                     a.put(_method);
254                     break;
255                 case 'p':
256                     // Remote port.
257                     a.put("%d".format(_uri.port));
258                     break;
259                 case 'P':
260                     // Path
261                     a.put(_uri.path);
262                     break;
263                 case 'q':
264                     // query parameters supplied with url.
265                     a.put(_uri.query);
266                     break;
267                 case 'U':
268                     a.put(_uri.uri());
269                     break;
270                 default:
271                     throw new FormatException("Unknown Request format spec " ~ f.spec);
272             }
273         }
274         return a.data();
275     }
276     string select_proxy(string scheme) {
277         if ( _proxy is null && proxies.length == 0 ) {
278             debug(requests) tracef("proxy=null");
279             return null;
280         }
281         if ( _proxy ) {
282             debug(requests) tracef("proxy=%s", _proxy);
283             return _proxy;
284         }
285         auto p = scheme in proxies;
286         if ( p !is null && *p != "") {
287             debug(requests) tracef("proxy=%s", *p);
288             return *p;
289         }
290         p = "all" in proxies;
291         if ( p !is null && *p != "") {
292             debug(requests) tracef("proxy=%s", *p);
293             return *p;
294         }
295         debug(requests) tracef("proxy=null");
296         return null;
297     }
298     void clearHeaders() {
299         _headers = null;
300     }
301     @property void uri(in URI newURI) {
302         //handleURLChange(_uri, newURI);
303         _uri = newURI;
304     }
305     /// Add headers to request
306     /// Params:
307     /// headers = headers to send.
308     void addHeaders(in string[string] headers) {
309         foreach(pair; headers.byKeyValue) {
310             string _h = pair.key;
311             switch(toLower(_h)) {
312             case "host":
313                 _userHeaders.Host = true;
314                 break;
315             case "user-agent":
316                 _userHeaders.UserAgent = true;
317                 break;
318             case "content-length":
319                 _userHeaders.ContentLength = true;
320                 break;
321             case "content-type":
322                 _userHeaders.ContentType = true;
323                 break;
324             case "connection":
325                 _userHeaders.Connection = true;
326                 break;
327             case "cookie":
328                 _userHeaders.Cookie = true;
329                 break;
330             default:
331                 break;
332             }
333             _headers[pair.key] = pair.value;
334         }
335     }
336     private void safeSetHeader(ref string[string] headers, bool userAdded, string h, string v) pure @safe {
337         if ( !userAdded ) {
338             headers[h] = v;
339         }
340     }
341     /// Remove headers from request
342     /// Params:
343     /// headers = headers to remove.
344     void removeHeaders(in string[] headers) pure {
345         _filteredHeaders ~= headers;
346     }
347     ///
348     /// compose headers to send
349     ///
350     private string[string] requestHeaders() {
351 
352         string[string] generatedHeaders;
353 
354         if ( _authenticator ) {
355             _authenticator.
356                 authHeaders(_uri.host).
357                 byKeyValue.
358                 each!(pair => generatedHeaders[pair.key] = pair.value);
359         }
360 
361         _headers.byKey.each!(h => generatedHeaders[h] = _headers[h]);
362 
363         safeSetHeader(generatedHeaders, _userHeaders.AcceptEncoding, "Accept-Encoding", "gzip,deflate");
364         safeSetHeader(generatedHeaders, _userHeaders.UserAgent, "User-Agent", "dlang-requests");
365         safeSetHeader(generatedHeaders, _userHeaders.Connection, "Connection", _keepAlive?"Keep-Alive":"Close");
366 
367         if ( !_userHeaders.Host )
368         {
369             generatedHeaders["Host"] = _uri.host;
370             if ( _uri.scheme !in standard_ports || _uri.port != standard_ports[_uri.scheme] ) {
371                 generatedHeaders["Host"] ~= ":%d".format(_uri.port);
372             }
373         }
374 
375         if ( _cookie._array.length && !_userHeaders.Cookie ) {
376             auto cs = _cookie._array.
377                 filter!(c => _uri.path.pathMatches(c.path) && _uri.host.domainMatches(c.domain)).
378                 map!(c => "%s=%s".format(c.attr, c.value)).
379                 joiner(";");
380             if ( ! cs.empty )
381             {
382                 generatedHeaders["Cookie"] = to!string(cs);
383             }
384         }
385 
386         _filteredHeaders.each!(h => generatedHeaders.remove(h));
387 
388         return generatedHeaders;
389     }
390     ///
391     /// Build request string.
392     /// Handle proxy and query parameters.
393     ///
394     private @property string requestString(QueryParam[] params = null) {
395         auto query = _uri.query.dup;
396         if ( params ) {
397             query ~= params2query(params);
398             if ( query[0] != '?' ) {
399                 query = "?" ~ query;
400             }
401         }
402         string actual_proxy = select_proxy(_uri.scheme);
403         if ( actual_proxy && _uri.scheme != "https" ) {
404             return "%s %s%s HTTP/1.1\r\n".format(_method, _uri.uri(No.params), query);
405         }
406         return "%s %s%s HTTP/1.1\r\n".format(_method, _uri.path, query);
407     }
408 
409     unittest {
410         HTTPRequest request;
411         request._uri = URI("https://www.blub.de/");
412         import std.stdio;
413         auto expected = "GET /?a=b&c=d HTTP/1.1\r\n";
414         auto actual = request.requestString([QueryParam("a", "b"), QueryParam("c", "d")]);
415         assert(expected == actual, "Expected\n  '%s' but got\n  '%s'".format(expected, actual));
416     }
417 
418     ///
419     /// encode parameters and build query part of the url
420     ///
421     private static string params2query(in QueryParam[] params) pure @safe {
422         return params.
423                 map!(a => "%s=%s".format(a.key.urlEncoded, a.value.urlEncoded)).
424                 join("&");
425     }
426     //
427     package unittest {
428         assert(params2query(queryParams("a","b", "c", " d "))=="a=b&c=%20d%20");
429     }
430     ///
431     /// Analyze received headers, take appropriate actions:
432     /// check content length, attach unchunk and uncompress
433     ///
434     private void analyzeHeaders(in string[string] headers) {
435 
436         _contentLength = -1;
437         _unChunker = null;
438         auto contentLength = "content-length" in headers;
439         if ( contentLength ) {
440             try {
441                 string l = *contentLength;
442                 _contentLength = parse!long(l);
443                 // TODO: maybe add a strict mode that checks if l was parsed completely
444                 if ( _maxContentLength && _contentLength > _maxContentLength) {
445                     throw new RequestException("ContentLength > maxContentLength (%d>%d)".
446                                 format(_contentLength, _maxContentLength));
447                 }
448             } catch (ConvException e) {
449                 throw new RequestException("Can't convert Content-Length from %s".format(*contentLength));
450             }
451         }
452         auto transferEncoding = "transfer-encoding" in headers;
453         if ( transferEncoding ) {
454             debug(requests) tracef("transferEncoding: %s", *transferEncoding);
455             if ( (*transferEncoding).toLower == "chunked") {
456                 _unChunker = new DecodeChunked();
457                 _bodyDecoder.insert(_unChunker);
458             }
459         }
460         auto contentEncoding = "content-encoding" in headers;
461         if ( contentEncoding ) switch (*contentEncoding) {
462             default:
463                 throw new RequestException("Unknown content-encoding " ~ *contentEncoding);
464             case "gzip":
465             case "deflate":
466                 _bodyDecoder.insert(new Decompressor!ubyte);
467         }
468 
469     }
470     ///
471     /// Called when we know that all headers already received in buffer.
472     /// This routine does not interpret headers content (see analyzeHeaders).
473     /// 1. Split headers on lines
474     /// 2. store status line, store response code
475     /// 3. unfold headers if needed
476     /// 4. store headers
477     ///
478     private void parseResponseHeaders(in ubyte[] input, string lineSep) {
479         string lastHeader;
480         auto buffer = cast(string)input;
481 
482         foreach(line; buffer.split(lineSep).map!(l => l.stripRight)) {
483             if ( ! _response.status_line.length ) {
484                 debug (requests) tracef("statusLine: %s", line);
485                 _response.status_line = line;
486                 if ( _verbosity >= 1 ) {
487                     writefln("< %s", line);
488                 }
489                 auto parsed = line.split(" ");
490                 if ( parsed.length >= 2 ) {
491                     _response.code = parsed[1].to!ushort;
492                     _response._version = _response.parse_version(parsed[0]);
493                 }
494                 continue;
495             }
496             if ( line[0] == ' ' || line[0] == '\t' ) {
497                 // unfolding https://tools.ietf.org/html/rfc822#section-3.1
498                 if ( auto stored = lastHeader in _response._responseHeaders) {
499                     *stored ~= line;
500                 }
501                 continue;
502             }
503             auto parsed = line.findSplit(":");
504             auto header = parsed[0].toLower;
505             auto value = parsed[2].strip;
506 
507             if ( _verbosity >= 1 ) {
508                 writefln("< %s: %s", header, value);
509             }
510 
511             lastHeader = header;
512             debug (requests) tracef("Header %s = %s", header, value);
513 
514             if ( header != "set-cookie" ) {
515                 auto stored = _response.responseHeaders.get(header, null);
516                 if ( stored ) {
517                     value = stored ~ "," ~ value;
518                 }
519                 _response._responseHeaders[header] = value;
520                 continue;
521             }
522             _cookie._array ~= processCookie(value);
523         }
524     }
525 
526     ///
527     /// Process Set-Cookie header from server response
528     ///
529     private Cookie[] processCookie(string value ) pure {
530         // cookie processing
531         //
532         // as we can't join several set-cookie lines in single line
533         // < Set-Cookie: idx=7f2800f63c112a65ef5082957bcca24b; expires=Mon, 29-May-2017 00:31:25 GMT; path=/; domain=example.com
534         // < Set-Cookie: idx=7f2800f63c112a65ef5082957bcca24b; expires=Mon, 29-May-2017 00:31:25 GMT; path=/; domain=example.com, cs=ip764-RgKqc-HvSkxRxdQQAKW8LA; path=/; domain=.example.com; HttpOnly
535         //
536         Cookie[] res;
537         string[string] kv;
538         auto fields = value.split(";").map!strip;
539         while(!fields.empty) {
540             auto s = fields.front.findSplit("=");
541             fields.popFront;
542             if ( s[1] != "=" ) {
543                 continue;
544             }
545             auto k = s[0];
546             auto v = s[2];
547             switch(k.toLower()) {
548                 case "domain":
549                     k = "domain";
550                     break;
551                 case "path":
552                     k = "path";
553                     break;
554                 case "expires":
555                     continue;
556                 case "max-age":
557                     continue;
558                 default:
559                     break;
560             }
561             kv[k] = v;
562         }
563         if ( "domain" !in kv ) {
564             kv["domain"] = _uri.host;
565         }
566         if ( "path" !in kv ) {
567             kv["path"] = _uri.path;
568         }
569         auto domain = kv["domain"]; kv.remove("domain");
570         auto path   = kv["path"];   kv.remove("path");
571         foreach(pair; kv.byKeyValue) {
572             auto _attr = pair.key;
573             auto _value = pair.value;
574             auto cookie = Cookie(path, domain, _attr, _value);
575             res ~= cookie;
576         }
577         return res;
578     }
579 
580     private bool willFollowRedirect() {
581         if ( !canFind(redirectCodes, _response.code) ) {
582             return false;
583         }
584         if ( !_maxRedirects ) {
585             return false;
586         }
587         if ( "location" !in _response.responseHeaders ) {
588             return false;
589         }
590         return true;
591     }
592     private URI uriFromLocation(const ref URI uri, in string location) {
593         URI newURI = uri;
594         try {
595             newURI = URI(location);
596         } catch (UriException e) {
597             debug(requests) trace("Can't parse Location:, try relative uri");
598             newURI.path = location;
599             newURI.uri = newURI.recalc_uri;
600         }
601         return newURI;
602     }
603     ///
604     /// if we have new uri, then we need to check if we have to reopen existent connection
605     ///
606     private void checkURL(string url, string file=__FILE__, size_t line=__LINE__) {
607         if (url is null && _uri.uri == "" ) {
608             throw new RequestException("No url configured", file, line);
609         }
610 
611         if ( url !is null ) {
612             URI newURI = URI(url);
613             //handleURLChange(_uri, newURI);
614             _uri = newURI;
615         }
616     }
617     ///
618     /// Setup connection. Handle proxy and https case
619     ///
620     /// Place new connection in ConnManager cache
621     ///
622     private NetworkStream setupConnection()
623     do {
624 
625         debug(requests) tracef("Set up new connection");
626         NetworkStream stream;
627 
628         // on exit
629         // place created connection to conn. manager
630         // close connection purged from manager (if any)
631         //
632         scope(exit) {
633             if ( stream )
634             {
635                 if ( auto purged_connection = _cm.put(_uri.scheme, _uri.host, _uri.port, stream) )
636                 {
637                     debug(requests) tracef("closing purged connection %s", purged_connection);
638                     purged_connection.close();
639                 }
640             }
641         }
642 
643         if ( _socketFactory )
644         {
645             debug(requests) tracef("use socketFactory");
646             stream = _socketFactory(_uri.scheme, _uri.host, _uri.port);
647         }
648 
649         if ( stream ) // socket factory created connection
650         {
651             return stream;
652         }
653 
654         URI   uri; // this URI will be used temporarry if we need proxy
655         string actual_proxy = select_proxy(_uri.scheme);
656         final switch (_uri.scheme) {
657             case"http":
658                 if ( actual_proxy ) {
659                     uri.uri_parse(actual_proxy);
660                     uri.idn_encode();
661                 } else {
662                     // use original uri
663                     uri = _uri;
664                 }
665                 stream = new TCPStream();
666                 stream.bind(_bind);
667                 stream.connect(uri.host, uri.port, _timeout);
668                 break ;
669             case"https":
670                 if ( actual_proxy ) {
671                     uri.uri_parse(actual_proxy);
672                     uri.idn_encode();
673                     stream = new TCPStream();
674                     stream.bind(_bind);
675                     stream.connect(uri.host, uri.port, _timeout);
676                     if ( verbosity>=1 ) {
677                         writeln("> CONNECT %s:%d HTTP/1.1".format(_uri.host, _uri.port));
678                     }
679                     stream.send("CONNECT %s:%d HTTP/1.1\r\n".format(_uri.host, _uri.port));
680                     if (uri.username)
681                     {
682                         debug(requests) tracef("Add Proxy-Authorization header");
683                         auto auth = new BasicAuthentication(uri.username, uri.password);
684                         auto header = auth.authHeaders("");
685                         stream.send("Proxy-Authorization: %s\r\n".format(header["Authorization"]));
686                     }
687                     stream.send("\r\n");
688                     while ( stream.isConnected ) {
689                         ubyte[1024] b;
690                         auto read = stream.receive(b);
691                         if ( verbosity>=1) {
692                             writefln("< %s", cast(string)b[0..read]);
693                         }
694                         debug(requests) tracef("read: %d", read);
695                         if ( b[0..read].canFind("\r\n\r\n") || b[0..read].canFind("\n\n") ) {
696                             debug(requests) tracef("proxy connection ready");
697                             // convert connection to ssl
698                             stream = new SSLStream(stream, _sslOptions, _uri.host);
699                             break ;
700                         } else {
701                             debug(requests) tracef("still wait for proxy connection");
702                         }
703                     }
704                 } else {
705                     uri = _uri;
706                     stream = new SSLStream(_sslOptions);
707                     stream.bind(_bind);
708                     stream.connect(uri.host, uri.port, _timeout);
709                     debug(requests) tracef("ssl connection to origin server ready");
710                 }
711                 break ;
712         }
713 
714         return stream;
715     }
716     ///
717     /// Request sent, now receive response.
718     /// Find headers, split on headers and body, continue to receive body
719     ///
720     private void receiveResponse(NetworkStream _stream) {
721 
722         try {
723             _stream.readTimeout = timeout;
724         } catch (Exception e) {
725             debug(requests) tracef("Failed to set read timeout for stream: %s", e.msg);
726             return;
727         }
728         // Commented this out as at exit we can have alreade closed socket
729         // scope(exit) {
730         //     if ( _stream && _stream.isOpen ) {
731         //         _stream.readTimeout = 0.seconds;
732         //     }
733         // }
734 
735         _bodyDecoder = new DataPipe!ubyte();
736         scope(exit) {
737             if ( !_useStreaming ) {
738                 _bodyDecoder = null;
739                 _unChunker = null;
740             }
741         }
742 
743         auto buffer = Buffer!ubyte();
744         Buffer!ubyte partialBody;
745         ptrdiff_t read;
746         string lineSep = null, headersEnd = null;
747         bool headersHaveBeenReceived;
748 
749         while( !headersHaveBeenReceived ) {
750 
751             auto b = new ubyte[_bufferSize];
752             read = _stream.receive(b);
753 
754             debug(requests) tracef("read: %d", read);
755             if ( read == 0 ) {
756                 break;
757             }
758             auto data = b[0..read];
759             buffer.putNoCopy(data);
760             if ( verbosity>=3 ) {
761                 writeln(data.dump.join("\n"));
762             }
763 
764             if ( buffer.length > maxHeadersLength ) {
765                 throw new RequestException("Headers length > maxHeadersLength (%d > %d)".format(buffer.length, maxHeadersLength));
766             }
767 
768             // Proper HTTP uses "\r\n" as a line separator, but broken servers sometimes use "\n".
769             // Servers that use "\r\n" might have "\n" inside a header.
770             // For any half-sane server, the first '\n' should be at the end of the status line, so this can be used to detect the line separator.
771             // In any case, all the interesting points in the header for now are at '\n' characters, so scan the newly read data for them.
772             foreach (idx; buffer.length-read..buffer.length)
773             {
774                 if ( buffer[idx] == '\n' )
775                 {
776                     if ( lineSep is null )
777                     {
778                         // First '\n'. Detect line/header endings.
779                         // HTTP header sections end with a double line separator
780                         lineSep = "\n";
781                         headersEnd = "\n\n";
782                         if ( idx > 0 && buffer[idx-1] == '\r' )
783                         {
784                             lineSep = "\r\n";
785                             headersEnd = "\r\n\r\n";
786                         }
787                     }
788                     else
789                     {
790                         // Potential header ending.
791                         if ( buffer.data[0..idx+1].endsWith(headersEnd) )
792                         {
793                             auto ResponseHeaders = buffer.data[0..idx+1-headersEnd.length];
794                             partialBody = buffer[idx+1..$];
795                             _contentReceived += partialBody.length;
796                             parseResponseHeaders(ResponseHeaders, lineSep);
797                             headersHaveBeenReceived = true;
798                             break;
799                         }
800                     }
801                 }
802             }
803         }
804 
805         analyzeHeaders(_response._responseHeaders);
806 
807         _bodyDecoder.putNoCopy(partialBody.data);
808 
809         auto v = _bodyDecoder.get();
810         _response._responseBody.putNoCopy(v);
811 
812         // https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
813         if  (
814                 // document must not have body:
815                 (_method == "HEAD") || responseMustNotIncludeBody(_response.code) || 
816                 // we can receive doc without content length and without chunking only in streaming mode
817                 // see https://github.com/ikod/dlang-requests/issues/146
818                 ((_contentLength < 0 && _unChunker is null) && !_useStreaming)
819             )
820         {
821             debug(requests) tracef("response without body");
822             return;
823         }
824 
825         _response._contentLength = _contentLength;
826         _response._contentReceived = _contentReceived;
827 
828         if ( _verbosity >= 2 ) writefln("< %d bytes of body received", partialBody.length);
829 
830         while( true ) {
831             if ( _contentLength >= 0 && _contentReceived >= _contentLength ) {
832                 debug(requests) trace("Body received.");
833                 break;
834             }
835             if ( _unChunker && _unChunker.done ) {
836                 break;
837             }
838 
839             if ( _useStreaming && _response._responseBody.length && !redirectCodes.canFind(_response.code) ) {
840                 debug(requests) trace("streaming requested");
841                 // save _stream in closure
842                 auto __stream = _stream;
843                 auto __bodyDecoder = _bodyDecoder;
844                 auto __unChunker = _unChunker;
845                 auto __contentReceived = _contentReceived;
846                 auto __contentLength = _contentLength;
847                 auto __bufferSize = _bufferSize;
848                 auto __response = _response;
849                 auto __verbosity = _verbosity;
850 
851                 // set up response
852                 _response._contentLength = _contentLength;
853                 _response.receiveAsRange.activated = true;
854                 _response.receiveAsRange.data = _response._responseBody.data;
855                 _response.receiveAsRange.cm = _cm;
856                 _response.receiveAsRange.read = delegate ubyte[] () {
857 
858                     while(true) {
859                         // check if we received everything we need
860                         if ( ( __unChunker && __unChunker.done )
861                             || !__stream.isConnected()
862                             || (__contentLength > 0 && __contentReceived >= __contentLength) )
863                         {
864                             debug(requests) trace("streaming_in receive completed");
865                             __bodyDecoder.flush();
866                             return __bodyDecoder.get();
867                         }
868                         // have to continue
869                         auto b = new ubyte[__bufferSize];
870                         try {
871                             read = __stream.receive(b);
872                         }
873                         catch (Exception e) {
874                             throw new RequestException("streaming_in error reading from socket", __FILE__, __LINE__, e);
875                         }
876                         debug(requests) tracef("streaming_in received %d bytes", read);
877 
878                         if ( read == 0 ) {
879                             debug(requests) tracef("streaming_in: server closed connection");
880                             __bodyDecoder.flush();
881                             return __bodyDecoder.get();
882                         }
883 
884                         if ( __verbosity>=3 ) {
885                             writeln(b[0..read].dump.join("\n"));
886                         }
887                         __response._contentReceived += read;
888                         __contentReceived += read;
889                         __bodyDecoder.putNoCopy(b[0..read]);
890                         auto res = __bodyDecoder.getNoCopy();
891                         if ( res.length == 0 ) {
892                             // there were nothing to produce (beginning of the chunk or no decompressed data)
893                             continue;
894                         }
895                         if (res.length == 1) {
896                             return res[0];
897                         }
898                         //
899                         // I'd like to "return _bodyDecoder.getNoCopy().join;" but it is slower
900                         //
901                         auto total = res.map!(b=>b.length).sum;
902                         // create buffer for joined bytes
903                         ubyte[] joined = new ubyte[total];
904                         size_t p;
905                         // memcopy
906                         foreach(ref _; res) {
907                             joined[p .. p + _.length] = _;
908                             p += _.length;
909                         }
910                         return joined;
911                     }
912                     assert(0);
913                 };
914                 // we prepared for streaming
915                 return;
916             }
917 
918             auto b = new ubyte[_bufferSize];
919             read = _stream.receive(b);
920 
921             if ( read == 0 ) {
922                 debug(requests) trace("read done");
923                 break;
924             }
925             if ( _verbosity >= 2 ) {
926                 writefln("< %d bytes of body received", read);
927             }
928 
929             if ( verbosity>=3 ) {
930                 writeln(b[0..read].dump.join("\n"));
931             }
932 
933             debug(requests) tracef("read: %d", read);
934             _contentReceived += read;
935             if ( _maxContentLength && _contentReceived > _maxContentLength ) {
936                 throw new RequestException("ContentLength > maxContentLength (%d>%d)".
937                     format(_contentLength, _maxContentLength));
938             }
939 
940             _bodyDecoder.putNoCopy(b[0..read]); // send buffer to all decoders
941 
942             _bodyDecoder.getNoCopy.             // fetch result and place to body
943                 each!(b => _response._responseBody.putNoCopy(b));
944 
945             debug(requests) tracef("receivedTotal: %d, contentLength: %d, bodyLength: %d", _contentReceived, _contentLength, _response._responseBody.length);
946 
947         }
948         _bodyDecoder.flush();
949         _response._responseBody.putNoCopy(_bodyDecoder.get());
950         _response._contentReceived = _contentReceived;
951     }
952     ///
953     /// Check that we received anything.
954     /// Server can close previous connection (keepalive or not)
955     ///
956     private bool serverPrematurelyClosedConnection() pure @safe {
957         immutable server_closed_connection = _response._responseHeaders.length == 0 && _response._status_line.length == 0;
958         // debug(requests) tracef("server closed connection = %s (headers.length=%s, status_line.length=%s)",
959         //     server_closed_connection, _response._responseHeaders.length,  _response._status_line.length);
960         return server_closed_connection;
961     }
962     private bool isIdempotent(in string method) pure @safe nothrow {
963         return ["GET", "HEAD"].canFind(method);
964     }
965     ///
966     /// If we do not want keepalive request,
967     /// or server signalled to close connection,
968     /// then close it
969     ///
970     void close_connection_if_not_keepalive(NetworkStream _stream) {
971         auto connection = "connection" in _response._responseHeaders;
972         if ( !_keepAlive ) {
973             _stream.close();
974         } else switch(_response._version) {
975             case HTTP11:
976                 // HTTP/1.1 defines the "close" connection option for the sender to signal that the connection
977                 // will be closed after completion of the response. For example,
978                 //        Connection: close
979                 // in either the request or the response header fields indicates that the connection
980                 // SHOULD NOT be considered `persistent' (section 8.1) after the current request/response is complete.
981                 // HTTP/1.1 applications that do not support persistent connections MUST include the "close" connection
982                 // option in every message.
983                 if ( connection && (*connection).toLower.split(",").canFind("close") ) {
984                     _stream.close();
985                 }
986                 break;
987             default:
988                 // for anything else close connection if there is no keep-alive in Connection
989                 if ( connection && !(*connection).toLower.split(",").canFind("keep-alive") ) {
990                     _stream.close();
991                 }
992                 break;
993         }
994     }
995     ///
996     /// Send multipart for request.
997     /// You would like to use this method for sending large portions of mixed data or uploading files to forms.
998     /// Content of the posted form consist of sources. Each source have at least name and value (can be string-like object or opened file, see more docs for MultipartForm struct)
999     /// Params:
1000     ///     url = url
1001     ///     sources = array of sources.
1002     deprecated("Use Request() instead of HTTPRequest(); will be removed 2019-07")
1003     HTTPResponse exec(string method="POST")(string url, MultipartForm sources) {
1004         import std.uuid;
1005         import std.file;
1006 
1007         checkURL(url);
1008         //if ( _cm is null ) {
1009         //    _cm = new ConnManager();
1010         //}
1011 
1012         NetworkStream _stream;
1013         _method = method;
1014         _response = new HTTPResponse;
1015         _response.uri = _uri;
1016         _response.finalURI = _uri;
1017         bool restartedRequest = false;
1018 
1019     connect:
1020         _contentReceived = 0;
1021         _response._startedAt = Clock.currTime;
1022 
1023         assert(_stream is null);
1024 
1025         _stream = _cm.get(_uri.scheme, _uri.host, _uri.port);
1026 
1027         if ( _stream is null ) {
1028             debug(requests) trace("create new connection");
1029             _stream = setupConnection();
1030         } else {
1031             debug(requests) trace("reuse old connection");
1032         }
1033 
1034         assert(_stream !is null);
1035 
1036         if ( !_stream.isConnected ) {
1037             debug(requests) trace("disconnected stream on enter");
1038             if ( !restartedRequest ) {
1039                 debug(requests) trace("disconnected stream on enter: retry");
1040                 assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1041 
1042                 _cm.del(_uri.scheme, _uri.host, _uri.port);
1043                 _stream.close();
1044                 _stream = null;
1045 
1046                 restartedRequest = true;
1047                 goto connect;
1048             }
1049             debug(requests) trace("disconnected stream on enter: return response");
1050             //_stream = null;
1051             return _response;
1052         }
1053         _response._connectedAt = Clock.currTime;
1054 
1055         Appender!string req;
1056         req.put(requestString());
1057 
1058         string   boundary = randomUUID().toString;
1059         string[] partHeaders;
1060         size_t   contentLength;
1061 
1062         foreach(ref part; sources._sources) {
1063             string h = "--" ~ boundary ~ "\r\n";
1064             string disposition = `form-data; name="%s"`.format(part.name);
1065             string optionals = part.
1066                 parameters.byKeyValue().
1067                 filter!(p => p.key!="Content-Type").
1068                 map!   (p => "%s=%s".format(p.key, p.value)).
1069                 join("; ");
1070 
1071             h ~= `Content-Disposition: ` ~ [disposition, optionals].join("; ") ~ "\r\n";
1072 
1073             auto contentType = "Content-Type" in part.parameters;
1074             if ( contentType ) {
1075                 h ~= "Content-Type: " ~ *contentType ~ "\r\n";
1076             }
1077 
1078             h ~= "\r\n";
1079             partHeaders ~= h;
1080             contentLength += h.length + part.input.getSize() + "\r\n".length;
1081         }
1082         contentLength += "--".length + boundary.length + "--\r\n".length;
1083 
1084         auto h = requestHeaders();
1085         safeSetHeader(h, _userHeaders.ContentType, "Content-Type", "multipart/form-data; boundary=" ~ boundary);
1086         safeSetHeader(h, _userHeaders.ContentLength, "Content-Length", to!string(contentLength));
1087 
1088         h.byKeyValue.
1089             map!(kv => kv.key ~ ": " ~ kv.value ~ "\r\n").
1090                 each!(h => req.put(h));
1091         req.put("\r\n");
1092 
1093         debug(requests) trace(req.data);
1094         if ( _verbosity >= 1 ) req.data.splitLines.each!(a => writeln("> " ~ a));
1095 
1096         try {
1097             _stream.send(req.data());
1098             foreach(ref source; sources._sources) {
1099                 debug(requests) tracef("sending part headers <%s>", partHeaders.front);
1100                 _stream.send(partHeaders.front);
1101                 partHeaders.popFront;
1102                 while (true) {
1103                     auto chunk = source.input.read();
1104                     if ( chunk.length <= 0 ) {
1105                         break;
1106                     }
1107                     _stream.send(chunk);
1108                 }
1109                 _stream.send("\r\n");
1110             }
1111             _stream.send("--" ~ boundary ~ "--\r\n");
1112             _response._requestSentAt = Clock.currTime;
1113             receiveResponse(_stream);
1114             _response._finishedAt = Clock.currTime;
1115         }
1116         catch (NetworkException e) {
1117             errorf("Error sending request: ", e.msg);
1118             _stream.close();
1119             return _response;
1120         }
1121 
1122         if ( serverPrematurelyClosedConnection()
1123         && !restartedRequest
1124         && isIdempotent(_method)
1125         ) {
1126             ///
1127             /// We didn't receive any data (keepalive connectioin closed?)
1128             /// and we can restart this request.
1129             /// Go ahead.
1130             ///
1131             debug(requests) tracef("Server closed keepalive connection");
1132 
1133             assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1134 
1135             _cm.del(_uri.scheme, _uri.host, _uri.port);
1136             _stream.close();
1137             _stream = null;
1138 
1139             restartedRequest = true;
1140             goto connect;
1141         }
1142 
1143         if ( _useStreaming ) {
1144             if ( _response._receiveAsRange.activated ) {
1145                 debug(requests) trace("streaming_in activated");
1146                 return _response;
1147             } else {
1148                 // this can happen if whole response body received together with headers
1149                 _response._receiveAsRange.data = _response.responseBody.data;
1150             }
1151         }
1152 
1153         close_connection_if_not_keepalive(_stream);
1154 
1155         if ( _verbosity >= 1 ) {
1156             writeln(">> Connect time: ", _response._connectedAt - _response._startedAt);
1157             writeln(">> Request send time: ", _response._requestSentAt - _response._connectedAt);
1158             writeln(">> Response recv time: ", _response._finishedAt - _response._requestSentAt);
1159         }
1160 
1161         if ( willFollowRedirect ) {
1162             if ( _history.length >= _maxRedirects ) {
1163                 _stream = null;
1164                 throw new MaxRedirectsException("%d redirects reached maxRedirects %d.".format(_history.length, _maxRedirects));
1165             }
1166             // "location" in response already checked in canFollowRedirect
1167             immutable new_location = *("location" in _response.responseHeaders);
1168             immutable current_uri = _uri, next_uri = uriFromLocation(_uri, new_location);
1169 
1170             // save current response for history
1171             _history ~= _response;
1172 
1173             // prepare new response (for redirected request)
1174             _response = new HTTPResponse;
1175             _response.uri = current_uri;
1176             _response.finalURI = next_uri;
1177             _stream = null;
1178 
1179             // set new uri
1180             this._uri = next_uri;
1181             debug(requests) tracef("Redirected to %s", next_uri);
1182             if ( _method != "GET" && _response.code != 307 && _response.code != 308 ) {
1183                 // 307 and 308 do not change method
1184                 return this.get();
1185             }
1186             if ( restartedRequest ) {
1187                 debug(requests) trace("Rare event: clearing 'restartedRequest' on redirect");
1188                 restartedRequest = false;
1189             }
1190             goto connect;
1191         }
1192 
1193         _response._history = _history;
1194         return _response;
1195     }
1196 
1197     // we use this if we send from ubyte[][] and user provided Content-Length
1198     private void sendFlattenContent(T)(NetworkStream _stream, T content) {
1199         while ( !content.empty ) {
1200             auto chunk = content.front;
1201             _stream.send(chunk);
1202             content.popFront;
1203         }
1204         debug(requests) tracef("sent");
1205     }
1206     // we use this if we send from ubyte[][] as chunked content
1207     private void sendChunkedContent(T)(NetworkStream _stream, T content) {
1208         while ( !content.empty ) {
1209             auto chunk = content.front;
1210             auto chunkHeader = "%x\r\n".format(chunk.length);
1211             debug(requests) tracef("sending %s%s", chunkHeader, chunk);
1212             _stream.send(chunkHeader);
1213             _stream.send(chunk);
1214             _stream.send("\r\n");
1215             content.popFront;
1216         }
1217         debug(requests) tracef("sent");
1218         _stream.send("0\r\n\r\n");
1219     }
1220     ///
1221     /// POST/PUT/... data from some string(with Content-Length), or from range of strings/bytes (use Transfer-Encoding: chunked).
1222     /// When rank 1 (flat array) used as content it must have length. In that case "content" will be sent directly to network, and Content-Length headers will be added.
1223     /// If you are goung to send some range and do not know length at the moment when you start to send request, then you can send chunks of chars or ubyte.
1224     /// Try not to send too short chunks as this will put additional load on client and server. Chunks of length 2048 or 4096 are ok.
1225     ///
1226     /// Parameters:
1227     ///    url = url
1228     ///    content = string or input range
1229     ///    contentType = content type
1230     ///  Returns:
1231     ///     Response
1232     ///  Examples:
1233     ///  ---------------------------------------------------------------------------------------------------------
1234     ///      rs = rq.exec!"POST"("http://httpbin.org/post", "привiт, свiт!", "application/octet-stream");
1235     ///
1236     ///      auto s = lineSplitter("one,\ntwo,\nthree.");
1237     ///      rs = rq.exec!"POST"("http://httpbin.org/post", s, "application/octet-stream");
1238     ///
1239     ///      auto s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1240     ///      rs = rq.exec!"POST"("http://httpbin.org/post", s.representation.chunks(10), "application/octet-stream");
1241     ///
1242     ///      auto f = File("tests/test.txt", "rb");
1243     ///      rs = rq.exec!"POST"("http://httpbin.org/post", f.byChunk(3), "application/octet-stream");
1244     ///  --------------------------------------------------------------------------------------------------------
1245     deprecated("Use Request() instead of HTTPRequest(); will be removed 2019-07")
1246     HTTPResponse exec(string method="POST", R)(string url, R content, string contentType="application/octet-stream")
1247         if ( (rank!R == 1)
1248             || (rank!R == 2 && isSomeChar!(Unqual!(typeof(content.front.front))))
1249             || (rank!R == 2 && (is(Unqual!(typeof(content.front.front)) == ubyte)))
1250         )
1251     do {
1252         debug(requests) tracef("started url=%s, this._uri=%s", url, _uri);
1253 
1254         checkURL(url);
1255         //if ( _cm is null ) {
1256         //    _cm = new ConnManager();
1257         //}
1258 
1259         NetworkStream _stream;
1260         _method = method;
1261         _response = new HTTPResponse;
1262         _history.length = 0;
1263         _response.uri = _uri;
1264         _response.finalURI = _uri;
1265         bool restartedRequest = false;
1266         bool send_flat;
1267 
1268     connect:
1269         _contentReceived = 0;
1270         _response._startedAt = Clock.currTime;
1271 
1272         assert(_stream is null);
1273 
1274         _stream = _cm.get(_uri.scheme, _uri.host, _uri.port);
1275 
1276         if ( _stream is null ) {
1277             debug(requests) trace("create new connection");
1278             _stream = setupConnection();
1279         } else {
1280             debug(requests) trace("reuse old connection");
1281         }
1282 
1283         assert(_stream !is null);
1284 
1285         if ( !_stream.isConnected ) {
1286             debug(requests) trace("disconnected stream on enter");
1287             if ( !restartedRequest ) {
1288                 debug(requests) trace("disconnected stream on enter: retry");
1289                 assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1290 
1291                 _cm.del(_uri.scheme, _uri.host, _uri.port);
1292                 _stream.close();
1293                 _stream = null;
1294 
1295                 restartedRequest = true;
1296                 goto connect;
1297             }
1298             debug(requests) trace("disconnected stream on enter: return response");
1299             //_stream = null;
1300             return _response;
1301         }
1302         _response._connectedAt = Clock.currTime;
1303 
1304         Appender!string req;
1305         req.put(requestString());
1306 
1307         auto h = requestHeaders;
1308         if ( contentType ) {
1309             safeSetHeader(h, _userHeaders.ContentType, "Content-Type", contentType);
1310         }
1311         static if ( rank!R == 1 ) {
1312             safeSetHeader(h, _userHeaders.ContentLength, "Content-Length", to!string(content.length));
1313         } else {
1314             if ( _userHeaders.ContentLength ) {
1315                 debug(requests) tracef("User provided content-length for chunked content");
1316                 send_flat = true;
1317             } else {
1318                 h["Transfer-Encoding"] = "chunked";
1319                 send_flat = false;
1320             }
1321         }
1322         h.byKeyValue.
1323             map!(kv => kv.key ~ ": " ~ kv.value ~ "\r\n").
1324             each!(h => req.put(h));
1325         req.put("\r\n");
1326 
1327         debug(requests) trace(req.data);
1328         if ( _verbosity >= 1 ) {
1329             req.data.splitLines.each!(a => writeln("> " ~ a));
1330         }
1331 
1332         try {
1333             // send headers
1334             _stream.send(req.data());
1335             // send body
1336             static if ( rank!R == 1) {
1337                 _stream.send(content);
1338             } else {
1339                 if ( send_flat ) {
1340                     sendFlattenContent(_stream, content);
1341                 } else {
1342                     sendChunkedContent(_stream, content);
1343                 }
1344             }
1345             _response._requestSentAt = Clock.currTime;
1346             debug(requests) trace("starting receive response");
1347             receiveResponse(_stream);
1348             debug(requests) trace("finished receive response");
1349             _response._finishedAt = Clock.currTime;
1350         } catch (NetworkException e) {
1351             _stream.close();
1352             throw new RequestException("Network error during data exchange");
1353         }
1354 
1355         if ( serverPrematurelyClosedConnection()
1356             && !restartedRequest
1357             && isIdempotent(_method))
1358         {
1359             ///
1360             /// We didn't receive any data (keepalive connectioin closed?)
1361             /// and we can restart this request.
1362             /// Go ahead.
1363             ///
1364             debug(requests) tracef("Server closed keepalive connection");
1365 
1366             assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1367 
1368             _cm.del(_uri.scheme, _uri.host, _uri.port);
1369             _stream.close();
1370             _stream = null;
1371 
1372             restartedRequest = true;
1373             goto connect;
1374         }
1375 
1376         if ( _useStreaming ) {
1377             if ( _response._receiveAsRange.activated ) {
1378                 debug(requests) trace("streaming_in activated");
1379                 return _response;
1380             } else {
1381                 // this can happen if whole response body received together with headers
1382                 _response._receiveAsRange.data = _response.responseBody.data;
1383             }
1384         }
1385 
1386         close_connection_if_not_keepalive(_stream);
1387 
1388         if ( _verbosity >= 1 ) {
1389             writeln(">> Connect time: ", _response._connectedAt - _response._startedAt);
1390             writeln(">> Request send time: ", _response._requestSentAt - _response._connectedAt);
1391             writeln(">> Response recv time: ", _response._finishedAt - _response._requestSentAt);
1392         }
1393 
1394 
1395         if ( willFollowRedirect ) {
1396             if ( _history.length >= _maxRedirects ) {
1397                 _stream = null;
1398                 throw new MaxRedirectsException("%d redirects reached maxRedirects %d.".format(_history.length, _maxRedirects));
1399             }
1400             // "location" in response already checked in canFollowRedirect
1401             immutable new_location = *("location" in _response.responseHeaders);
1402             immutable current_uri = _uri, next_uri = uriFromLocation(_uri, new_location);
1403 
1404             // save current response for history
1405             _history ~= _response;
1406 
1407             // prepare new response (for redirected request)
1408             _response = new HTTPResponse;
1409             _response.uri = current_uri;
1410             _response.finalURI = next_uri;
1411 
1412             _stream = null;
1413 
1414             // set new uri
1415             this._uri = next_uri;
1416             debug(requests) tracef("Redirected to %s", next_uri);
1417             if ( _method != "GET" && _response.code != 307 && _response.code != 308 ) {
1418                 // 307 and 308 do not change method
1419                 return this.get();
1420             }
1421             if ( restartedRequest ) {
1422                 debug(requests) trace("Rare event: clearing 'restartedRequest' on redirect");
1423                 restartedRequest = false;
1424             }
1425             goto connect;
1426         }
1427 
1428         _response._history = _history;
1429         return _response;
1430     }
1431     ///
1432     /// Send request with parameters.
1433     /// If used for POST or PUT requests then application/x-www-form-urlencoded used.
1434     /// Request parameters will be encoded into request string or placed in request body for POST/PUT
1435     /// requests.
1436     /// Parameters:
1437     ///     url = url
1438     ///     params = request parameters
1439     ///  Returns:
1440     ///     Response
1441     ///  Examples:
1442     ///  ---------------------------------------------------------------------------------
1443     ///     rs = Request().exec!"GET"("http://httpbin.org/get", ["c":"d", "a":"b"]);
1444     ///  ---------------------------------------------------------------------------------
1445     ///
1446     deprecated("Use Request() instead of HTTPRequest; will be removed 2019-07")
1447     HTTPResponse exec(string method="GET")(string url = null, QueryParam[] params = null)
1448     do {
1449         debug(requests) tracef("started url=%s, this._uri=%s", url, _uri);
1450 
1451         checkURL(url);
1452         //if ( _cm is null ) {
1453         //    _cm = new ConnManager();
1454         //}
1455 
1456         NetworkStream _stream;
1457         _method = method;
1458         _response = new HTTPResponse;
1459         _history.length = 0;
1460         _response.uri = _uri;
1461         _response.finalURI = _uri;
1462         bool restartedRequest = false; // True if this is restarted keepAlive request
1463 
1464     connect:
1465         if ( _method == "GET" && _uri in _permanent_redirects ) {
1466             debug(requests) trace("use parmanent redirects cache");
1467             _uri = uriFromLocation(_uri, _permanent_redirects[_uri]);
1468             _response._finalURI = _uri;
1469         }
1470         _contentReceived = 0;
1471         _response._startedAt = Clock.currTime;
1472 
1473         assert(_stream is null);
1474 
1475         _stream = _cm.get(_uri.scheme, _uri.host, _uri.port);
1476 
1477         if ( _stream is null ) {
1478             debug(requests) trace("create new connection");
1479             _stream = setupConnection();
1480         } else {
1481             debug(requests) trace("reuse old connection");
1482         }
1483 
1484         assert(_stream !is null);
1485 
1486         if ( !_stream.isConnected ) {
1487             debug(requests) trace("disconnected stream on enter");
1488             if ( !restartedRequest ) {
1489                 debug(requests) trace("disconnected stream on enter: retry");
1490                 assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1491 
1492                 _cm.del(_uri.scheme, _uri.host, _uri.port);
1493                 _stream.close();
1494                 _stream = null;
1495 
1496                 restartedRequest = true;
1497                 goto connect;
1498             }
1499             debug(requests) trace("disconnected stream on enter: return response");
1500             //_stream = null;
1501             return _response;
1502         }
1503         _response._connectedAt = Clock.currTime;
1504 
1505         auto h = requestHeaders();
1506 
1507         Appender!string req;
1508 
1509         string encoded;
1510 
1511         switch (_method) {
1512             case "POST","PUT","PATCH":
1513                 encoded = params2query(params);
1514                 safeSetHeader(h, _userHeaders.ContentType, "Content-Type", "application/x-www-form-urlencoded");
1515                 if ( encoded.length > 0) {
1516                     safeSetHeader(h, _userHeaders.ContentLength, "Content-Length", to!string(encoded.length));
1517                 }
1518                 req.put(requestString());
1519                 break;
1520             default:
1521                 req.put(requestString(params));
1522         }
1523 
1524         h.byKeyValue.
1525             map!(kv => kv.key ~ ": " ~ kv.value ~ "\r\n").
1526             each!(h => req.put(h));
1527         req.put("\r\n");
1528         if ( encoded ) {
1529             req.put(encoded);
1530         }
1531 
1532         debug(requests) trace(req.data);
1533         if ( _verbosity >= 1 ) {
1534             req.data.splitLines.each!(a => writeln("> " ~ a));
1535         }
1536         //
1537         // Now send request and receive response
1538         //
1539         try {
1540             _stream.send(req.data());
1541             _response._requestSentAt = Clock.currTime;
1542             debug(requests) trace("starting receive response");
1543             receiveResponse(_stream);
1544             debug(requests) trace("done receive response");
1545             _response._finishedAt = Clock.currTime;
1546         }
1547         catch (NetworkException e) {
1548             // On SEND this can means:
1549             // we started to send request to the server, but it closed connection because of keepalive timeout.
1550             // We have to restart request if possible.
1551 
1552             // On RECEIVE - if we received something - then this exception is real and unexpected error.
1553             // If we didn't receive anything - we can restart request again as it can be
1554             debug(requests) tracef("Exception on receive response: %s", e.msg);
1555             if ( _response._responseHeaders.length != 0 )
1556             {
1557                 _stream.close();
1558                 throw new RequestException("Unexpected network error");
1559             }
1560         }
1561 
1562         if ( serverPrematurelyClosedConnection()
1563             && !restartedRequest
1564             && isIdempotent(_method)
1565             ) {
1566             ///
1567             /// We didn't receive any data (keepalive connectioin closed?)
1568             /// and we can restart this request.
1569             /// Go ahead.
1570             ///
1571             debug(requests) tracef("Server closed keepalive connection");
1572 
1573             assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1574 
1575             _cm.del(_uri.scheme, _uri.host, _uri.port);
1576             _stream.close();
1577             _stream = null;
1578 
1579             restartedRequest = true;
1580             goto connect;
1581         }
1582 
1583         if ( _useStreaming ) {
1584             if ( _response._receiveAsRange.activated ) {
1585                 debug(requests) trace("streaming_in activated");
1586                 return _response;
1587             } else {
1588                 // this can happen if whole response body received together with headers
1589                 _response._receiveAsRange.data = _response.responseBody.data;
1590             }
1591         }
1592 
1593         close_connection_if_not_keepalive(_stream);
1594 
1595         if ( _verbosity >= 1 ) {
1596             writeln(">> Connect time: ", _response._connectedAt - _response._startedAt);
1597             writeln(">> Request send time: ", _response._requestSentAt - _response._connectedAt);
1598             writeln(">> Response recv time: ", _response._finishedAt - _response._requestSentAt);
1599         }
1600 
1601         if ( willFollowRedirect ) {
1602             debug(requests) trace("going to follow redirect");
1603             if ( _history.length >= _maxRedirects ) {
1604                 _stream = null;
1605                 throw new MaxRedirectsException("%d redirects reached maxRedirects %d.".format(_history.length, _maxRedirects));
1606             }
1607             // "location" in response already checked in canFollowRedirect
1608             immutable new_location = *("location" in _response.responseHeaders);
1609             immutable current_uri = _uri, next_uri = uriFromLocation(_uri, new_location);
1610 
1611             if ( _method == "GET" && _response.code == 301 ) {
1612                 _permanent_redirects[_uri] = new_location;
1613             }
1614 
1615             // save current response for history
1616             _history ~= _response;
1617 
1618             // prepare new response (for redirected request)
1619             _response = new HTTPResponse;
1620             _response.uri = current_uri;
1621             _response.finalURI = next_uri;
1622             _stream = null;
1623 
1624             // set new uri
1625             _uri = next_uri;
1626             debug(requests) tracef("Redirected to %s", next_uri);
1627             if ( _method != "GET" && _response.code != 307 && _response.code != 308 ) {
1628                 // 307 and 308 do not change method
1629                 return this.get();
1630             }
1631             if ( restartedRequest ) {
1632                 debug(requests) trace("Rare event: clearing 'restartedRequest' on redirect");
1633                 restartedRequest = false;
1634             }
1635             goto connect;
1636         }
1637 
1638         _response._history = _history;
1639         return _response;
1640     }
1641 
1642     /// WRAPPERS
1643     ///
1644     /// send file(s) using POST and multipart form.
1645     /// This wrapper will be deprecated, use post with MultipartForm - it is more general and clear.
1646     /// Parameters:
1647     ///     url = url
1648     ///     files = array of PostFile structures
1649     /// Returns:
1650     ///     Response
1651     /// Each PostFile structure contain path to file, and optional field name and content type.
1652     /// If no field name provided, then basename of the file will be used.
1653     /// application/octet-stream is default when no content type provided.
1654     /// Example:
1655     /// ---------------------------------------------------------------
1656     ///    PostFile[] files = [
1657     ///                   {fileName:"tests/abc.txt", fieldName:"abc", contentType:"application/octet-stream"},
1658     ///                   {fileName:"tests/test.txt"}
1659     ///               ];
1660     ///    rs = rq.exec!"POST"("http://httpbin.org/post", files);
1661     /// ---------------------------------------------------------------
1662     ///
1663     deprecated("Use Request() instead of HTTPRequest(); will be removed 2019-07")
1664     HTTPResponse exec(string method="POST")(string url, PostFile[] files) if (method=="POST") {
1665         MultipartForm multipart;
1666         File[]        toClose;
1667         foreach(ref f; files) {
1668             File file = File(f.fileName, "rb");
1669             toClose ~= file;
1670             string fileName = f.fileName ? f.fileName : f.fieldName;
1671             string contentType = f.contentType ? f.contentType : "application/octetstream";
1672             multipart.add(f.fieldName, new FormDataFile(file), ["filename":fileName, "Content-Type": contentType]);
1673         }
1674         auto res = exec!"POST"(url, multipart);
1675         toClose.each!"a.close";
1676         return res;
1677     }
1678     ///
1679     /// exec request with parameters when you can use dictionary (when you have no duplicates in parameter names)
1680     /// Consider switch to exec(url, QueryParams) as it more generic and clear.
1681     /// Parameters:
1682     ///     url = url
1683     ///     params = dictionary with field names as keys and field values as values.
1684     /// Returns:
1685     ///     Response
1686     deprecated("Use Request() instead of HTTPRequest(); will be removed 2019-07")
1687     HTTPResponse exec(string method="GET")(string url, string[string] params) {
1688         return exec!method(url, params.byKeyValue.map!(p => QueryParam(p.key, p.value)).array);
1689     }
1690     ///
1691     /// GET request. Simple wrapper over exec!"GET"
1692     /// Params:
1693     /// args = request parameters. see exec docs.
1694     ///
1695     deprecated("Use Request() instead of HTTPRequest; will be removed 2019-07")
1696     HTTPResponse get(A...)(A args) {
1697         return exec!"GET"(args);
1698     }
1699     ///
1700     /// POST request. Simple wrapper over exec!"POST"
1701     /// Params:
1702     /// uri = endpoint uri
1703     /// args = request parameters. see exec docs.
1704     ///
1705     deprecated("Use Request() instead of HTTPRequest; will be removed 2019-07")
1706     HTTPResponse post(A...)(string uri, A args) {
1707         return exec!"POST"(uri, args);
1708     }
1709 
1710     import requests.request;
1711 
1712     // we use this if we send from ubyte[][] and user provided Content-Length
1713     private void sendFlattenContent(NetworkStream _stream) {
1714         while ( !_postData.empty ) {
1715             auto chunk = _postData.front;
1716             _stream.send(chunk);
1717             _postData.popFront;
1718         }
1719         debug(requests) tracef("sent");
1720     }
1721     // we use this if we send from ubyte[][] as chunked content
1722     private void sendChunkedContent(NetworkStream _stream) {
1723         while ( !_postData.empty ) {
1724             auto chunk = _postData.front;
1725             auto chunkHeader = "%x\r\n".format(chunk.length);
1726             debug(requests) tracef("sending %s%s", chunkHeader, cast(string)chunk);
1727             _stream.send(chunkHeader);
1728             _stream.send(chunk);
1729             _stream.send("\r\n");
1730             debug(requests) tracef("chunk sent");
1731             _postData.popFront;
1732         }
1733         debug(requests) tracef("sent");
1734         _stream.send("0\r\n\r\n");
1735     }
1736 
1737     HTTPResponse exec_from_range(InputRangeAdapter postData)
1738     do {
1739 
1740         _postData = postData;
1741 
1742         debug(requests) tracef("exec from range");
1743 
1744         NetworkStream _stream;
1745         _response = new HTTPResponse;
1746         _history.length = 0;
1747         _response.uri = _uri;
1748         _response.finalURI = _uri;
1749         bool restartedRequest = false;
1750         bool send_flat;
1751 
1752     connect:
1753         _contentReceived = 0;
1754         _response._startedAt = Clock.currTime;
1755 
1756         assert(_stream is null);
1757 
1758         _stream = _cm.get(_uri.scheme, _uri.host, _uri.port);
1759 
1760         if ( _stream is null ) {
1761             debug(requests) trace("create new connection");
1762             _stream = setupConnection();
1763         } else {
1764             debug(requests) trace("reuse old connection");
1765         }
1766 
1767         assert(_stream !is null);
1768 
1769         if ( !_stream.isConnected ) {
1770             debug(requests) trace("disconnected stream on enter");
1771             if ( !restartedRequest ) {
1772                 debug(requests) trace("disconnected stream on enter: retry");
1773                 assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1774 
1775                 _cm.del(_uri.scheme, _uri.host, _uri.port);
1776                 _stream.close();
1777                 _stream = null;
1778 
1779                 restartedRequest = true;
1780                 goto connect;
1781             }
1782             debug(requests) trace("disconnected stream on enter: return response");
1783             //_stream = null;
1784             return _response;
1785         }
1786         _response._connectedAt = Clock.currTime;
1787 
1788         Appender!string req;
1789         req.put(requestString());
1790 
1791         auto h = requestHeaders;
1792         if ( _contentType ) {
1793             safeSetHeader(h, _userHeaders.ContentType, "Content-Type", _contentType);
1794         }
1795 
1796         if ( _postData.length >= 0 )
1797         {
1798             // we know t
1799             safeSetHeader(h, _userHeaders.ContentLength, "Content-Length", to!string(_postData.length));
1800         }
1801 
1802         if ( _userHeaders.ContentLength || "Content-Length" in h )
1803         {
1804             debug(requests) tracef("User provided content-length for chunked content");
1805             send_flat = true;
1806         }
1807         else
1808         {
1809             h["Transfer-Encoding"] = "chunked";
1810             send_flat = false;
1811         }
1812         h.byKeyValue.
1813             map!(kv => kv.key ~ ": " ~ kv.value ~ "\r\n").
1814             each!(h => req.put(h));
1815         req.put("\r\n");
1816 
1817         debug(requests) tracef("send <%s>", req.data);
1818         if ( _verbosity >= 1 ) {
1819             req.data.splitLines.each!(a => writeln("> " ~ a));
1820         }
1821 
1822         try {
1823             // send headers
1824             _stream.send(req.data());
1825             // send body
1826             if ( send_flat ) {
1827                 sendFlattenContent(_stream);
1828             } else {
1829                 sendChunkedContent(_stream);
1830             }
1831             _response._requestSentAt = Clock.currTime;
1832             debug(requests) trace("starting receive response");
1833             receiveResponse(_stream);
1834             debug(requests) trace("finished receive response");
1835             _response._finishedAt = Clock.currTime;
1836         }
1837         catch (NetworkException e)
1838         {
1839             _stream.close();
1840             throw new RequestException("Network error during data exchange");
1841         }
1842         if ( serverPrematurelyClosedConnection()
1843         && !restartedRequest
1844         && isIdempotent(_method)
1845         ) {
1846             ///
1847             /// We didn't receive any data (keepalive connectioin closed?)
1848             /// and we can restart this request.
1849             /// Go ahead.
1850             ///
1851             debug(requests) tracef("Server closed keepalive connection");
1852 
1853             assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1854 
1855             _cm.del(_uri.scheme, _uri.host, _uri.port);
1856             _stream.close();
1857             _stream = null;
1858 
1859             restartedRequest = true;
1860             goto connect;
1861         }
1862 
1863         if ( _useStreaming ) {
1864             if ( _response._receiveAsRange.activated ) {
1865                 debug(requests) trace("streaming_in activated");
1866                 return _response;
1867             } else {
1868                 // this can happen if whole response body received together with headers
1869                 _response._receiveAsRange.data = _response.responseBody.data;
1870             }
1871         }
1872 
1873         close_connection_if_not_keepalive(_stream);
1874 
1875         if ( _verbosity >= 1 ) {
1876             writeln(">> Connect time: ", _response._connectedAt - _response._startedAt);
1877             writeln(">> Request send time: ", _response._requestSentAt - _response._connectedAt);
1878             writeln(">> Response recv time: ", _response._finishedAt - _response._requestSentAt);
1879         }
1880 
1881 
1882         if ( willFollowRedirect ) {
1883             if ( _history.length >= _maxRedirects ) {
1884                 _stream = null;
1885                 throw new MaxRedirectsException("%d redirects reached maxRedirects %d.".format(_history.length, _maxRedirects));
1886             }
1887             // "location" in response already checked in canFollowRedirect
1888             immutable new_location = *("location" in _response.responseHeaders);
1889             immutable current_uri = _uri, next_uri = uriFromLocation(_uri, new_location);
1890 
1891             immutable get_or_head = _method == "GET" || _method == "HEAD";
1892             immutable code = _response.code;
1893 
1894             // save current response for history
1895             _history ~= _response;
1896 
1897             if ( code == 301 )
1898             {
1899                 // permanent redirect and change method
1900                 _permanent_redirects[_uri] = new_location;
1901                 if ( !get_or_head )
1902                 {
1903                     _method = "GET";
1904                 }
1905             }
1906             if ( (code == 302 || code == 303) && !get_or_head)
1907             {
1908                 // only change method
1909                 _method = "GET";
1910             }
1911             if ( code == 307 )
1912             {
1913                 // no change method, no permanent
1914             }
1915             if ( code == 308 )
1916             {
1917                 // permanent redirection and do not change method
1918                 _permanent_redirects[_uri] = new_location;
1919             }
1920 
1921             // prepare new response (for redirected request)
1922             _response = new HTTPResponse;
1923             _response.uri = current_uri;
1924             _response.finalURI = next_uri;
1925 
1926             _stream = null;
1927 
1928             // set new uri
1929             this._uri = next_uri;
1930             debug(requests) tracef("Redirected to %s", next_uri);
1931             if ( restartedRequest ) {
1932                 debug(requests) trace("Rare event: clearing 'restartedRequest' on redirect");
1933                 restartedRequest = false;
1934             }
1935             if ( _method == "GET")
1936             {
1937                 return exec_from_parameters();
1938             }
1939             goto connect;
1940         }
1941 
1942         _response._history = _history;
1943         return _response;
1944     }
1945 
1946     HTTPResponse exec_from_multipart_form(MultipartForm form) {
1947         import std.uuid;
1948         import std.file;
1949 
1950         _multipartForm = form;
1951 
1952         debug(requests) tracef("exec from multipart form");
1953 
1954         NetworkStream _stream;
1955         _response = new HTTPResponse;
1956         _response.uri = _uri;
1957         _response.finalURI = _uri;
1958         bool restartedRequest = false;
1959 
1960     connect:
1961         _contentReceived = 0;
1962         _response._startedAt = Clock.currTime;
1963 
1964         assert(_stream is null);
1965 
1966         _stream = _cm.get(_uri.scheme, _uri.host, _uri.port);
1967 
1968         if ( _stream is null ) {
1969             debug(requests) trace("create new connection");
1970             _stream = setupConnection();
1971         } else {
1972             debug(requests) trace("reuse old connection");
1973         }
1974 
1975         assert(_stream !is null);
1976 
1977         if ( !_stream.isConnected ) {
1978             debug(requests) trace("disconnected stream on enter");
1979             if ( !restartedRequest ) {
1980                 debug(requests) trace("disconnected stream on enter: retry");
1981                 assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1982 
1983                 _cm.del(_uri.scheme, _uri.host, _uri.port);
1984                 _stream.close();
1985                 _stream = null;
1986 
1987                 restartedRequest = true;
1988                 goto connect;
1989             }
1990             debug(requests) trace("disconnected stream on enter: return response");
1991             //_stream = null;
1992             return _response;
1993         }
1994         _response._connectedAt = Clock.currTime;
1995 
1996         Appender!string req;
1997         req.put(requestString());
1998 
1999         string   boundary = randomUUID().toString;
2000         string[] partHeaders;
2001         size_t   contentLength;
2002 
2003         foreach(ref part; _multipartForm._sources) {
2004             string h = "--" ~ boundary ~ "\r\n";
2005             string disposition = `form-data; name="%s"`.format(part.name);
2006             string optionals = part.
2007             parameters.byKeyValue().
2008             filter!(p => p.key!="Content-Type").
2009             map!   (p => "%s=%s".format(p.key, p.value)).
2010             join("; ");
2011 
2012             h ~= `Content-Disposition: ` ~ [disposition, optionals].join("; ") ~ "\r\n";
2013 
2014             auto contentType = "Content-Type" in part.parameters;
2015             if ( contentType ) {
2016                 h ~= "Content-Type: " ~ *contentType ~ "\r\n";
2017             }
2018 
2019             h ~= "\r\n";
2020             partHeaders ~= h;
2021             contentLength += h.length + part.input.getSize() + "\r\n".length;
2022         }
2023         contentLength += "--".length + boundary.length + "--\r\n".length;
2024 
2025         auto h = requestHeaders();
2026         safeSetHeader(h, _userHeaders.ContentType, "Content-Type", "multipart/form-data; boundary=" ~ boundary);
2027         safeSetHeader(h, _userHeaders.ContentLength, "Content-Length", to!string(contentLength));
2028 
2029         h.byKeyValue.
2030         map!(kv => kv.key ~ ": " ~ kv.value ~ "\r\n").
2031         each!(h => req.put(h));
2032         req.put("\r\n");
2033 
2034         debug(requests) trace(req.data);
2035         if ( _verbosity >= 1 ) req.data.splitLines.each!(a => writeln("> " ~ a));
2036 
2037         try {
2038             _stream.send(req.data());
2039             foreach(ref source; _multipartForm._sources) {
2040                 debug(requests) tracef("sending part headers <%s>", partHeaders.front);
2041                 _stream.send(partHeaders.front);
2042                 partHeaders.popFront;
2043                 while (true) {
2044                     auto chunk = source.input.read();
2045                     if ( chunk.length <= 0 ) {
2046                         break;
2047                     }
2048                     _stream.send(chunk);
2049                 }
2050                 _stream.send("\r\n");
2051             }
2052             _stream.send("--" ~ boundary ~ "--\r\n");
2053             _response._requestSentAt = Clock.currTime;
2054             receiveResponse(_stream);
2055             _response._finishedAt = Clock.currTime;
2056         }
2057         catch (NetworkException e) {
2058             errorf("Error sending request: ", e.msg);
2059             _stream.close();
2060             return _response;
2061         }
2062 
2063         if ( serverPrematurelyClosedConnection()
2064         && !restartedRequest
2065         && isIdempotent(_method)
2066         ) {
2067             ///
2068             /// We didn't receive any data (keepalive connectioin closed?)
2069             /// and we can restart this request.
2070             /// Go ahead.
2071             ///
2072             debug(requests) tracef("Server closed keepalive connection");
2073 
2074             assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
2075 
2076             _cm.del(_uri.scheme, _uri.host, _uri.port);
2077             _stream.close();
2078             _stream = null;
2079 
2080             restartedRequest = true;
2081             goto connect;
2082         }
2083 
2084         if ( _useStreaming ) {
2085             if ( _response._receiveAsRange.activated ) {
2086                 debug(requests) trace("streaming_in activated");
2087                 return _response;
2088             } else {
2089                 // this can happen if whole response body received together with headers
2090                 _response._receiveAsRange.data = _response.responseBody.data;
2091             }
2092         }
2093 
2094         close_connection_if_not_keepalive(_stream);
2095 
2096         if ( _verbosity >= 1 ) {
2097             writeln(">> Connect time: ", _response._connectedAt - _response._startedAt);
2098             writeln(">> Request send time: ", _response._requestSentAt - _response._connectedAt);
2099             writeln(">> Response recv time: ", _response._finishedAt - _response._requestSentAt);
2100         }
2101 
2102         if ( willFollowRedirect ) {
2103             if ( _history.length >= _maxRedirects ) {
2104                 _stream = null;
2105                 throw new MaxRedirectsException("%d redirects reached maxRedirects %d.".format(_history.length, _maxRedirects));
2106             }
2107             // "location" in response already checked in canFollowRedirect
2108             immutable new_location = *("location" in _response.responseHeaders);
2109             immutable current_uri = _uri;
2110             immutable next_uri = uriFromLocation(_uri, new_location);
2111 
2112             immutable get_or_head = _method == "GET" || _method == "HEAD";
2113             immutable code = _response.code;
2114 
2115             // save current response for history
2116             _history ~= _response;
2117 
2118             if ( code == 301 )
2119             {
2120                 // permanent redirect and change method
2121                 _permanent_redirects[_uri] = new_location;
2122                 if ( !get_or_head )
2123                 {
2124                     _method = "GET";
2125                 }
2126             }
2127             if ( (code == 302 || code == 303) && !get_or_head)
2128             {
2129                 // only change method
2130                 _method = "GET";
2131             }
2132             if ( code == 307 )
2133             {
2134                 // no change method, no permanent
2135             }
2136             if ( code == 308 )
2137             {
2138                 // permanent redirection and do not change method
2139                 _permanent_redirects[_uri] = new_location;
2140             }
2141 
2142             // prepare new response (for redirected request)
2143             _response = new HTTPResponse;
2144             _response.uri = current_uri;
2145             _response.finalURI = next_uri;
2146             _stream = null;
2147 
2148             // set new uri
2149             this._uri = next_uri;
2150             debug(requests) tracef("Redirected to %s", next_uri);
2151             if ( restartedRequest ) {
2152                 debug(requests) trace("Rare event: clearing 'restartedRequest' on redirect");
2153                 restartedRequest = false;
2154             }
2155             if ( _method == "GET")
2156             {
2157                 return exec_from_parameters();
2158             }
2159             goto connect;
2160         }
2161 
2162         _response._history = _history;
2163         return _response;
2164     }
2165 
2166     HTTPResponse exec_from_parameters() {
2167 
2168         debug(requests) tracef("exec from parameters request");
2169 
2170         assert(_uri != URI.init);
2171         NetworkStream _stream;
2172         _response = new HTTPResponse;
2173         _history.length = 0;
2174         _response.uri = _uri;
2175         _response.finalURI = _uri;
2176         bool restartedRequest = false; // True if this is restarted keepAlive request
2177 
2178     connect:
2179         if ( _method == "GET" && _uri in _permanent_redirects ) {
2180             debug(requests) trace("use parmanent redirects cache");
2181             _uri = uriFromLocation(_uri, _permanent_redirects[_uri]);
2182             _response._finalURI = _uri;
2183         }
2184         _contentReceived = 0;
2185         _response._startedAt = Clock.currTime;
2186 
2187         assert(_stream is null);
2188 
2189         _stream = _cm.get(_uri.scheme, _uri.host, _uri.port);
2190 
2191         if ( _stream is null ) {
2192             debug(requests) trace("create new connection");
2193             _stream = setupConnection();
2194         } else {
2195             debug(requests) trace("reuse old connection");
2196         }
2197 
2198         assert(_stream !is null);
2199 
2200         if ( !_stream.isConnected ) {
2201             debug(requests) trace("disconnected stream on enter");
2202             if ( !restartedRequest ) {
2203                 debug(requests) trace("disconnected stream on enter: retry");
2204                 assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
2205 
2206                 _cm.del(_uri.scheme, _uri.host, _uri.port);
2207                 _stream.close();
2208                 _stream = null;
2209 
2210                 restartedRequest = true;
2211                 goto connect;
2212             }
2213             debug(requests) trace("disconnected stream on enter: return response");
2214             //_stream = null;
2215             return _response;
2216         }
2217         _response._connectedAt = Clock.currTime;
2218 
2219         auto h = requestHeaders();
2220 
2221         Appender!string req;
2222 
2223         string encoded;
2224 
2225         switch (_method) {
2226             case "POST","PUT","PATCH":
2227                 encoded = params2query(_params);
2228                 safeSetHeader(h, _userHeaders.ContentType, "Content-Type", "application/x-www-form-urlencoded");
2229                 safeSetHeader(h, _userHeaders.ContentLength, "Content-Length", to!string(encoded.length));
2230                 req.put(requestString());
2231                 break;
2232             default:
2233                 req.put(requestString(_params));
2234         }
2235 
2236         h.byKeyValue.
2237         map!(kv => kv.key ~ ": " ~ kv.value ~ "\r\n").
2238         each!(h => req.put(h));
2239         req.put("\r\n");
2240         if ( encoded ) {
2241             req.put(encoded);
2242         }
2243 
2244         debug(requests) trace(req.data);
2245         if ( _verbosity >= 1 ) {
2246             req.data.splitLines.each!(a => writeln("> " ~ a));
2247         }
2248         //
2249         // Now send request and receive response
2250         //
2251         try {
2252             _stream.send(req.data());
2253             _response._requestSentAt = Clock.currTime;
2254             debug(requests) trace("starting receive response");
2255             receiveResponse(_stream);
2256             debug(requests) tracef("done receive response");
2257             _response._finishedAt = Clock.currTime;
2258         }
2259         catch (NetworkException e) {
2260             // On SEND this can means:
2261             // we started to send request to the server, but it closed connection because of keepalive timeout.
2262             // We have to restart request if possible.
2263 
2264             // On RECEIVE - if we received something - then this exception is real and unexpected error.
2265             // If we didn't receive anything - we can restart request again as it can be
2266             debug(requests) tracef("Exception on receive response: %s", e.msg);
2267             if ( _response._responseHeaders.length != 0 )
2268             {
2269                 _stream.close();
2270                 throw new RequestException("Unexpected network error");
2271             }
2272         }
2273 
2274         if ( serverPrematurelyClosedConnection()
2275             && !restartedRequest
2276             && isIdempotent(_method)
2277             ) {
2278             ///
2279             /// We didn't receive any data (keepalive connectioin closed?)
2280             /// and we can restart this request.
2281             /// Go ahead.
2282             ///
2283             debug(requests) tracef("Server closed keepalive connection");
2284 
2285             assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
2286 
2287             _cm.del(_uri.scheme, _uri.host, _uri.port);
2288             _stream.close();
2289             _stream = null;
2290 
2291             restartedRequest = true;
2292             goto connect;
2293         }
2294 
2295         if ( _useStreaming ) {
2296             if ( _response._receiveAsRange.activated ) {
2297                 debug(requests) trace("streaming_in activated");
2298                 return _response;
2299             } else {
2300                 // this can happen if whole response body received together with headers
2301                 _response._receiveAsRange.data = _response.responseBody.data;
2302             }
2303         }
2304 
2305         close_connection_if_not_keepalive(_stream);
2306 
2307         if ( _verbosity >= 1 ) {
2308             writeln(">> Connect time: ", _response._connectedAt - _response._startedAt);
2309             writeln(">> Request send time: ", _response._requestSentAt - _response._connectedAt);
2310             writeln(">> Response recv time: ", _response._finishedAt - _response._requestSentAt);
2311         }
2312 
2313         if ( willFollowRedirect ) {
2314             debug(requests) trace("going to follow redirect");
2315             if ( _history.length >= _maxRedirects ) {
2316                 _stream = null;
2317                 throw new MaxRedirectsException("%d redirects reached maxRedirects %d.".format(_history.length, _maxRedirects));
2318             }
2319             // "location" in response already checked in canFollowRedirect
2320             immutable new_location = *("location" in _response.responseHeaders);
2321             immutable current_uri = _uri;
2322             immutable next_uri = uriFromLocation(_uri, new_location);
2323 
2324             immutable get_or_head = _method == "GET" || _method == "HEAD";
2325             immutable code = _response.code;
2326 
2327             // save current response for history
2328             _history ~= _response;
2329 
2330             if ( code == 301 )
2331             {
2332                 // permanent redirect and change method
2333                 _permanent_redirects[_uri] = new_location;
2334                 if ( !get_or_head )
2335                 {
2336                     _method = "GET";
2337                 }
2338             }
2339             if ( (code == 302 || code == 303) && !get_or_head)
2340             {
2341                 // only change method
2342                 _method = "GET";
2343             }
2344             if ( code == 307 )
2345             {
2346                 // no change method, no permanent
2347             }
2348             if ( code == 308 )
2349             {
2350                 // permanent redirection and do not change method
2351                 _permanent_redirects[_uri] = new_location;
2352             }
2353 
2354             // prepare new response (for redirected request)
2355             _response = new HTTPResponse;
2356             _response.uri = current_uri;
2357             _response.finalURI = next_uri;
2358             _stream = null;
2359 
2360             // set new uri
2361             _uri = next_uri;
2362             debug(requests) tracef("Redirected to %s", next_uri);
2363             //if ( _method != "GET" && _response.code != 307 && _response.code != 308 ) {
2364             //    // 307 and 308 do not change method
2365             //    return exec_from_parameters(r);
2366             //}
2367             if ( restartedRequest ) {
2368                 debug(requests) trace("Rare event: clearing 'restartedRequest' on redirect");
2369                 restartedRequest = false;
2370             }
2371             goto connect;
2372         }
2373 
2374         _response._history = _history;
2375         return _response;
2376     }
2377     HTTPResponse execute(Request r)
2378     {
2379         _method = r.method;
2380         _uri = r.uri;
2381         _useStreaming = r.useStreaming;
2382         _permanent_redirects = r.permanent_redirects;
2383         _maxRedirects = r.maxRedirects;
2384         _authenticator = r.authenticator;
2385         _maxHeadersLength = r.maxHeadersLength;
2386         _maxContentLength = r.maxContentLength;
2387         _verbosity = r.verbosity;
2388         _keepAlive = r.keepAlive;
2389         _bufferSize = r.bufferSize;
2390         _proxy = r.proxy;
2391         _timeout = r.timeout;
2392         _contentType = r.contentType;
2393         _socketFactory = r.socketFactory;
2394         _sslOptions = r.sslOptions;
2395         _bind = r.bind;
2396         _headers = r.headers;
2397         _userHeaders = r.userHeaders;
2398 
2399         _params = r.params;
2400 
2401         // this assignments increments refCounts, so we can't use const Request
2402         // but Request is anyway struct and called by-value
2403         _cm = r.cm;
2404         _cookie = r.cookie;
2405 
2406         debug(requests) trace("serving %s".format(r));
2407         if ( !r.postData.empty)
2408         {
2409             return exec_from_range(r.postData);
2410         }
2411         if ( r.hasMultipartForm )
2412         {
2413             return exec_from_multipart_form(r.multipartForm);
2414         }
2415         auto rs = exec_from_parameters();
2416         return rs;
2417     }
2418 }
2419 
2420 version(vibeD) {
2421     import std.json;
2422     package string httpTestServer() {
2423         return "http://httpbin.org/";
2424     }
2425     package string fromJsonArrayToStr(JSONValue v) {
2426         return v.str;
2427     }
2428 }
2429 else {
2430     import std.json;
2431     package string httpTestServer() {
2432         return "http://127.0.0.1:8081/";
2433     }
2434     package string fromJsonArrayToStr(JSONValue v) {
2435         return cast(string)(v.array.map!"cast(ubyte)a.integer".array);
2436     }
2437 }