1 module heaploop.networking.http;
2 import heaploop.networking.tcp;
3 import heaploop.networking.dns;
4 import heaploop.looping;
5 import heaploop.streams;
6 import events;
7 import std.string : format, translate;
8 import std.array : split, appender, replace;
9 import std.uri : decodeComponent, encodeComponent;
10 import http.parser.core;
11 
12 debug {
13     import std.stdio : writeln;
14 }
15 
16 /*
17  * HTTP Common
18  */
19 
20 class NetworkCredential {
21     import std.base64;
22 
23     private string _userName, _password;
24     public:
25         this(string userName = null, string password = null) {
26             _userName = userName;
27             _password = password;
28         }
29         this(Uri uri) {
30            if(uri.userInfo) {
31                auto pieces = uri.userInfo.split(":");
32                auto len = pieces.length;
33                if(pieces.length > 0) {
34                    this(pieces[0], pieces[1]);
35                    return;
36                }
37            } 
38            this();
39         }
40         @property {
41             string userName() {
42                 return _userName;
43             }
44             void userName(string userName) {
45                 _userName = userName;
46             }
47             string password() {
48                 return _password;
49             }
50             void password(string password) {
51                 _password = password;
52             }
53             string authorizationHeader() {
54                 return "Basic " ~ cast(string)Base64.encode(cast(ubyte[])(_userName ~ ":" ~ _password));
55             }
56         }
57 }
58 
59 enum HttpParserEventType : ubyte {
60     Unknown,
61     MessageBegin,
62     Url,
63     Header,
64     HeadersComplete,
65     StatusComplete,
66     Body,
67     MessageComplete
68 }
69 
70 struct HttpParserEvent {
71         union Store {
72              string str; 
73              HttpBodyChunk chunk;
74              HttpHeader header;
75         }
76 
77         Store store;
78         HttpParserEventType type;
79 }
80 
81 abstract class HttpConnectionBase : Looper {
82     private:
83         TcpStream _stream;
84         HttpParser _parser;
85 
86         HttpIncomingMessage _currentMessage;
87 
88         HttpParserEvent[] _parserEvents;
89 
90         void _onMessageBegin(HttpParser p) {
91             debug std.stdio.writeln("HTTP message began");
92             HttpParserEvent ev;
93             ev.type = HttpParserEventType.MessageBegin;
94             _parserEvents ~= ev;
95         }
96 
97         void onUrl(HttpParser p, string uri) {
98             HttpParserEvent ev;
99             ev.type = HttpParserEventType.Url;
100             ev.store.str = _currentMessage.rawUri;
101             _parserEvents ~= ev;
102         }
103 
104         void onHeadersComplete(HttpParser p) {
105             HttpParserEvent ev;
106             ev.type = HttpParserEventType.HeadersComplete;
107             _parserEvents ~= ev;
108         }
109 
110         void _onStatusComplete(HttpParser parser, string statusLine) {
111             HttpParserEvent ev;
112             ev.type = HttpParserEventType.StatusComplete;
113             ev.store.str = statusLine;
114             _parserEvents ~= ev;
115         }
116 
117         void onBody(HttpParser parser, HttpBodyChunk chunk) {
118             HttpParserEvent ev;
119             ev.type = HttpParserEventType.Body;
120             ev.store.chunk = chunk;
121             _parserEvents ~= ev;
122         }
123 
124         void onHeader(HttpParser p, HttpHeader header) {
125             HttpParserEvent ev;
126             ev.type = HttpParserEventType.Header;
127             ev.store.header = header;
128             _parserEvents ~= ev;
129         }
130 
131         void onMessageComplete(HttpParser p) {
132         }
133 
134         void _stopProcessing() {
135             if(_stream is null) return;
136             debug std.stdio.writeln("_Stopping connection, closing stream");
137             _stream.stopReading();
138             _stream.close();
139             _stream = null;
140             _parser = null;
141             debug std.stdio.writeln("_Stopping connection, OK");
142         }
143         ~this() {
144            debug std.stdio.writeln("Collecting HttpConnection");
145         }
146 
147 package:
148         void write(ubyte[] data) {
149             // TODO: make sure we are not in read mode
150             _stream.write(data);
151         }
152 
153     protected:
154         final {
155             @property {
156                 HttpIncomingMessage currentMessage() nothrow pure {
157                     return _currentMessage;
158                 }
159 
160                 HttpParser parser() nothrow pure {
161                     return _parser;
162                 }
163             }
164         }
165 
166         abstract HttpIncomingMessage createMessage();
167 
168         abstract void onMessageBegin();
169 
170         abstract void onBeforeProcess();
171         
172         abstract void onProcessMessage();
173         
174         void onStatusComplete(string statusLine, uint statusCode) {
175 
176         }
177 
178         bool _linkProcessing(string requester)() {
179             bool isNewMessage = false;
180             try {
181                 debug std.stdio.writeln(requester ~ " Linked to reading to Process HTTP Requests");
182                 if(!_stream.isOpen) {
183                     return false;
184                 }
185                 ubyte[] data = _stream.readOnce();
186                 if(data.length == 0) {
187                     return false;
188                 }
189                 debug std.stdio.writeln("Readed bytes ", data.length);
190                 _parserEvents = null;
191                 _parser.execute(data);
192                 foreach(ref HttpParserEvent ev; _parserEvents) {
193                     switch(ev.type) {
194                         case HttpParserEventType.MessageBegin: 
195                             _currentMessage = createMessage();
196                             onMessageBegin();
197                             break;
198                         case HttpParserEventType.Url:
199                             _currentMessage.rawUri = ev.store.str;
200                             _currentMessage.uri = Uri(_currentMessage.rawUri);
201                             break;
202                         case HttpParserEventType.Header:
203                             _currentMessage.addHeader(ev.store.header);
204                             break;
205                         case HttpParserEventType.HeadersComplete:
206                             _currentMessage.protocolVersion = _parser.protocolVersion;
207                             _currentMessage.transmissionMode = _parser.transmissionMode;
208                             debug std.stdio.writeln("protocol version set, ", _currentMessage.protocolVersion.toString);
209                             isNewMessage = true;
210                             break;
211                         case HttpParserEventType.StatusComplete:
212                             onStatusComplete(ev.store.str, _parser.statusCode);
213                             break;
214                         case HttpParserEventType.Body:
215                             auto chunk = ev.store.chunk;
216                             debug writeln("HTTP Response Message: read some BODY data (", chunk.buffer.length, " bytes) ", " is final ", chunk.isFinal, ": ", chunk.buffer);
217                             debug writeln("onBody ", cast(string)chunk.buffer);
218                             if(_currentMessage._isProcessingLinked) {
219                                 // trigger the body read action directly
220                                 _currentMessage._readTrigger(chunk);
221                                 if(chunk.isFinal) {
222                                     _currentMessage._isReadingComplete = true;
223                                 }
224                                 debug writeln("Chunk delivered directly");
225                             } else {
226                                 // it's the first buffer, save it and unlink processing
227                                 _currentMessage._bufferedChunk = chunk;
228                                 debug writeln("Chunk buffered until the incoming message reads");
229                             }
230                             break;
231                     }
232                 }
233             } catch(LoopException lex) {
234                 if(lex.name == "EOF")  {
235                     debug std.stdio.writeln("Connection closed");
236                 } else {
237                     throw lex;
238                 }
239             }
240             if(isNewMessage) {
241                 onBeforeProcess();
242                 // read
243                 onProcessMessage();
244             }
245 
246             static if(requester != "HttpIncomingMessage") {
247                 debug std.stdio.writeln(requester ~ " HTTP link looping");
248                 _linkProcessing!requester();
249             } else {
250                 debug std.stdio.writeln(requester ~ " HTTP link completed");
251             }
252             return true;
253         }
254 
255     public:
256         this(TcpStream stream, HttpParserType parserType) {
257             _stream = stream;
258             _parser = new HttpParser(parserType);
259             _parser.onMessageBegin = &_onMessageBegin;
260             _parser.onHeader = &onHeader;
261             _parser.onHeadersComplete = &onHeadersComplete;
262             _parser.onMessageComplete = &onMessageComplete;
263             _parser.onStatusComplete = &_onStatusComplete;
264             _parser.onUrl = &onUrl;
265             _parser.onBody = &onBody;
266         }
267 
268 
269         void stop() {
270            debug std.stdio.writeln("Stopping connection");
271            _stopProcessing();
272         }
273 
274         @property {
275             Loop loop() nothrow pure {
276                 return _stream.loop;
277             }
278         }
279 }
280 
281 abstract class HttpConnection(TIncomingMessage : HttpIncomingMessage) : HttpConnectionBase {
282     protected:
283         abstract TIncomingMessage createIncomingMessage();
284 
285         override HttpIncomingMessage createMessage() {
286             return createIncomingMessage();
287         }
288 
289         @property TIncomingMessage currentMessage() {
290             return cast(TIncomingMessage)super.currentMessage;
291         }
292         alias HttpConnectionBase.currentMessage currentMessage;
293     public:
294 
295         this(TcpStream stream, HttpParserType parserType) {
296             super(stream, parserType);
297         }
298 
299 }
300 
301 abstract class HttpMessage : Looper {
302     private:
303         Loop _loop;
304         HttpHeader[] _headers;
305         HttpVersion _version;
306         string _contentType;
307 
308     package:
309         this(Loop loop) 
310             in {
311                 assert(loop !is null, "loop is required to create HttpMessage");
312             }
313             body {
314                 _loop = loop;
315             }
316     public:
317 
318         @property {
319             Loop loop() nothrow pure {
320                 return _loop;
321             }
322 
323             HttpHeader[] headers() nothrow pure {
324                 return _headers;
325             }
326 
327             string contentType() {
328                 return _contentType;
329             }
330         }
331 
332         void addHeader(HttpHeader header) {
333             _headers ~= header;
334             if(header.name == "Content-Type") {
335                 _contentType = header.value;
336             }
337         }
338 
339         void protocolVersion(in HttpVersion v) {
340             _version = v;
341         }
342 
343         HttpVersion protocolVersion() {
344             return _version;
345         }
346 }
347 
348 abstract class HttpIncomingMessage : HttpMessage
349 {
350     private:
351         string _rawUri;
352         Uri _uri;
353         HttpConnectionBase _connection;
354         HttpBodyTransmissionMode _transmissionMode;
355     package:
356 
357         // first chunk of the body buffer
358         HttpBodyChunk _bufferedChunk;
359         bool _isProcessingLinked;
360         bool _isReadingComplete;
361 
362         @property {
363             void transmissionMode(HttpBodyTransmissionMode mode) {
364                 _transmissionMode = mode;
365             }
366         }
367 
368         void delegate(HttpBodyChunk) _readTrigger;
369 
370 
371     public:
372         this(HttpConnectionBase connection)
373             in {
374                 assert(connection !is null);
375             }
376             body {
377                 super(connection.loop);
378                 this._connection = connection;
379             }
380 
381             Action!(void, HttpBodyChunk) read() {
382                 return new Action!(void, HttpBodyChunk)((a) {
383                    _readTrigger = a;
384                    // if there is a chunk buffered, deliver it
385                    if(this._bufferedChunk.buffer.length > 0) {
386                         a(this._bufferedChunk);
387                         this._bufferedChunk = HttpBodyChunk.init;
388                    }
389                    _isProcessingLinked = true;
390                    while(this.shouldRead && !this._isReadingComplete) {
391                        // process connection messages here
392                        // so it blocks while reading
393                        bool shouldContinueReading = this._connection._linkProcessing!"HttpIncomingMessage"();
394                        if(!shouldContinueReading){
395                             break;
396                        }
397                    }
398                    _isProcessingLinked = false;
399                 });
400             }
401         @property {
402 
403             string rawUri() {
404                 return _rawUri;
405             }
406 
407             void rawUri(string uri) {
408                 _rawUri = uri;
409             }
410 
411             Uri uri() {
412                 return _uri;
413             }
414 
415             void uri(Uri uri) {
416                 _uri = uri;
417             }
418 
419             bool shouldRead() {
420                 writeln("Transmission Mode ", this.transmissionMode, " should read ", this.transmissionMode.shouldRead);
421                 return this.transmissionMode.shouldRead;
422             }
423 
424             HttpBodyTransmissionMode transmissionMode() nothrow pure {
425                 return _transmissionMode;
426             }
427 
428         }
429 }
430 
431 /*
432  * HTTP Server
433  */
434 
435 class HttpContext {
436 private:
437     HttpRequest _request;
438     HttpResponse _response;
439 
440     this(HttpRequest request, HttpResponse response) {
441         _request = request;
442         _response = response;
443         _request.context = this;
444         _response.context = this;
445     }
446 
447     public:
448     @property {
449         HttpRequest request() {
450             return _request;
451         }
452         HttpResponse response() {
453             return _response;
454         }
455     }
456 }
457 
458 class HttpRequest : HttpIncomingMessage {
459     private:
460         string _method;
461         HttpContext _context;
462 
463     public:
464 
465         this(HttpServerConnection connection) {
466             super(connection);
467         }
468 
469         @property {
470             string method() {
471                 return _method;
472             }
473             void method(string m) {
474                 _method = m;
475             }
476         }
477 
478         @property {
479             HttpContext context() {
480                 return _context;
481             }
482             package void context(HttpContext context) {
483                 _context = context;
484             }
485         }
486 }
487 
488 class HttpResponse {
489     private:
490         HttpServerConnection _connection;
491         HttpHeader[] _headers;
492         bool _headersSent;
493         uint _statusCode;
494         string _statusText;
495         string _contentType;
496         HttpContext _context;
497         bool _chunked;
498         ubyte[] _bufferedWrites;
499 
500         void lineWrite(string data = "") {
501             _connection.write(cast(ubyte[])(data ~ "\r\n"));
502         }
503 
504         void _ensureHeadersSent() {
505             if(!headersSent) {
506                 lineWrite("HTTP/%s %d %s".format(_context.request.protocolVersion.toString, _statusCode, _statusText));
507                 foreach(header; _headers) {
508                     lineWrite(header.name ~ " : " ~ header.value);
509                 }
510                 lineWrite("Content-Type: %s".format(_contentType));
511                 if(_chunked) {
512                     lineWrite("Transfer-Encoding: chunked");
513                 } else {
514                     lineWrite("Content-Length: %d".format(_bufferedWrites.length));
515                 }
516                 //lineWrite("Connection: close");
517                 lineWrite();
518                 _headersSent = true;
519             }
520         }
521 
522     package:
523         void _init() {
524             auto ver = _context.request.protocolVersion;
525             bool is1_0 = ver.major == 1 && ver.minor == 0;
526             _chunked = !is1_0;
527         }
528 
529     public:
530         this(HttpServerConnection connection) {
531             _connection = connection;
532             this.statusCode = 200;
533             this.contentType = "text/plain";
534         }
535 
536         @property bool headersSent() {
537             return _headersSent;
538         }
539 
540         @property {
541             uint statusCode() {
542                 return _statusCode;
543             }
544             void statusCode(uint statusCode) {
545                 _statusCode = statusCode;
546                 switch(statusCode) {
547                     case 200:
548                         _statusText = "OK";
549                         break;
550                     case 201:
551                         _statusText = "CREATED";
552                         break;
553                     case 422:
554                         _statusText = "UNPROCESSABLE ENTITY";
555                         break;
556                     case 500:
557                         _statusText = "INTERNAL SERVER ERROR";
558                         break;
559                     default:
560                         throw new Exception(std..string.format("Unknown HTTP status code %s... pull request time?", _statusCode));
561                 }
562             }
563         }
564 
565         @property {
566             string contentType() {
567                 return _contentType;
568             }
569 
570             void contentType(string contentType) {
571                 _contentType = contentType;
572             }
573         }
574 
575         @property {
576             HttpContext context() {
577                 return _context;
578             }
579             package void context(HttpContext context) {
580                 _context = context;
581             }
582         }
583 
584         void write(ubyte[] data) {
585             if(_chunked) {
586                 _ensureHeadersSent();
587                 _connection.write((cast(ubyte[])format("%x\r\n", data.length)));
588                 _connection.write(data ~ cast(ubyte[])"\r\n");
589             } else {
590                 _bufferedWrites ~= data;
591             }
592         }
593 
594         void write(string data) {
595             write(cast(ubyte[])data);
596         }
597 
598         void end() {
599             debug std.stdio.writeln("Ending");
600             _ensureHeadersSent();
601             if(_chunked) {
602                 write(cast(ubyte[])[]);
603             } else {
604                 _connection.write(_bufferedWrites);
605                 debug std.stdio.writeln("Closing");
606                 //close();
607                 debug std.stdio.writeln("...Closed");
608             }
609             debug std.stdio.writeln("...Ended");
610         }
611 
612         void close() {
613             _connection.stop();
614         }
615 
616         void addHeader(string name, string value) {
617             addHeader(HttpHeader(name, value));
618         }
619 
620         void addHeader(HttpHeader header) {
621             if(_headersSent) {
622                 throw new Exception("HTTP headers already sent. HttpResponse.addHeader can not be used after HttpResponse.end");
623             }
624             _headers ~= header;
625         }
626 }
627 
628 class HttpServerConnection : HttpConnection!HttpRequest {
629     alias Action!(void, HttpRequest, HttpResponse) processEventList;
630 
631 
632     private:
633         HttpResponse _currentResponse;
634         HttpContext _currentContext;
635         void delegate(HttpRequest, HttpResponse) _processCallback;
636         processEventList _processAction;
637     
638     protected:
639          override HttpRequest createIncomingMessage() {
640              return new HttpRequest(this);
641          }
642 
643          override void onMessageBegin() {
644             _currentResponse = new HttpResponse(this);
645             _currentContext = new HttpContext(this.currentMessage, _currentResponse);
646          }
647 
648          override void onBeforeProcess() {
649             currentMessage.method = this.parser.method;
650             _currentResponse._init();
651          }
652         
653          override void onProcessMessage() {
654             _processCallback(currentMessage, _currentResponse);
655          }
656     
657     public:
658         this(TcpStream stream) {
659             _stream = stream;
660             super(stream, HttpParserType.REQUEST);
661         }
662         
663         processEventList process() {
664             if(_processAction is null) {
665                 _processAction = new processEventList((trigger) {
666                     _processCallback = trigger;
667                     _linkProcessing!"HttpServerConnection";
668                 });
669             }
670             return _processAction;
671         }
672 }
673 
674 class HttpListener
675 {
676     private:
677         TcpStream _server;
678 
679     protected:
680         HttpServerConnection createConnection(TcpStream stream) {
681             return new HttpServerConnection(stream);
682         }
683 
684     public:
685         this() {
686             _server = new TcpStream;
687         }
688 
689         TThis bind4(this TThis)(string address, int port) {
690             _server.bind4(address, port);
691             return cast(TThis)this;
692         }
693 
694         Action!(void, HttpServerConnection) listen(int backlog = 50000) {
695             return new Action!(void, HttpServerConnection)((trigger) {
696                 _server.listen(backlog) ^= (client) {
697                     auto connection = this.createConnection(client);
698                     trigger(connection);
699                 };
700             });
701         }
702 
703 }
704 
705 alias string[string] FormFields;
706 
707 private:
708 
709 static dchar[dchar] FormDecodingTranslation;
710 
711 static this() {
712     FormDecodingTranslation = ['+' : ' '];
713 }
714 
715 string decodeFormComponent(string component) {
716     return component.translate(FormDecodingTranslation);
717 }
718 
719 string encodeFormComponent(string component) {
720     return component.replace("%20", "+");
721 }
722 
723 public:
724 
725 FormFields parseURLEncodedForm(string content) {
726     if(content.length < 1) return null;
727     FormFields fields;
728     string[] pairs = content.split("&");
729     foreach(entry; pairs) {
730         string[] values = entry.split("=");
731         string name = values[0].decodeComponent.decodeFormComponent;
732         string value = values[1];
733         fields[name] = value.decodeComponent.decodeFormComponent;
734     }
735     return fields;
736 }
737 
738 string encodeURLForm(FormFields fields) {
739     if(fields.length < 1) return null;
740     auto text = appender!string;
741     foreach(i, name;fields.keys) {
742         if(i != 0) {
743             text.put("&");
744         }
745         text.put(name.encodeComponent.encodeFormComponent);
746         text.put("=");
747         text.put(fields[name].encodeComponent.encodeFormComponent);
748     }
749     string txt = text.data;
750     return txt;
751 }
752 
753 /*
754  * HTTP Client
755  */
756 
757 ushort inferPortForUriSchema(string schema) {
758 
759     switch(schema) {
760         case "http": return 80;
761         default: assert(0, "Unable to infer port number for schema %s".format(schema));
762     }
763 }
764 
765 class HttpRequestMessage
766 {
767     private:
768         string _method;
769         HttpVersion _version;
770         Uri _uri;
771         HttpContent _content;
772         NetworkCredential _credentials;
773 
774     public:
775         @property {
776 
777             void method(in string m) {
778                 _method = m;
779             }
780 
781             string method() {
782                 return _method;
783             }
784 
785             void protocolVersion(in HttpVersion v) {
786                 _version = v;
787             }
788 
789             HttpVersion protocolVersion() {
790                 return _version;
791             }
792 
793             Uri uri() {
794                 return _uri;
795             }
796 
797             void uri(Uri uri) {
798                 _uri = uri;
799             }
800 
801             HttpContent content() {
802                 return _content;
803             }
804             void content(HttpContent content) {
805                 _content = content;
806             }
807 
808             NetworkCredential credentials() {
809                 return _credentials;
810             }
811             void credentials(NetworkCredential credentials) {
812                 _credentials = credentials;
813             }
814         }
815 
816         void send(TcpStream stream) {
817             string path = null;
818             if(_uri.query !is null) {
819                 path = "%s?%s".format(_uri.path, _uri.query);
820             } else {
821                 path = _uri.path;
822             }
823             auto writeHeader = delegate void(string s) {
824                 stream.write(cast(ubyte[])(s ~ "\r\n"));
825             };
826             writeHeader("%s %s HTTP/%s".format(_method, path, _version.toString));
827             writeHeader("Host: %s".format(_uri.host));
828             if(_credentials) {
829                 writeHeader("Authorization: %s".format(_credentials.authorizationHeader));
830             }
831 
832             ubyte[] entity;
833             if(this.content !is null) {
834                 this.content.writeTo(delegate void(ubyte[] d) {
835                     entity ~= d;
836                 });
837                 writeHeader("Content-Length: %d".format(entity.length));
838                 writeHeader("Content-Type: %s".format(this.content.contentType));
839             }
840             writeHeader("");
841             if(entity.length > 0) {
842                 stream.write(entity);
843             }
844             debug writeln("headers send");
845         }
846 }
847 
848 class HttpResponseMessage : HttpIncomingMessage
849 {
850     private:
851         uint _statusCode;
852 
853     public:
854         this(HttpClientConnection connection)
855         {
856             super(connection);
857         }
858 
859         @property {
860             uint statusCode() {
861                 return _statusCode;
862             }
863 
864             void statusCode(uint code) {
865                 _statusCode = code;
866             }
867         }
868 }
869 
870 class HttpClientConnection : HttpConnection!HttpResponseMessage {
871     alias Action!(void, HttpResponseMessage) responseAction;
872 
873 
874     private:
875         HttpResponse _currentResponse;
876         HttpContext _currentContext;
877         void delegate(HttpResponseMessage) _responseCallback;
878         responseAction _responseAction;
879     
880     protected:
881          override HttpResponseMessage createIncomingMessage() {
882              return new HttpResponseMessage(this);
883          }
884 
885          override void onMessageBegin() {}
886 
887          override void onBeforeProcess() {}
888         
889          override void onProcessMessage() {
890             _responseCallback(currentMessage);
891          }
892 
893          override void onStatusComplete(string statusLine, uint statusCode) {
894             currentMessage.statusCode = statusCode; 
895          }
896     
897     public:
898         this(TcpStream stream) {
899             super(stream, HttpParserType.RESPONSE);
900         }
901         
902         responseAction response() {
903             if(_responseAction is null) {
904                 _responseAction = new responseAction((trigger) {
905                     _responseCallback = trigger;
906                     _linkProcessing!"HttpClientConnection"();
907                 });
908             }
909             return _responseAction;
910         }
911 }
912 
913 abstract class HttpContent {
914     public:
915         abstract void writeTo(void delegate(ubyte[] data) writer);
916 
917         @property abstract string contentType();
918 }
919 
920 class UbyteContent: HttpContent {
921     private:
922         ubyte[] _buffer;
923 
924     public:
925         this(ubyte[] buffer) {
926             _buffer = buffer;
927         }
928 
929         override void writeTo(void delegate(ubyte[] data) writer) {
930             if(_buffer !is null) {
931                 writer(_buffer);
932             }
933         }
934 
935         @property override string contentType() {
936             return "application/octet-stream";
937         }
938 }
939 
940 class FormUrlEncodedContent : UbyteContent {
941     public:
942         this(string[string] fields) {
943             super(cast(ubyte[])encodeURLForm(fields));
944         }
945         @property override string contentType() {
946             return "application/x-www-form-urlencoded";
947         }
948 }
949 
950 class HttpClient 
951 {
952     private:
953         Uri _rootUri;
954         NetworkCredential _credentials;
955     public:
956         this(string rootUri) {
957             this(Uri(rootUri));
958         }
959 
960         this(Uri rootUri) {
961             _rootUri = rootUri;
962             if(rootUri.userInfo) {
963                 _credentials = new NetworkCredential(rootUri);
964             }
965         }
966 
967         @property {
968             Uri rootUri() nothrow pure {
969                 return _rootUri;
970             }
971 
972             NetworkCredential credentials() {
973                 return _credentials;
974             }
975         }
976 
977         StrictAction!(StrictTrigger.Sync, void, HttpResponseMessage) send(string method, string path, HttpContent content = null) {
978             return new StrictAction!(StrictTrigger.Sync, void, HttpResponseMessage)((trigger) {
979                 Uri uri = Uri(_rootUri.toString ~ path);
980                 ushort port = uri.port;
981                 if(port == 0) {
982                     port = inferPortForUriSchema(uri.schema);
983                 }
984                 TcpStream stream = new TcpStream;
985                 debug writeln("HOST ", uri.host, port);
986                 stream.connect(uri.host, port);
987                 auto request = new HttpRequestMessage;
988                 request.credentials = _credentials;
989                 request.method = method;
990                 request.uri = uri;
991                 request.protocolVersion = HttpVersion(1,0);
992                 request.content = content;
993                 request.send(stream);
994                 auto connection = new HttpClientConnection(stream);
995                 HttpResponseMessage response;
996                 connection.response ^= (r) {
997                     trigger(r);
998                 };
999             });
1000         }
1001 
1002         StrictAction!(StrictTrigger.Sync, void, HttpResponseMessage) post(string path, HttpContent content = null) {
1003             return send("POST", path, content);
1004         }
1005 
1006         StrictAction!(StrictTrigger.Sync, void, HttpResponseMessage) post(string path, string[string] fields) {
1007             return post(path, new FormUrlEncodedContent(fields));
1008         }
1009 
1010         StrictAction!(StrictTrigger.Sync, void, HttpResponseMessage) put(string path, HttpContent content = null) {
1011             return send("PUT", path, content);
1012         }
1013 
1014         StrictAction!(StrictTrigger.Sync, void, HttpResponseMessage) get(string path) {
1015             return new StrictAction!(StrictTrigger.Sync, void, HttpResponseMessage)((trigger) {
1016                 Uri uri = Uri(_rootUri.toString ~ path);
1017                 ushort port = uri.port;
1018                 if(port == 0) {
1019                     port = inferPortForUriSchema(uri.schema);
1020                 }
1021                 TcpStream stream = new TcpStream;
1022                 debug writeln("HOST ", uri.host, port);
1023                 stream.connect(uri.host, port);
1024                 auto request = new HttpRequestMessage;
1025                 request.credentials = _credentials;
1026                 request.method = "GET";
1027                 request.uri = uri;
1028                 request.protocolVersion = HttpVersion(1,0);
1029                 request.send(stream);
1030                 auto connection = new HttpClientConnection(stream);
1031                 HttpResponseMessage response;
1032                 connection.response ^= (r) {
1033                     trigger(r);
1034                     //connection.stop;
1035                 };
1036             });
1037         }
1038 }
1039