1 module mars.clients.CurlHttpClient;
2 
3 import core.thread;
4 import std.net.curl;
5 import std.stdio;
6 
7 import mars.HttpResponseHandler;
8 import mars.HttpClient;
9 import mars.HttpClientOptions;
10 import mars.HttpRequest;
11 import mars.HttpResponse;
12 import mars.RequestParams;
13 import mars.ServerException;
14 import mars.StatusCode;
15 import mars.UrlHelper;
16 
17 class CurlHttpClient : HttpClient {
18 	
19 	private HttpClientOptions mOptions;
20 	private HTTP mHttp;
21 	
22 	private this(HttpClientOptions options) {
23 		mOptions = options;
24 		mHttp = HTTP();
25 		
26 		loadDefaultHeaders();
27 	}
28 	
29 	static HttpClient init(HttpClientOptions options) {
30 		return new this(options);
31 	}
32 	
33 	void post(HttpRequest request, HttpResponseHandler responseHandler) {
34 		doRequest(HTTP.Method.post, request, responseHandler);
35 	}
36 	
37 	HttpResponse post(HttpRequest request) {
38 		return doRequest(HTTP.Method.post, request);
39 	}
40 	
41 	void get(HttpRequest request, HttpResponseHandler responseHandler) {
42 		doRequest(HTTP.Method.get, request, responseHandler);
43 	}
44 	
45 	HttpResponse get(HttpRequest request) {
46 		return doRequest(HTTP.Method.get, request);
47 	}
48 	
49 	void put(HttpRequest request, HttpResponseHandler responseHandler) {
50 		doRequest(HTTP.Method.put, request, responseHandler);
51 	}
52 	
53 	HttpResponse put(HttpRequest request) {
54 		return doRequest(HTTP.Method.put, request);
55 	}
56 	
57 	void del(HttpRequest request, HttpResponseHandler responseHandler) {
58 		doRequest(HTTP.Method.del, request, responseHandler);
59 	}
60 	
61 	HttpResponse del(HttpRequest request) {
62 		return doRequest(HTTP.Method.del, request);
63 	}
64 	
65 	void patch(HttpRequest request, HttpResponseHandler responseHandler) {
66 		doRequest(HTTP.Method.patch, request, responseHandler);
67 	}
68 	
69 	HttpResponse patch(HttpRequest request) {
70 		return doRequest(HTTP.Method.patch, request);
71 	}
72 
73     void upload(HttpRequest request, File file, HttpResponseHandler responseHandler) {
74         // TODO move to thread
75         responseHandler.onResponse(upload(request, file));
76     }
77 
78     HttpResponse upload(HttpRequest request, File file) {
79         return null;
80     }
81 	
82 	HttpResponse doRequest(HTTP.Method httpMethod, HttpRequest request) {
83 		import std.stdio;
84 		import std.conv;
85 		
86 		int statusCode = StatusCode.BAD_REQUEST;
87 		string[string] responseHeaders;
88 		ubyte[] responseBody;
89 
90 		mHttp.method = httpMethod;
91 		mHttp.operationTimeout = mOptions.timeout;
92 		string requestUrl = UrlHelper.createUrl(mOptions.baseUrl, request.getUrl, request.getUrlParams, request.getParams);
93 		mHttp.url = requestUrl;
94 		
95 		if (httpMethod != HTTP.Method.get) {
96 			string contentType = "Content-Type" in mOptions.headers ? mOptions.headers["Content-Type"] : null;
97 			if (contentType is null) {
98 				contentType = "Content-Type" in request.getHeaders ? request.getHeaders["Content-Type"] : "application/json";
99 			}
100 			mHttp.setPostData(request.getData, contentType);
101 		}
102 		
103 		if (request.getHeaders != null && request.getHeaders.length > 0) {
104 			foreach (name, value; request.getHeaders) {
105 				mHttp.addRequestHeader(name, value);
106 			}
107 		}
108 		
109 		mHttp.onReceiveStatusLine = (HTTP.StatusLine status) {
110 			statusCode = status.code;
111 		};
112 		mHttp.onReceiveHeader = (in char[] key, in char[] value) {
113 			responseHeaders[to!string(key)] = to!string(value);
114 		};
115 		mHttp.onReceive = (ubyte[] data) {
116 			responseBody = data;
117 			return data.length;
118 		};
119 		mHttp.onProgress = (size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow) {
120 			// writeln("Progress ", dltotal, ", ", dlnow, ", ", ultotal, ", ", ulnow);
121 			return 0;
122 		};
123 
124 		try {
125 			mHttp.perform();
126 		} catch (Exception e) {
127 			import std.algorithm : startsWith;
128 			import mars.TimeoutException;
129 
130 			if (e.msg.startsWith("Timeout was reached on handle")) {
131 				throw new TimeoutException(requestUrl);
132 			} else {
133 				throw e;
134 			}
135 		}
136 		
137 		loadDefaultHeaders();
138 		
139 		HttpResponse response = new HttpResponse.Builder()
140 			.url(requestUrl)
141 			.statusCode(statusCode)
142 			.headers(responseHeaders)
143 			.responseBody(responseBody)
144 			.exception(StatusCode.isSuccess(statusCode) ? null : new ServerException(statusCode))
145 			.build();
146 		
147 		return response;
148 	}
149 	
150 	private void doRequest(HTTP.Method httpMethod, HttpRequest request, HttpResponseHandler responseHandler) {
151 		// TODO move to thread
152 		//Thread t = new JobThread(httpMethod, request, responseHandler);
153 		//t.start();
154 		responseHandler.onResponse(doRequest(httpMethod, request));
155 	}
156 	
157 	/**
158 	 * Remove all custom headers except the default headers
159 	 */
160 	private void loadDefaultHeaders() {
161 		mHttp.clearRequestHeaders;
162 		
163 		string[string] headers = mOptions.headers;
164 		
165 		if (headers != null && headers.length > 0) {
166 			foreach (name, value; headers) {
167 				mHttp.addRequestHeader(name, value);
168 			}
169 		}
170 	}
171 	
172 	private class JobThread : Thread {
173 
174 		private HTTP.Method mHttpMethod;
175 		private HttpRequest mRequest;
176 		private HttpResponseHandler mResponseHandler;
177 		
178 		this(HTTP.Method httpMethod, HttpRequest request, HttpResponseHandler responseHandler) {
179 			mHttpMethod = httpMethod;
180 			mRequest = request;
181 			mResponseHandler = responseHandler;
182 			
183 			super(&run);
184 		}
185 
186 		private	void run() {
187 			mResponseHandler.onResponse(doRequest(mHttpMethod, mRequest));
188 		}
189 	}
190 }