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