1 module lighttp.client.client; 2 3 import std.conv : to, ConvException; 4 5 import libasync; 6 7 import lighttp.util; 8 9 import xbuffer : Buffer; 10 11 struct ClientOptions { 12 13 bool closeOnSuccess = false; 14 15 bool closeOnFailure = false; 16 17 //bool followRedirect = false; 18 19 } 20 21 class Client { 22 23 private EventLoop _eventLoop; 24 private ClientOptions _options; 25 26 this(EventLoop eventLoop, ClientOptions options=ClientOptions.init) { 27 _eventLoop = eventLoop; 28 _options = options; 29 } 30 31 this(ClientOptions options=ClientOptions.init) { 32 this(getThreadEventLoop(), options); 33 } 34 35 @property ClientOptions options() pure nothrow @safe @nogc { 36 return _options; 37 } 38 39 @property EventLoop eventLoop() pure nothrow @safe @nogc { 40 return _eventLoop; 41 } 42 43 ClientConnection connect(string ip, ushort port=80) { 44 return new ClientConnection(new AsyncTCPConnection(_eventLoop), ip, port); 45 } 46 47 class ClientConnection { 48 49 private AsyncTCPConnection _connection; 50 51 private bool _connected = false; 52 private bool _performing = false; 53 private bool _successful; 54 55 private immutable string host; 56 57 private Buffer _buffer; 58 private size_t _contentLength; 59 private void delegate() _handler; 60 61 private ClientResponse _response; 62 63 private void delegate(ClientResponse) _success; 64 private void delegate() _failure; 65 66 this(AsyncTCPConnection connection, string ip, ushort port) { 67 _buffer = new Buffer(4096); 68 _handler = &this.handleFirst; 69 _success = (ClientResponse response){}; 70 _failure = {}; 71 _connection = connection; 72 _connection.host(ip, port); 73 _connection.run(&this.handler); 74 this.host = ip ~ (port != 80 ? ":" ~ to!string(port) : ""); 75 } 76 77 auto perform(ClientRequest request) { 78 assert(!_performing); 79 _performing = true; 80 _successful = false; 81 request.headers["Host"] = this.host; 82 if(_connected) { 83 _connection.send(cast(ubyte[])request.toString()); 84 } else { 85 _buffer.reset(); 86 _buffer.write(request.toString()); 87 } 88 return this; 89 } 90 91 auto get(string path) { 92 return this.perform(new ClientRequest("GET", path)); 93 } 94 95 auto post(string path, string body_) { 96 return this.perform(new ClientRequest("POST", path, body_)); 97 } 98 99 auto success(void delegate(ClientResponse) callback) { 100 _success = callback; 101 return this; 102 } 103 104 auto failure(void delegate() callback) { 105 _failure = callback; 106 return this; 107 } 108 109 bool close() { 110 return _connection.kill(true); 111 } 112 113 private void handler(TCPEvent event) { 114 switch(event) with(TCPEvent) { 115 case CONNECT: 116 _connected = true; 117 if(_buffer.data.length) _connection.send(_buffer.data!ubyte); 118 break; 119 case READ: 120 static ubyte[] __buffer = new ubyte[4096]; 121 _buffer.reset(); 122 while(true) { 123 auto len = _connection.recv(__buffer); 124 if(len > 0) _buffer.write(__buffer[0..len]); 125 if(len < __buffer.length) break; 126 } 127 _handler(); 128 break; 129 case CLOSE: 130 if(!_successful) _failure(); 131 break; 132 default: 133 break; 134 } 135 } 136 137 private void handleFirst() { 138 ClientResponse response = new ClientResponse(); 139 if(response.parse(_buffer.data!char)) { 140 if(auto contentLength = "content-length" in response.headers) { 141 try { 142 _contentLength = to!size_t(*contentLength); 143 if(_contentLength > response.body_.length) { 144 _handler = &this.handleLong; 145 _response = response; 146 return; 147 } 148 } catch(ConvException) { 149 _performing = false; 150 _successful = false; 151 _failure(); 152 if(_options.closeOnFailure) this.close(); 153 return; 154 } 155 } 156 _performing = false; 157 _successful = true; 158 _success(response); 159 if(_options.closeOnSuccess) this.close(); 160 } else { 161 _performing = false; 162 _successful = false; 163 _failure(); 164 if(_options.closeOnFailure) this.close(); 165 } 166 } 167 168 private void handleLong() { 169 _response.body_ = _response.body_ ~ _buffer.data!char; 170 if(_response.body_.length >= _contentLength) { 171 _performing = false; 172 _successful = true; 173 _success(_response); 174 if(_options.closeOnSuccess) this.close(); 175 } 176 } 177 178 } 179 180 }