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 ( (_method == "HEAD") || responseMustNotIncludeBody(_response.code) || (_contentLength < 0 && _unChunker is null) )
814         {
815             debug(requests) tracef("response without body");
816             return;
817         }
818 
819         _response._contentLength = _contentLength;
820         _response._contentReceived = _contentReceived;
821 
822         if ( _verbosity >= 2 ) writefln("< %d bytes of body received", partialBody.length);
823 
824         while( true ) {
825             if ( _contentLength >= 0 && _contentReceived >= _contentLength ) {
826                 debug(requests) trace("Body received.");
827                 break;
828             }
829             if ( _unChunker && _unChunker.done ) {
830                 break;
831             }
832 
833             if ( _useStreaming && _response._responseBody.length && !redirectCodes.canFind(_response.code) ) {
834                 debug(requests) trace("streaming requested");
835                 // save _stream in closure
836                 auto __stream = _stream;
837                 auto __bodyDecoder = _bodyDecoder;
838                 auto __unChunker = _unChunker;
839                 auto __contentReceived = _contentReceived;
840                 auto __contentLength = _contentLength;
841                 auto __bufferSize = _bufferSize;
842                 auto __response = _response;
843                 auto __verbosity = _verbosity;
844 
845                 // set up response
846                 _response._contentLength = _contentLength;
847                 _response.receiveAsRange.activated = true;
848                 _response.receiveAsRange.data = _response._responseBody.data;
849                 _response.receiveAsRange.cm = _cm;
850                 _response.receiveAsRange.read = delegate ubyte[] () {
851 
852                     while(true) {
853                         // check if we received everything we need
854                         if ( ( __unChunker && __unChunker.done )
855                             || !__stream.isConnected()
856                             || (__contentLength > 0 && __contentReceived >= __contentLength) )
857                         {
858                             debug(requests) trace("streaming_in receive completed");
859                             __bodyDecoder.flush();
860                             return __bodyDecoder.get();
861                         }
862                         // have to continue
863                         auto b = new ubyte[__bufferSize];
864                         try {
865                             read = __stream.receive(b);
866                         }
867                         catch (Exception e) {
868                             throw new RequestException("streaming_in error reading from socket", __FILE__, __LINE__, e);
869                         }
870                         debug(requests) tracef("streaming_in received %d bytes", read);
871 
872                         if ( read == 0 ) {
873                             debug(requests) tracef("streaming_in: server closed connection");
874                             __bodyDecoder.flush();
875                             return __bodyDecoder.get();
876                         }
877 
878                         if ( __verbosity>=3 ) {
879                             writeln(b[0..read].dump.join("\n"));
880                         }
881                         __response._contentReceived += read;
882                         __contentReceived += read;
883                         __bodyDecoder.putNoCopy(b[0..read]);
884                         auto res = __bodyDecoder.getNoCopy();
885                         if ( res.length == 0 ) {
886                             // there were nothing to produce (beginning of the chunk or no decompressed data)
887                             continue;
888                         }
889                         if (res.length == 1) {
890                             return res[0];
891                         }
892                         //
893                         // I'd like to "return _bodyDecoder.getNoCopy().join;" but it is slower
894                         //
895                         auto total = res.map!(b=>b.length).sum;
896                         // create buffer for joined bytes
897                         ubyte[] joined = new ubyte[total];
898                         size_t p;
899                         // memcopy
900                         foreach(ref _; res) {
901                             joined[p .. p + _.length] = _;
902                             p += _.length;
903                         }
904                         return joined;
905                     }
906                     assert(0);
907                 };
908                 // we prepared for streaming
909                 return;
910             }
911 
912             auto b = new ubyte[_bufferSize];
913             read = _stream.receive(b);
914 
915             if ( read == 0 ) {
916                 debug(requests) trace("read done");
917                 break;
918             }
919             if ( _verbosity >= 2 ) {
920                 writefln("< %d bytes of body received", read);
921             }
922 
923             if ( verbosity>=3 ) {
924                 writeln(b[0..read].dump.join("\n"));
925             }
926 
927             debug(requests) tracef("read: %d", read);
928             _contentReceived += read;
929             if ( _maxContentLength && _contentReceived > _maxContentLength ) {
930                 throw new RequestException("ContentLength > maxContentLength (%d>%d)".
931                     format(_contentLength, _maxContentLength));
932             }
933 
934             _bodyDecoder.putNoCopy(b[0..read]); // send buffer to all decoders
935 
936             _bodyDecoder.getNoCopy.             // fetch result and place to body
937                 each!(b => _response._responseBody.putNoCopy(b));
938 
939             debug(requests) tracef("receivedTotal: %d, contentLength: %d, bodyLength: %d", _contentReceived, _contentLength, _response._responseBody.length);
940 
941         }
942         _bodyDecoder.flush();
943         _response._responseBody.putNoCopy(_bodyDecoder.get());
944         _response._contentReceived = _contentReceived;
945     }
946     ///
947     /// Check that we received anything.
948     /// Server can close previous connection (keepalive or not)
949     ///
950     private bool serverPrematurelyClosedConnection() pure @safe {
951         immutable server_closed_connection = _response._responseHeaders.length == 0 && _response._status_line.length == 0;
952         // debug(requests) tracef("server closed connection = %s (headers.length=%s, status_line.length=%s)",
953         //     server_closed_connection, _response._responseHeaders.length,  _response._status_line.length);
954         return server_closed_connection;
955     }
956     private bool isIdempotent(in string method) pure @safe nothrow {
957         return ["GET", "HEAD"].canFind(method);
958     }
959     ///
960     /// If we do not want keepalive request,
961     /// or server signalled to close connection,
962     /// then close it
963     ///
964     void close_connection_if_not_keepalive(NetworkStream _stream) {
965         auto connection = "connection" in _response._responseHeaders;
966         if ( !_keepAlive ) {
967             _stream.close();
968         } else switch(_response._version) {
969             case HTTP11:
970                 // HTTP/1.1 defines the "close" connection option for the sender to signal that the connection
971                 // will be closed after completion of the response. For example,
972                 //        Connection: close
973                 // in either the request or the response header fields indicates that the connection
974                 // SHOULD NOT be considered `persistent' (section 8.1) after the current request/response is complete.
975                 // HTTP/1.1 applications that do not support persistent connections MUST include the "close" connection
976                 // option in every message.
977                 if ( connection && (*connection).toLower.split(",").canFind("close") ) {
978                     _stream.close();
979                 }
980                 break;
981             default:
982                 // for anything else close connection if there is no keep-alive in Connection
983                 if ( connection && !(*connection).toLower.split(",").canFind("keep-alive") ) {
984                     _stream.close();
985                 }
986                 break;
987         }
988     }
989     ///
990     /// Send multipart for request.
991     /// You would like to use this method for sending large portions of mixed data or uploading files to forms.
992     /// 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)
993     /// Params:
994     ///     url = url
995     ///     sources = array of sources.
996     deprecated("Use Request() instead of HTTPRequest(); will be removed 2019-07")
997     HTTPResponse exec(string method="POST")(string url, MultipartForm sources) {
998         import std.uuid;
999         import std.file;
1000 
1001         checkURL(url);
1002         //if ( _cm is null ) {
1003         //    _cm = new ConnManager();
1004         //}
1005 
1006         NetworkStream _stream;
1007         _method = method;
1008         _response = new HTTPResponse;
1009         _response.uri = _uri;
1010         _response.finalURI = _uri;
1011         bool restartedRequest = false;
1012 
1013     connect:
1014         _contentReceived = 0;
1015         _response._startedAt = Clock.currTime;
1016 
1017         assert(_stream is null);
1018 
1019         _stream = _cm.get(_uri.scheme, _uri.host, _uri.port);
1020 
1021         if ( _stream is null ) {
1022             debug(requests) trace("create new connection");
1023             _stream = setupConnection();
1024         } else {
1025             debug(requests) trace("reuse old connection");
1026         }
1027 
1028         assert(_stream !is null);
1029 
1030         if ( !_stream.isConnected ) {
1031             debug(requests) trace("disconnected stream on enter");
1032             if ( !restartedRequest ) {
1033                 debug(requests) trace("disconnected stream on enter: retry");
1034                 assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1035 
1036                 _cm.del(_uri.scheme, _uri.host, _uri.port);
1037                 _stream.close();
1038                 _stream = null;
1039 
1040                 restartedRequest = true;
1041                 goto connect;
1042             }
1043             debug(requests) trace("disconnected stream on enter: return response");
1044             //_stream = null;
1045             return _response;
1046         }
1047         _response._connectedAt = Clock.currTime;
1048 
1049         Appender!string req;
1050         req.put(requestString());
1051 
1052         string   boundary = randomUUID().toString;
1053         string[] partHeaders;
1054         size_t   contentLength;
1055 
1056         foreach(ref part; sources._sources) {
1057             string h = "--" ~ boundary ~ "\r\n";
1058             string disposition = `form-data; name="%s"`.format(part.name);
1059             string optionals = part.
1060                 parameters.byKeyValue().
1061                 filter!(p => p.key!="Content-Type").
1062                 map!   (p => "%s=%s".format(p.key, p.value)).
1063                 join("; ");
1064 
1065             h ~= `Content-Disposition: ` ~ [disposition, optionals].join("; ") ~ "\r\n";
1066 
1067             auto contentType = "Content-Type" in part.parameters;
1068             if ( contentType ) {
1069                 h ~= "Content-Type: " ~ *contentType ~ "\r\n";
1070             }
1071 
1072             h ~= "\r\n";
1073             partHeaders ~= h;
1074             contentLength += h.length + part.input.getSize() + "\r\n".length;
1075         }
1076         contentLength += "--".length + boundary.length + "--\r\n".length;
1077 
1078         auto h = requestHeaders();
1079         safeSetHeader(h, _userHeaders.ContentType, "Content-Type", "multipart/form-data; boundary=" ~ boundary);
1080         safeSetHeader(h, _userHeaders.ContentLength, "Content-Length", to!string(contentLength));
1081 
1082         h.byKeyValue.
1083             map!(kv => kv.key ~ ": " ~ kv.value ~ "\r\n").
1084                 each!(h => req.put(h));
1085         req.put("\r\n");
1086 
1087         debug(requests) trace(req.data);
1088         if ( _verbosity >= 1 ) req.data.splitLines.each!(a => writeln("> " ~ a));
1089 
1090         try {
1091             _stream.send(req.data());
1092             foreach(ref source; sources._sources) {
1093                 debug(requests) tracef("sending part headers <%s>", partHeaders.front);
1094                 _stream.send(partHeaders.front);
1095                 partHeaders.popFront;
1096                 while (true) {
1097                     auto chunk = source.input.read();
1098                     if ( chunk.length <= 0 ) {
1099                         break;
1100                     }
1101                     _stream.send(chunk);
1102                 }
1103                 _stream.send("\r\n");
1104             }
1105             _stream.send("--" ~ boundary ~ "--\r\n");
1106             _response._requestSentAt = Clock.currTime;
1107             receiveResponse(_stream);
1108             _response._finishedAt = Clock.currTime;
1109         }
1110         catch (NetworkException e) {
1111             errorf("Error sending request: ", e.msg);
1112             _stream.close();
1113             return _response;
1114         }
1115 
1116         if ( serverPrematurelyClosedConnection()
1117         && !restartedRequest
1118         && isIdempotent(_method)
1119         ) {
1120             ///
1121             /// We didn't receive any data (keepalive connectioin closed?)
1122             /// and we can restart this request.
1123             /// Go ahead.
1124             ///
1125             debug(requests) tracef("Server closed keepalive connection");
1126 
1127             assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1128 
1129             _cm.del(_uri.scheme, _uri.host, _uri.port);
1130             _stream.close();
1131             _stream = null;
1132 
1133             restartedRequest = true;
1134             goto connect;
1135         }
1136 
1137         if ( _useStreaming ) {
1138             if ( _response._receiveAsRange.activated ) {
1139                 debug(requests) trace("streaming_in activated");
1140                 return _response;
1141             } else {
1142                 // this can happen if whole response body received together with headers
1143                 _response._receiveAsRange.data = _response.responseBody.data;
1144             }
1145         }
1146 
1147         close_connection_if_not_keepalive(_stream);
1148 
1149         if ( _verbosity >= 1 ) {
1150             writeln(">> Connect time: ", _response._connectedAt - _response._startedAt);
1151             writeln(">> Request send time: ", _response._requestSentAt - _response._connectedAt);
1152             writeln(">> Response recv time: ", _response._finishedAt - _response._requestSentAt);
1153         }
1154 
1155         if ( willFollowRedirect ) {
1156             if ( _history.length >= _maxRedirects ) {
1157                 _stream = null;
1158                 throw new MaxRedirectsException("%d redirects reached maxRedirects %d.".format(_history.length, _maxRedirects));
1159             }
1160             // "location" in response already checked in canFollowRedirect
1161             immutable new_location = *("location" in _response.responseHeaders);
1162             immutable current_uri = _uri, next_uri = uriFromLocation(_uri, new_location);
1163 
1164             // save current response for history
1165             _history ~= _response;
1166 
1167             // prepare new response (for redirected request)
1168             _response = new HTTPResponse;
1169             _response.uri = current_uri;
1170             _response.finalURI = next_uri;
1171             _stream = null;
1172 
1173             // set new uri
1174             this._uri = next_uri;
1175             debug(requests) tracef("Redirected to %s", next_uri);
1176             if ( _method != "GET" && _response.code != 307 && _response.code != 308 ) {
1177                 // 307 and 308 do not change method
1178                 return this.get();
1179             }
1180             if ( restartedRequest ) {
1181                 debug(requests) trace("Rare event: clearing 'restartedRequest' on redirect");
1182                 restartedRequest = false;
1183             }
1184             goto connect;
1185         }
1186 
1187         _response._history = _history;
1188         return _response;
1189     }
1190 
1191     // we use this if we send from ubyte[][] and user provided Content-Length
1192     private void sendFlattenContent(T)(NetworkStream _stream, T content) {
1193         while ( !content.empty ) {
1194             auto chunk = content.front;
1195             _stream.send(chunk);
1196             content.popFront;
1197         }
1198         debug(requests) tracef("sent");
1199     }
1200     // we use this if we send from ubyte[][] as chunked content
1201     private void sendChunkedContent(T)(NetworkStream _stream, T content) {
1202         while ( !content.empty ) {
1203             auto chunk = content.front;
1204             auto chunkHeader = "%x\r\n".format(chunk.length);
1205             debug(requests) tracef("sending %s%s", chunkHeader, chunk);
1206             _stream.send(chunkHeader);
1207             _stream.send(chunk);
1208             _stream.send("\r\n");
1209             content.popFront;
1210         }
1211         debug(requests) tracef("sent");
1212         _stream.send("0\r\n\r\n");
1213     }
1214     ///
1215     /// POST/PUT/... data from some string(with Content-Length), or from range of strings/bytes (use Transfer-Encoding: chunked).
1216     /// 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.
1217     /// 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.
1218     /// 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.
1219     ///
1220     /// Parameters:
1221     ///    url = url
1222     ///    content = string or input range
1223     ///    contentType = content type
1224     ///  Returns:
1225     ///     Response
1226     ///  Examples:
1227     ///  ---------------------------------------------------------------------------------------------------------
1228     ///      rs = rq.exec!"POST"("http://httpbin.org/post", "привiт, свiт!", "application/octet-stream");
1229     ///
1230     ///      auto s = lineSplitter("one,\ntwo,\nthree.");
1231     ///      rs = rq.exec!"POST"("http://httpbin.org/post", s, "application/octet-stream");
1232     ///
1233     ///      auto s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1234     ///      rs = rq.exec!"POST"("http://httpbin.org/post", s.representation.chunks(10), "application/octet-stream");
1235     ///
1236     ///      auto f = File("tests/test.txt", "rb");
1237     ///      rs = rq.exec!"POST"("http://httpbin.org/post", f.byChunk(3), "application/octet-stream");
1238     ///  --------------------------------------------------------------------------------------------------------
1239     deprecated("Use Request() instead of HTTPRequest(); will be removed 2019-07")
1240     HTTPResponse exec(string method="POST", R)(string url, R content, string contentType="application/octet-stream")
1241         if ( (rank!R == 1)
1242             || (rank!R == 2 && isSomeChar!(Unqual!(typeof(content.front.front))))
1243             || (rank!R == 2 && (is(Unqual!(typeof(content.front.front)) == ubyte)))
1244         )
1245     do {
1246         debug(requests) tracef("started url=%s, this._uri=%s", url, _uri);
1247 
1248         checkURL(url);
1249         //if ( _cm is null ) {
1250         //    _cm = new ConnManager();
1251         //}
1252 
1253         NetworkStream _stream;
1254         _method = method;
1255         _response = new HTTPResponse;
1256         _history.length = 0;
1257         _response.uri = _uri;
1258         _response.finalURI = _uri;
1259         bool restartedRequest = false;
1260         bool send_flat;
1261 
1262     connect:
1263         _contentReceived = 0;
1264         _response._startedAt = Clock.currTime;
1265 
1266         assert(_stream is null);
1267 
1268         _stream = _cm.get(_uri.scheme, _uri.host, _uri.port);
1269 
1270         if ( _stream is null ) {
1271             debug(requests) trace("create new connection");
1272             _stream = setupConnection();
1273         } else {
1274             debug(requests) trace("reuse old connection");
1275         }
1276 
1277         assert(_stream !is null);
1278 
1279         if ( !_stream.isConnected ) {
1280             debug(requests) trace("disconnected stream on enter");
1281             if ( !restartedRequest ) {
1282                 debug(requests) trace("disconnected stream on enter: retry");
1283                 assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1284 
1285                 _cm.del(_uri.scheme, _uri.host, _uri.port);
1286                 _stream.close();
1287                 _stream = null;
1288 
1289                 restartedRequest = true;
1290                 goto connect;
1291             }
1292             debug(requests) trace("disconnected stream on enter: return response");
1293             //_stream = null;
1294             return _response;
1295         }
1296         _response._connectedAt = Clock.currTime;
1297 
1298         Appender!string req;
1299         req.put(requestString());
1300 
1301         auto h = requestHeaders;
1302         if ( contentType ) {
1303             safeSetHeader(h, _userHeaders.ContentType, "Content-Type", contentType);
1304         }
1305         static if ( rank!R == 1 ) {
1306             safeSetHeader(h, _userHeaders.ContentLength, "Content-Length", to!string(content.length));
1307         } else {
1308             if ( _userHeaders.ContentLength ) {
1309                 debug(requests) tracef("User provided content-length for chunked content");
1310                 send_flat = true;
1311             } else {
1312                 h["Transfer-Encoding"] = "chunked";
1313                 send_flat = false;
1314             }
1315         }
1316         h.byKeyValue.
1317             map!(kv => kv.key ~ ": " ~ kv.value ~ "\r\n").
1318             each!(h => req.put(h));
1319         req.put("\r\n");
1320 
1321         debug(requests) trace(req.data);
1322         if ( _verbosity >= 1 ) {
1323             req.data.splitLines.each!(a => writeln("> " ~ a));
1324         }
1325 
1326         try {
1327             // send headers
1328             _stream.send(req.data());
1329             // send body
1330             static if ( rank!R == 1) {
1331                 _stream.send(content);
1332             } else {
1333                 if ( send_flat ) {
1334                     sendFlattenContent(_stream, content);
1335                 } else {
1336                     sendChunkedContent(_stream, content);
1337                 }
1338             }
1339             _response._requestSentAt = Clock.currTime;
1340             debug(requests) trace("starting receive response");
1341             receiveResponse(_stream);
1342             debug(requests) trace("finished receive response");
1343             _response._finishedAt = Clock.currTime;
1344         } catch (NetworkException e) {
1345             _stream.close();
1346             throw new RequestException("Network error during data exchange");
1347         }
1348 
1349         if ( serverPrematurelyClosedConnection()
1350             && !restartedRequest
1351             && isIdempotent(_method))
1352         {
1353             ///
1354             /// We didn't receive any data (keepalive connectioin closed?)
1355             /// and we can restart this request.
1356             /// Go ahead.
1357             ///
1358             debug(requests) tracef("Server closed keepalive connection");
1359 
1360             assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1361 
1362             _cm.del(_uri.scheme, _uri.host, _uri.port);
1363             _stream.close();
1364             _stream = null;
1365 
1366             restartedRequest = true;
1367             goto connect;
1368         }
1369 
1370         if ( _useStreaming ) {
1371             if ( _response._receiveAsRange.activated ) {
1372                 debug(requests) trace("streaming_in activated");
1373                 return _response;
1374             } else {
1375                 // this can happen if whole response body received together with headers
1376                 _response._receiveAsRange.data = _response.responseBody.data;
1377             }
1378         }
1379 
1380         close_connection_if_not_keepalive(_stream);
1381 
1382         if ( _verbosity >= 1 ) {
1383             writeln(">> Connect time: ", _response._connectedAt - _response._startedAt);
1384             writeln(">> Request send time: ", _response._requestSentAt - _response._connectedAt);
1385             writeln(">> Response recv time: ", _response._finishedAt - _response._requestSentAt);
1386         }
1387 
1388 
1389         if ( willFollowRedirect ) {
1390             if ( _history.length >= _maxRedirects ) {
1391                 _stream = null;
1392                 throw new MaxRedirectsException("%d redirects reached maxRedirects %d.".format(_history.length, _maxRedirects));
1393             }
1394             // "location" in response already checked in canFollowRedirect
1395             immutable new_location = *("location" in _response.responseHeaders);
1396             immutable current_uri = _uri, next_uri = uriFromLocation(_uri, new_location);
1397 
1398             // save current response for history
1399             _history ~= _response;
1400 
1401             // prepare new response (for redirected request)
1402             _response = new HTTPResponse;
1403             _response.uri = current_uri;
1404             _response.finalURI = next_uri;
1405 
1406             _stream = null;
1407 
1408             // set new uri
1409             this._uri = next_uri;
1410             debug(requests) tracef("Redirected to %s", next_uri);
1411             if ( _method != "GET" && _response.code != 307 && _response.code != 308 ) {
1412                 // 307 and 308 do not change method
1413                 return this.get();
1414             }
1415             if ( restartedRequest ) {
1416                 debug(requests) trace("Rare event: clearing 'restartedRequest' on redirect");
1417                 restartedRequest = false;
1418             }
1419             goto connect;
1420         }
1421 
1422         _response._history = _history;
1423         return _response;
1424     }
1425     ///
1426     /// Send request with parameters.
1427     /// If used for POST or PUT requests then application/x-www-form-urlencoded used.
1428     /// Request parameters will be encoded into request string or placed in request body for POST/PUT
1429     /// requests.
1430     /// Parameters:
1431     ///     url = url
1432     ///     params = request parameters
1433     ///  Returns:
1434     ///     Response
1435     ///  Examples:
1436     ///  ---------------------------------------------------------------------------------
1437     ///     rs = Request().exec!"GET"("http://httpbin.org/get", ["c":"d", "a":"b"]);
1438     ///  ---------------------------------------------------------------------------------
1439     ///
1440     deprecated("Use Request() instead of HTTPRequest; will be removed 2019-07")
1441     HTTPResponse exec(string method="GET")(string url = null, QueryParam[] params = null)
1442     do {
1443         debug(requests) tracef("started url=%s, this._uri=%s", url, _uri);
1444 
1445         checkURL(url);
1446         //if ( _cm is null ) {
1447         //    _cm = new ConnManager();
1448         //}
1449 
1450         NetworkStream _stream;
1451         _method = method;
1452         _response = new HTTPResponse;
1453         _history.length = 0;
1454         _response.uri = _uri;
1455         _response.finalURI = _uri;
1456         bool restartedRequest = false; // True if this is restarted keepAlive request
1457 
1458     connect:
1459         if ( _method == "GET" && _uri in _permanent_redirects ) {
1460             debug(requests) trace("use parmanent redirects cache");
1461             _uri = uriFromLocation(_uri, _permanent_redirects[_uri]);
1462             _response._finalURI = _uri;
1463         }
1464         _contentReceived = 0;
1465         _response._startedAt = Clock.currTime;
1466 
1467         assert(_stream is null);
1468 
1469         _stream = _cm.get(_uri.scheme, _uri.host, _uri.port);
1470 
1471         if ( _stream is null ) {
1472             debug(requests) trace("create new connection");
1473             _stream = setupConnection();
1474         } else {
1475             debug(requests) trace("reuse old connection");
1476         }
1477 
1478         assert(_stream !is null);
1479 
1480         if ( !_stream.isConnected ) {
1481             debug(requests) trace("disconnected stream on enter");
1482             if ( !restartedRequest ) {
1483                 debug(requests) trace("disconnected stream on enter: retry");
1484                 assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1485 
1486                 _cm.del(_uri.scheme, _uri.host, _uri.port);
1487                 _stream.close();
1488                 _stream = null;
1489 
1490                 restartedRequest = true;
1491                 goto connect;
1492             }
1493             debug(requests) trace("disconnected stream on enter: return response");
1494             //_stream = null;
1495             return _response;
1496         }
1497         _response._connectedAt = Clock.currTime;
1498 
1499         auto h = requestHeaders();
1500 
1501         Appender!string req;
1502 
1503         string encoded;
1504 
1505         switch (_method) {
1506             case "POST","PUT","PATCH":
1507                 encoded = params2query(params);
1508                 safeSetHeader(h, _userHeaders.ContentType, "Content-Type", "application/x-www-form-urlencoded");
1509                 if ( encoded.length > 0) {
1510                     safeSetHeader(h, _userHeaders.ContentLength, "Content-Length", to!string(encoded.length));
1511                 }
1512                 req.put(requestString());
1513                 break;
1514             default:
1515                 req.put(requestString(params));
1516         }
1517 
1518         h.byKeyValue.
1519             map!(kv => kv.key ~ ": " ~ kv.value ~ "\r\n").
1520             each!(h => req.put(h));
1521         req.put("\r\n");
1522         if ( encoded ) {
1523             req.put(encoded);
1524         }
1525 
1526         debug(requests) trace(req.data);
1527         if ( _verbosity >= 1 ) {
1528             req.data.splitLines.each!(a => writeln("> " ~ a));
1529         }
1530         //
1531         // Now send request and receive response
1532         //
1533         try {
1534             _stream.send(req.data());
1535             _response._requestSentAt = Clock.currTime;
1536             debug(requests) trace("starting receive response");
1537             receiveResponse(_stream);
1538             debug(requests) trace("done receive response");
1539             _response._finishedAt = Clock.currTime;
1540         }
1541         catch (NetworkException e) {
1542             // On SEND this can means:
1543             // we started to send request to the server, but it closed connection because of keepalive timeout.
1544             // We have to restart request if possible.
1545 
1546             // On RECEIVE - if we received something - then this exception is real and unexpected error.
1547             // If we didn't receive anything - we can restart request again as it can be
1548             debug(requests) tracef("Exception on receive response: %s", e.msg);
1549             if ( _response._responseHeaders.length != 0 )
1550             {
1551                 _stream.close();
1552                 throw new RequestException("Unexpected network error");
1553             }
1554         }
1555 
1556         if ( serverPrematurelyClosedConnection()
1557             && !restartedRequest
1558             && isIdempotent(_method)
1559             ) {
1560             ///
1561             /// We didn't receive any data (keepalive connectioin closed?)
1562             /// and we can restart this request.
1563             /// Go ahead.
1564             ///
1565             debug(requests) tracef("Server closed keepalive connection");
1566 
1567             assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1568 
1569             _cm.del(_uri.scheme, _uri.host, _uri.port);
1570             _stream.close();
1571             _stream = null;
1572 
1573             restartedRequest = true;
1574             goto connect;
1575         }
1576 
1577         if ( _useStreaming ) {
1578             if ( _response._receiveAsRange.activated ) {
1579                 debug(requests) trace("streaming_in activated");
1580                 return _response;
1581             } else {
1582                 // this can happen if whole response body received together with headers
1583                 _response._receiveAsRange.data = _response.responseBody.data;
1584             }
1585         }
1586 
1587         close_connection_if_not_keepalive(_stream);
1588 
1589         if ( _verbosity >= 1 ) {
1590             writeln(">> Connect time: ", _response._connectedAt - _response._startedAt);
1591             writeln(">> Request send time: ", _response._requestSentAt - _response._connectedAt);
1592             writeln(">> Response recv time: ", _response._finishedAt - _response._requestSentAt);
1593         }
1594 
1595         if ( willFollowRedirect ) {
1596             debug(requests) trace("going to follow redirect");
1597             if ( _history.length >= _maxRedirects ) {
1598                 _stream = null;
1599                 throw new MaxRedirectsException("%d redirects reached maxRedirects %d.".format(_history.length, _maxRedirects));
1600             }
1601             // "location" in response already checked in canFollowRedirect
1602             immutable new_location = *("location" in _response.responseHeaders);
1603             immutable current_uri = _uri, next_uri = uriFromLocation(_uri, new_location);
1604 
1605             if ( _method == "GET" && _response.code == 301 ) {
1606                 _permanent_redirects[_uri] = new_location;
1607             }
1608 
1609             // save current response for history
1610             _history ~= _response;
1611 
1612             // prepare new response (for redirected request)
1613             _response = new HTTPResponse;
1614             _response.uri = current_uri;
1615             _response.finalURI = next_uri;
1616             _stream = null;
1617 
1618             // set new uri
1619             _uri = next_uri;
1620             debug(requests) tracef("Redirected to %s", next_uri);
1621             if ( _method != "GET" && _response.code != 307 && _response.code != 308 ) {
1622                 // 307 and 308 do not change method
1623                 return this.get();
1624             }
1625             if ( restartedRequest ) {
1626                 debug(requests) trace("Rare event: clearing 'restartedRequest' on redirect");
1627                 restartedRequest = false;
1628             }
1629             goto connect;
1630         }
1631 
1632         _response._history = _history;
1633         return _response;
1634     }
1635 
1636     /// WRAPPERS
1637     ///
1638     /// send file(s) using POST and multipart form.
1639     /// This wrapper will be deprecated, use post with MultipartForm - it is more general and clear.
1640     /// Parameters:
1641     ///     url = url
1642     ///     files = array of PostFile structures
1643     /// Returns:
1644     ///     Response
1645     /// Each PostFile structure contain path to file, and optional field name and content type.
1646     /// If no field name provided, then basename of the file will be used.
1647     /// application/octet-stream is default when no content type provided.
1648     /// Example:
1649     /// ---------------------------------------------------------------
1650     ///    PostFile[] files = [
1651     ///                   {fileName:"tests/abc.txt", fieldName:"abc", contentType:"application/octet-stream"},
1652     ///                   {fileName:"tests/test.txt"}
1653     ///               ];
1654     ///    rs = rq.exec!"POST"("http://httpbin.org/post", files);
1655     /// ---------------------------------------------------------------
1656     ///
1657     deprecated("Use Request() instead of HTTPRequest(); will be removed 2019-07")
1658     HTTPResponse exec(string method="POST")(string url, PostFile[] files) if (method=="POST") {
1659         MultipartForm multipart;
1660         File[]        toClose;
1661         foreach(ref f; files) {
1662             File file = File(f.fileName, "rb");
1663             toClose ~= file;
1664             string fileName = f.fileName ? f.fileName : f.fieldName;
1665             string contentType = f.contentType ? f.contentType : "application/octetstream";
1666             multipart.add(f.fieldName, new FormDataFile(file), ["filename":fileName, "Content-Type": contentType]);
1667         }
1668         auto res = exec!"POST"(url, multipart);
1669         toClose.each!"a.close";
1670         return res;
1671     }
1672     ///
1673     /// exec request with parameters when you can use dictionary (when you have no duplicates in parameter names)
1674     /// Consider switch to exec(url, QueryParams) as it more generic and clear.
1675     /// Parameters:
1676     ///     url = url
1677     ///     params = dictionary with field names as keys and field values as values.
1678     /// Returns:
1679     ///     Response
1680     deprecated("Use Request() instead of HTTPRequest(); will be removed 2019-07")
1681     HTTPResponse exec(string method="GET")(string url, string[string] params) {
1682         return exec!method(url, params.byKeyValue.map!(p => QueryParam(p.key, p.value)).array);
1683     }
1684     ///
1685     /// GET request. Simple wrapper over exec!"GET"
1686     /// Params:
1687     /// args = request parameters. see exec docs.
1688     ///
1689     deprecated("Use Request() instead of HTTPRequest; will be removed 2019-07")
1690     HTTPResponse get(A...)(A args) {
1691         return exec!"GET"(args);
1692     }
1693     ///
1694     /// POST request. Simple wrapper over exec!"POST"
1695     /// Params:
1696     /// uri = endpoint uri
1697     /// args = request parameters. see exec docs.
1698     ///
1699     deprecated("Use Request() instead of HTTPRequest; will be removed 2019-07")
1700     HTTPResponse post(A...)(string uri, A args) {
1701         return exec!"POST"(uri, args);
1702     }
1703 
1704     import requests.request;
1705 
1706     // we use this if we send from ubyte[][] and user provided Content-Length
1707     private void sendFlattenContent(NetworkStream _stream) {
1708         while ( !_postData.empty ) {
1709             auto chunk = _postData.front;
1710             _stream.send(chunk);
1711             _postData.popFront;
1712         }
1713         debug(requests) tracef("sent");
1714     }
1715     // we use this if we send from ubyte[][] as chunked content
1716     private void sendChunkedContent(NetworkStream _stream) {
1717         while ( !_postData.empty ) {
1718             auto chunk = _postData.front;
1719             auto chunkHeader = "%x\r\n".format(chunk.length);
1720             debug(requests) tracef("sending %s%s", chunkHeader, cast(string)chunk);
1721             _stream.send(chunkHeader);
1722             _stream.send(chunk);
1723             _stream.send("\r\n");
1724             debug(requests) tracef("chunk sent");
1725             _postData.popFront;
1726         }
1727         debug(requests) tracef("sent");
1728         _stream.send("0\r\n\r\n");
1729     }
1730 
1731     HTTPResponse exec_from_range(InputRangeAdapter postData)
1732     do {
1733 
1734         _postData = postData;
1735 
1736         debug(requests) tracef("exec from range");
1737 
1738         NetworkStream _stream;
1739         _response = new HTTPResponse;
1740         _history.length = 0;
1741         _response.uri = _uri;
1742         _response.finalURI = _uri;
1743         bool restartedRequest = false;
1744         bool send_flat;
1745 
1746     connect:
1747         _contentReceived = 0;
1748         _response._startedAt = Clock.currTime;
1749 
1750         assert(_stream is null);
1751 
1752         _stream = _cm.get(_uri.scheme, _uri.host, _uri.port);
1753 
1754         if ( _stream is null ) {
1755             debug(requests) trace("create new connection");
1756             _stream = setupConnection();
1757         } else {
1758             debug(requests) trace("reuse old connection");
1759         }
1760 
1761         assert(_stream !is null);
1762 
1763         if ( !_stream.isConnected ) {
1764             debug(requests) trace("disconnected stream on enter");
1765             if ( !restartedRequest ) {
1766                 debug(requests) trace("disconnected stream on enter: retry");
1767                 assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1768 
1769                 _cm.del(_uri.scheme, _uri.host, _uri.port);
1770                 _stream.close();
1771                 _stream = null;
1772 
1773                 restartedRequest = true;
1774                 goto connect;
1775             }
1776             debug(requests) trace("disconnected stream on enter: return response");
1777             //_stream = null;
1778             return _response;
1779         }
1780         _response._connectedAt = Clock.currTime;
1781 
1782         Appender!string req;
1783         req.put(requestString());
1784 
1785         auto h = requestHeaders;
1786         if ( _contentType ) {
1787             safeSetHeader(h, _userHeaders.ContentType, "Content-Type", _contentType);
1788         }
1789 
1790         if ( _postData.length >= 0 )
1791         {
1792             // we know t
1793             safeSetHeader(h, _userHeaders.ContentLength, "Content-Length", to!string(_postData.length));
1794         }
1795 
1796         if ( _userHeaders.ContentLength || "Content-Length" in h )
1797         {
1798             debug(requests) tracef("User provided content-length for chunked content");
1799             send_flat = true;
1800         }
1801         else
1802         {
1803             h["Transfer-Encoding"] = "chunked";
1804             send_flat = false;
1805         }
1806         h.byKeyValue.
1807             map!(kv => kv.key ~ ": " ~ kv.value ~ "\r\n").
1808             each!(h => req.put(h));
1809         req.put("\r\n");
1810 
1811         debug(requests) tracef("send <%s>", req.data);
1812         if ( _verbosity >= 1 ) {
1813             req.data.splitLines.each!(a => writeln("> " ~ a));
1814         }
1815 
1816         try {
1817             // send headers
1818             _stream.send(req.data());
1819             // send body
1820             if ( send_flat ) {
1821                 sendFlattenContent(_stream);
1822             } else {
1823                 sendChunkedContent(_stream);
1824             }
1825             _response._requestSentAt = Clock.currTime;
1826             debug(requests) trace("starting receive response");
1827             receiveResponse(_stream);
1828             debug(requests) trace("finished receive response");
1829             _response._finishedAt = Clock.currTime;
1830         }
1831         catch (NetworkException e)
1832         {
1833             _stream.close();
1834             throw new RequestException("Network error during data exchange");
1835         }
1836         if ( serverPrematurelyClosedConnection()
1837         && !restartedRequest
1838         && isIdempotent(_method)
1839         ) {
1840             ///
1841             /// We didn't receive any data (keepalive connectioin closed?)
1842             /// and we can restart this request.
1843             /// Go ahead.
1844             ///
1845             debug(requests) tracef("Server closed keepalive connection");
1846 
1847             assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1848 
1849             _cm.del(_uri.scheme, _uri.host, _uri.port);
1850             _stream.close();
1851             _stream = null;
1852 
1853             restartedRequest = true;
1854             goto connect;
1855         }
1856 
1857         if ( _useStreaming ) {
1858             if ( _response._receiveAsRange.activated ) {
1859                 debug(requests) trace("streaming_in activated");
1860                 return _response;
1861             } else {
1862                 // this can happen if whole response body received together with headers
1863                 _response._receiveAsRange.data = _response.responseBody.data;
1864             }
1865         }
1866 
1867         close_connection_if_not_keepalive(_stream);
1868 
1869         if ( _verbosity >= 1 ) {
1870             writeln(">> Connect time: ", _response._connectedAt - _response._startedAt);
1871             writeln(">> Request send time: ", _response._requestSentAt - _response._connectedAt);
1872             writeln(">> Response recv time: ", _response._finishedAt - _response._requestSentAt);
1873         }
1874 
1875 
1876         if ( willFollowRedirect ) {
1877             if ( _history.length >= _maxRedirects ) {
1878                 _stream = null;
1879                 throw new MaxRedirectsException("%d redirects reached maxRedirects %d.".format(_history.length, _maxRedirects));
1880             }
1881             // "location" in response already checked in canFollowRedirect
1882             immutable new_location = *("location" in _response.responseHeaders);
1883             immutable current_uri = _uri, next_uri = uriFromLocation(_uri, new_location);
1884 
1885             immutable get_or_head = _method == "GET" || _method == "HEAD";
1886             immutable code = _response.code;
1887 
1888             // save current response for history
1889             _history ~= _response;
1890 
1891             if ( code == 301 )
1892             {
1893                 // permanent redirect and change method
1894                 _permanent_redirects[_uri] = new_location;
1895                 if ( !get_or_head )
1896                 {
1897                     _method = "GET";
1898                 }
1899             }
1900             if ( (code == 302 || code == 303) && !get_or_head)
1901             {
1902                 // only change method
1903                 _method = "GET";
1904             }
1905             if ( code == 307 )
1906             {
1907                 // no change method, no permanent
1908             }
1909             if ( code == 308 )
1910             {
1911                 // permanent redirection and do not change method
1912                 _permanent_redirects[_uri] = new_location;
1913             }
1914 
1915             // prepare new response (for redirected request)
1916             _response = new HTTPResponse;
1917             _response.uri = current_uri;
1918             _response.finalURI = next_uri;
1919 
1920             _stream = null;
1921 
1922             // set new uri
1923             this._uri = next_uri;
1924             debug(requests) tracef("Redirected to %s", next_uri);
1925             if ( restartedRequest ) {
1926                 debug(requests) trace("Rare event: clearing 'restartedRequest' on redirect");
1927                 restartedRequest = false;
1928             }
1929             if ( _method == "GET")
1930             {
1931                 return exec_from_parameters();
1932             }
1933             goto connect;
1934         }
1935 
1936         _response._history = _history;
1937         return _response;
1938     }
1939 
1940     HTTPResponse exec_from_multipart_form(MultipartForm form) {
1941         import std.uuid;
1942         import std.file;
1943 
1944         _multipartForm = form;
1945 
1946         debug(requests) tracef("exec from multipart form");
1947 
1948         NetworkStream _stream;
1949         _response = new HTTPResponse;
1950         _response.uri = _uri;
1951         _response.finalURI = _uri;
1952         bool restartedRequest = false;
1953 
1954     connect:
1955         _contentReceived = 0;
1956         _response._startedAt = Clock.currTime;
1957 
1958         assert(_stream is null);
1959 
1960         _stream = _cm.get(_uri.scheme, _uri.host, _uri.port);
1961 
1962         if ( _stream is null ) {
1963             debug(requests) trace("create new connection");
1964             _stream = setupConnection();
1965         } else {
1966             debug(requests) trace("reuse old connection");
1967         }
1968 
1969         assert(_stream !is null);
1970 
1971         if ( !_stream.isConnected ) {
1972             debug(requests) trace("disconnected stream on enter");
1973             if ( !restartedRequest ) {
1974                 debug(requests) trace("disconnected stream on enter: retry");
1975                 assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
1976 
1977                 _cm.del(_uri.scheme, _uri.host, _uri.port);
1978                 _stream.close();
1979                 _stream = null;
1980 
1981                 restartedRequest = true;
1982                 goto connect;
1983             }
1984             debug(requests) trace("disconnected stream on enter: return response");
1985             //_stream = null;
1986             return _response;
1987         }
1988         _response._connectedAt = Clock.currTime;
1989 
1990         Appender!string req;
1991         req.put(requestString());
1992 
1993         string   boundary = randomUUID().toString;
1994         string[] partHeaders;
1995         size_t   contentLength;
1996 
1997         foreach(ref part; _multipartForm._sources) {
1998             string h = "--" ~ boundary ~ "\r\n";
1999             string disposition = `form-data; name="%s"`.format(part.name);
2000             string optionals = part.
2001             parameters.byKeyValue().
2002             filter!(p => p.key!="Content-Type").
2003             map!   (p => "%s=%s".format(p.key, p.value)).
2004             join("; ");
2005 
2006             h ~= `Content-Disposition: ` ~ [disposition, optionals].join("; ") ~ "\r\n";
2007 
2008             auto contentType = "Content-Type" in part.parameters;
2009             if ( contentType ) {
2010                 h ~= "Content-Type: " ~ *contentType ~ "\r\n";
2011             }
2012 
2013             h ~= "\r\n";
2014             partHeaders ~= h;
2015             contentLength += h.length + part.input.getSize() + "\r\n".length;
2016         }
2017         contentLength += "--".length + boundary.length + "--\r\n".length;
2018 
2019         auto h = requestHeaders();
2020         safeSetHeader(h, _userHeaders.ContentType, "Content-Type", "multipart/form-data; boundary=" ~ boundary);
2021         safeSetHeader(h, _userHeaders.ContentLength, "Content-Length", to!string(contentLength));
2022 
2023         h.byKeyValue.
2024         map!(kv => kv.key ~ ": " ~ kv.value ~ "\r\n").
2025         each!(h => req.put(h));
2026         req.put("\r\n");
2027 
2028         debug(requests) trace(req.data);
2029         if ( _verbosity >= 1 ) req.data.splitLines.each!(a => writeln("> " ~ a));
2030 
2031         try {
2032             _stream.send(req.data());
2033             foreach(ref source; _multipartForm._sources) {
2034                 debug(requests) tracef("sending part headers <%s>", partHeaders.front);
2035                 _stream.send(partHeaders.front);
2036                 partHeaders.popFront;
2037                 while (true) {
2038                     auto chunk = source.input.read();
2039                     if ( chunk.length <= 0 ) {
2040                         break;
2041                     }
2042                     _stream.send(chunk);
2043                 }
2044                 _stream.send("\r\n");
2045             }
2046             _stream.send("--" ~ boundary ~ "--\r\n");
2047             _response._requestSentAt = Clock.currTime;
2048             receiveResponse(_stream);
2049             _response._finishedAt = Clock.currTime;
2050         }
2051         catch (NetworkException e) {
2052             errorf("Error sending request: ", e.msg);
2053             _stream.close();
2054             return _response;
2055         }
2056 
2057         if ( serverPrematurelyClosedConnection()
2058         && !restartedRequest
2059         && isIdempotent(_method)
2060         ) {
2061             ///
2062             /// We didn't receive any data (keepalive connectioin closed?)
2063             /// and we can restart this request.
2064             /// Go ahead.
2065             ///
2066             debug(requests) tracef("Server closed keepalive connection");
2067 
2068             assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
2069 
2070             _cm.del(_uri.scheme, _uri.host, _uri.port);
2071             _stream.close();
2072             _stream = null;
2073 
2074             restartedRequest = true;
2075             goto connect;
2076         }
2077 
2078         if ( _useStreaming ) {
2079             if ( _response._receiveAsRange.activated ) {
2080                 debug(requests) trace("streaming_in activated");
2081                 return _response;
2082             } else {
2083                 // this can happen if whole response body received together with headers
2084                 _response._receiveAsRange.data = _response.responseBody.data;
2085             }
2086         }
2087 
2088         close_connection_if_not_keepalive(_stream);
2089 
2090         if ( _verbosity >= 1 ) {
2091             writeln(">> Connect time: ", _response._connectedAt - _response._startedAt);
2092             writeln(">> Request send time: ", _response._requestSentAt - _response._connectedAt);
2093             writeln(">> Response recv time: ", _response._finishedAt - _response._requestSentAt);
2094         }
2095 
2096         if ( willFollowRedirect ) {
2097             if ( _history.length >= _maxRedirects ) {
2098                 _stream = null;
2099                 throw new MaxRedirectsException("%d redirects reached maxRedirects %d.".format(_history.length, _maxRedirects));
2100             }
2101             // "location" in response already checked in canFollowRedirect
2102             immutable new_location = *("location" in _response.responseHeaders);
2103             immutable current_uri = _uri;
2104             immutable next_uri = uriFromLocation(_uri, new_location);
2105 
2106             immutable get_or_head = _method == "GET" || _method == "HEAD";
2107             immutable code = _response.code;
2108 
2109             // save current response for history
2110             _history ~= _response;
2111 
2112             if ( code == 301 )
2113             {
2114                 // permanent redirect and change method
2115                 _permanent_redirects[_uri] = new_location;
2116                 if ( !get_or_head )
2117                 {
2118                     _method = "GET";
2119                 }
2120             }
2121             if ( (code == 302 || code == 303) && !get_or_head)
2122             {
2123                 // only change method
2124                 _method = "GET";
2125             }
2126             if ( code == 307 )
2127             {
2128                 // no change method, no permanent
2129             }
2130             if ( code == 308 )
2131             {
2132                 // permanent redirection and do not change method
2133                 _permanent_redirects[_uri] = new_location;
2134             }
2135 
2136             // prepare new response (for redirected request)
2137             _response = new HTTPResponse;
2138             _response.uri = current_uri;
2139             _response.finalURI = next_uri;
2140             _stream = null;
2141 
2142             // set new uri
2143             this._uri = next_uri;
2144             debug(requests) tracef("Redirected to %s", next_uri);
2145             if ( restartedRequest ) {
2146                 debug(requests) trace("Rare event: clearing 'restartedRequest' on redirect");
2147                 restartedRequest = false;
2148             }
2149             if ( _method == "GET")
2150             {
2151                 return exec_from_parameters();
2152             }
2153             goto connect;
2154         }
2155 
2156         _response._history = _history;
2157         return _response;
2158     }
2159 
2160     HTTPResponse exec_from_parameters() {
2161 
2162         debug(requests) tracef("exec from parameters request");
2163 
2164         assert(_uri != URI.init);
2165         NetworkStream _stream;
2166         _response = new HTTPResponse;
2167         _history.length = 0;
2168         _response.uri = _uri;
2169         _response.finalURI = _uri;
2170         bool restartedRequest = false; // True if this is restarted keepAlive request
2171 
2172     connect:
2173         if ( _method == "GET" && _uri in _permanent_redirects ) {
2174             debug(requests) trace("use parmanent redirects cache");
2175             _uri = uriFromLocation(_uri, _permanent_redirects[_uri]);
2176             _response._finalURI = _uri;
2177         }
2178         _contentReceived = 0;
2179         _response._startedAt = Clock.currTime;
2180 
2181         assert(_stream is null);
2182 
2183         _stream = _cm.get(_uri.scheme, _uri.host, _uri.port);
2184 
2185         if ( _stream is null ) {
2186             debug(requests) trace("create new connection");
2187             _stream = setupConnection();
2188         } else {
2189             debug(requests) trace("reuse old connection");
2190         }
2191 
2192         assert(_stream !is null);
2193 
2194         if ( !_stream.isConnected ) {
2195             debug(requests) trace("disconnected stream on enter");
2196             if ( !restartedRequest ) {
2197                 debug(requests) trace("disconnected stream on enter: retry");
2198                 assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
2199 
2200                 _cm.del(_uri.scheme, _uri.host, _uri.port);
2201                 _stream.close();
2202                 _stream = null;
2203 
2204                 restartedRequest = true;
2205                 goto connect;
2206             }
2207             debug(requests) trace("disconnected stream on enter: return response");
2208             //_stream = null;
2209             return _response;
2210         }
2211         _response._connectedAt = Clock.currTime;
2212 
2213         auto h = requestHeaders();
2214 
2215         Appender!string req;
2216 
2217         string encoded;
2218 
2219         switch (_method) {
2220             case "POST","PUT","PATCH":
2221                 encoded = params2query(_params);
2222                 safeSetHeader(h, _userHeaders.ContentType, "Content-Type", "application/x-www-form-urlencoded");
2223                 safeSetHeader(h, _userHeaders.ContentLength, "Content-Length", to!string(encoded.length));
2224                 req.put(requestString());
2225                 break;
2226             default:
2227                 req.put(requestString(_params));
2228         }
2229 
2230         h.byKeyValue.
2231         map!(kv => kv.key ~ ": " ~ kv.value ~ "\r\n").
2232         each!(h => req.put(h));
2233         req.put("\r\n");
2234         if ( encoded ) {
2235             req.put(encoded);
2236         }
2237 
2238         debug(requests) trace(req.data);
2239         if ( _verbosity >= 1 ) {
2240             req.data.splitLines.each!(a => writeln("> " ~ a));
2241         }
2242         //
2243         // Now send request and receive response
2244         //
2245         try {
2246             _stream.send(req.data());
2247             _response._requestSentAt = Clock.currTime;
2248             debug(requests) trace("starting receive response");
2249             receiveResponse(_stream);
2250             debug(requests) tracef("done receive response");
2251             _response._finishedAt = Clock.currTime;
2252         }
2253         catch (NetworkException e) {
2254             // On SEND this can means:
2255             // we started to send request to the server, but it closed connection because of keepalive timeout.
2256             // We have to restart request if possible.
2257 
2258             // On RECEIVE - if we received something - then this exception is real and unexpected error.
2259             // If we didn't receive anything - we can restart request again as it can be
2260             debug(requests) tracef("Exception on receive response: %s", e.msg);
2261             if ( _response._responseHeaders.length != 0 )
2262             {
2263                 _stream.close();
2264                 throw new RequestException("Unexpected network error");
2265             }
2266         }
2267 
2268         if ( serverPrematurelyClosedConnection()
2269             && !restartedRequest
2270             && isIdempotent(_method)
2271             ) {
2272             ///
2273             /// We didn't receive any data (keepalive connectioin closed?)
2274             /// and we can restart this request.
2275             /// Go ahead.
2276             ///
2277             debug(requests) tracef("Server closed keepalive connection");
2278 
2279             assert(_cm.get(_uri.scheme, _uri.host, _uri.port) == _stream);
2280 
2281             _cm.del(_uri.scheme, _uri.host, _uri.port);
2282             _stream.close();
2283             _stream = null;
2284 
2285             restartedRequest = true;
2286             goto connect;
2287         }
2288 
2289         if ( _useStreaming ) {
2290             if ( _response._receiveAsRange.activated ) {
2291                 debug(requests) trace("streaming_in activated");
2292                 return _response;
2293             } else {
2294                 // this can happen if whole response body received together with headers
2295                 _response._receiveAsRange.data = _response.responseBody.data;
2296             }
2297         }
2298 
2299         close_connection_if_not_keepalive(_stream);
2300 
2301         if ( _verbosity >= 1 ) {
2302             writeln(">> Connect time: ", _response._connectedAt - _response._startedAt);
2303             writeln(">> Request send time: ", _response._requestSentAt - _response._connectedAt);
2304             writeln(">> Response recv time: ", _response._finishedAt - _response._requestSentAt);
2305         }
2306 
2307         if ( willFollowRedirect ) {
2308             debug(requests) trace("going to follow redirect");
2309             if ( _history.length >= _maxRedirects ) {
2310                 _stream = null;
2311                 throw new MaxRedirectsException("%d redirects reached maxRedirects %d.".format(_history.length, _maxRedirects));
2312             }
2313             // "location" in response already checked in canFollowRedirect
2314             immutable new_location = *("location" in _response.responseHeaders);
2315             immutable current_uri = _uri;
2316             immutable next_uri = uriFromLocation(_uri, new_location);
2317 
2318             immutable get_or_head = _method == "GET" || _method == "HEAD";
2319             immutable code = _response.code;
2320 
2321             // save current response for history
2322             _history ~= _response;
2323 
2324             if ( code == 301 )
2325             {
2326                 // permanent redirect and change method
2327                 _permanent_redirects[_uri] = new_location;
2328                 if ( !get_or_head )
2329                 {
2330                     _method = "GET";
2331                 }
2332             }
2333             if ( (code == 302 || code == 303) && !get_or_head)
2334             {
2335                 // only change method
2336                 _method = "GET";
2337             }
2338             if ( code == 307 )
2339             {
2340                 // no change method, no permanent
2341             }
2342             if ( code == 308 )
2343             {
2344                 // permanent redirection and do not change method
2345                 _permanent_redirects[_uri] = new_location;
2346             }
2347 
2348             // prepare new response (for redirected request)
2349             _response = new HTTPResponse;
2350             _response.uri = current_uri;
2351             _response.finalURI = next_uri;
2352             _stream = null;
2353 
2354             // set new uri
2355             _uri = next_uri;
2356             debug(requests) tracef("Redirected to %s", next_uri);
2357             //if ( _method != "GET" && _response.code != 307 && _response.code != 308 ) {
2358             //    // 307 and 308 do not change method
2359             //    return exec_from_parameters(r);
2360             //}
2361             if ( restartedRequest ) {
2362                 debug(requests) trace("Rare event: clearing 'restartedRequest' on redirect");
2363                 restartedRequest = false;
2364             }
2365             goto connect;
2366         }
2367 
2368         _response._history = _history;
2369         return _response;
2370     }
2371     HTTPResponse execute(Request r)
2372     {
2373         _method = r.method;
2374         _uri = r.uri;
2375         _useStreaming = r.useStreaming;
2376         _permanent_redirects = r.permanent_redirects;
2377         _maxRedirects = r.maxRedirects;
2378         _authenticator = r.authenticator;
2379         _maxHeadersLength = r.maxHeadersLength;
2380         _maxContentLength = r.maxContentLength;
2381         _verbosity = r.verbosity;
2382         _keepAlive = r.keepAlive;
2383         _bufferSize = r.bufferSize;
2384         _proxy = r.proxy;
2385         _timeout = r.timeout;
2386         _contentType = r.contentType;
2387         _socketFactory = r.socketFactory;
2388         _sslOptions = r.sslOptions;
2389         _bind = r.bind;
2390         _headers = r.headers;
2391         _userHeaders = r.userHeaders;
2392 
2393         _params = r.params;
2394 
2395         // this assignments increments refCounts, so we can't use const Request
2396         // but Request is anyway struct and called by-value
2397         _cm = r.cm;
2398         _cookie = r.cookie;
2399 
2400         debug(requests) trace("serving %s".format(r));
2401         if ( !r.postData.empty)
2402         {
2403             return exec_from_range(r.postData);
2404         }
2405         if ( r.hasMultipartForm )
2406         {
2407             return exec_from_multipart_form(r.multipartForm);
2408         }
2409         auto rs = exec_from_parameters();
2410         return rs;
2411     }
2412 }
2413 
2414 version(vibeD) {
2415     import std.json;
2416     package string httpTestServer() {
2417         return "http://httpbin.org/";
2418     }
2419     package string fromJsonArrayToStr(JSONValue v) {
2420         return v.str;
2421     }
2422 }
2423 else {
2424     import std.json;
2425     package string httpTestServer() {
2426         return "http://127.0.0.1:8081/";
2427     }
2428     package string fromJsonArrayToStr(JSONValue v) {
2429         return cast(string)(v.array.map!"cast(ubyte)a.integer".array);
2430     }
2431 }