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 }