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 }