1 module requests.request;
2 import requests.http;
3 import requests.ftp;
4 import requests.streams;
5 import requests.base;
6 import requests.uri;
7 
8 import std.datetime;
9 import std.conv;
10 import std.experimental.logger;
11 import std.format;
12 import requests.utils;
13 
14 
15 /**
16    This is simplest interface to both http and ftp protocols.
17    Request has methods get, post and exec which routed to proper concrete handler (http or ftp, etc).
18    To enable some protocol-specific featutes you have to use protocol interface directly (see docs for HTTPRequest or FTPRequest)
19 */
20 public struct Request {
21     private {
22         URI         _uri;
23         HTTPRequest _http;  // route all http/https requests here
24         FTPRequest  _ftp;   // route all ftp requests here
25         string      _method;
26     }
27     /// Set timeout on connection and IO operation.
28     /// $(B v) - timeout value
29     /// If timeout expired Request operation will throw $(B TimeoutException).
30     @property void timeout(Duration v) pure @nogc nothrow {
31         _http.timeout = v;
32         _ftp.timeout = v;
33     }
34     /// Set http keepAlive value
35     /// $(B v) - use keepalive requests - $(B true), or not - $(B false)
36     /// Request will automatically reopen connection when host, protocol
37     /// or port change (so it is safe to send different requests through
38     /// single instance of Request).
39     /// It also recovers if server prematurely close keep-alive connection.
40     @property void keepAlive(bool v) pure @nogc nothrow {
41         _http.keepAlive = v;
42     }
43     /// Set limit on HTTP redirects
44     /// $(B v) - limit on redirect depth
45     /// Throws $(B MaxRedirectsException) when limit is reached.
46     @property void maxRedirects(uint v) pure @nogc nothrow {
47         _http.maxRedirects = v;
48     }
49     /// Set maximum content lenth both for http and ftp requests
50     /// $(B v) - maximum content length in bytes. When limit reached - throws $(B RequestException)
51     @property void maxContentLength(size_t v) pure @nogc nothrow {
52         _http.maxContentLength = v;
53         _ftp.maxContentLength = v;
54     }
55     /// Set maximum length for HTTP headers
56     /// $(B v) - maximum length of the HTTP response. When limit reached - throws $(B RequestException)
57     @property void maxHeadersLength(size_t v) pure @nogc nothrow {
58         _http.maxHeadersLength = v;
59     }
60     /// Set IO buffer size for http and ftp requests
61     /// $(B v) - buffer size in bytes.
62     @property void bufferSize(size_t v) {
63         _http.bufferSize = v;
64         _ftp.bufferSize = v;
65     }
66     /// Set verbosity for HTTP or FTP requests.
67     /// $(B v) - verbosity level (0 - no output, 1 - headers to stdout, 2 - headers and data hexdump to stdout). default = 0.
68     @property void verbosity(uint v) {
69         _http.verbosity = v;
70         _ftp.verbosity = v;
71     }
72     /++ Set authenticator for http requests.
73      +   $(B v) - Auth instance.
74      +   Example:
75      +   ---
76      +   import requests;
77      +   void main() {
78      +       rq = Request();
79      +       rq.authenticator = new BasicAuthentication("user", "passwd");
80      +       rs = rq.get("http://httpbin.org/basic-auth/user/passwd");
81      +   }
82      +   ---
83      +/
84     @property void authenticator(Auth v) {
85         _http.authenticator = v;
86         _ftp.authenticator = v;
87     }
88     /// set proxy property.
89     /// $(B v) - full url to proxy.
90     ///
91     /// Note that we recognize proxy settings from process environment (see $(LINK https://github.com/ikod/dlang-requests/issues/46)):
92     /// you can use http_proxy, https_proxy, all_proxy (as well as uppercase names).
93     @property void proxy(string v) {
94         _http.proxy = v;
95         _ftp.proxy = v;
96     }
97     /++ Set Cookie for http requests.
98         $(B v) - array of cookie.
99 
100         You can set and read cookies. In the next example server set cookie and we read it.
101         Example:
102         ---
103         void main() {
104            rs = rq.get("http://httpbin.org/cookies/set?A=abcd&b=cdef");
105            assert(rs.code == 200);
106            auto json = parseJSON(cast(string)rs.responseBody.data).object["cookies"].object;
107            assert(json["A"].str == "abcd");
108            assert(json["b"].str == "cdef");
109            foreach(c; rq.cookie) {
110                final switch(c.attr) {
111                    case "A":
112                         assert(c.value == "abcd");
113                         break;
114                    case "b":
115                         assert(c.value == "cdef");
116                         break;
117                 }
118             }
119         }
120         ---
121     +/
122     @property void cookie(Cookie[] v) pure @nogc nothrow {
123         _http.cookie = v;
124     }
125     /// Get Cookie for http requests.
126     @property Cookie[] cookie()  pure @nogc nothrow {
127         return _http.cookie;
128     }
129     /++
130      set "streaming" property
131      $(B v) = value to set (true - use streaming).
132 
133      Use streaming when you do not want to keep whole response in memory.
134      Example:
135      ---
136      import requests;
137      import std.stdio;
138 
139      void main() {
140          Request rq = Request();
141 
142          rq.useStreaming = true;
143          auto rs = rq.get("http://example.com/SomeHugePicture.png");
144          auto stream = rs.receiveAsRange();
145          File file = File("SomeHugePicture.png", "wb");
146 
147          while(!stream.empty)  {
148              file.rawWrite(stream.front);
149              stream.popFront;
150          }
151          file.close();
152      }
153      ---
154     +/
155 
156     @property void useStreaming(bool v) pure @nogc nothrow {
157         _http.useStreaming = v;
158         _ftp.useStreaming = v;
159     }
160     ///
161     /// get length og actually received content.
162     /// this value increase over time, while we receive data
163     /// 
164     @property long contentReceived() pure @nogc nothrow {
165         final switch ( _uri.scheme ) {
166             case "http", "https":
167                 return _http.contentReceived;
168             case "ftp":
169                 return _ftp.contentReceived;
170         }
171     }
172     /// get contentLength of the responce
173     @property long contentLength() pure @nogc nothrow {
174         final switch ( _uri.scheme ) {
175             case "http", "https":
176                 return _http.contentLength;
177             case "ftp":
178                 return _ftp.contentLength;
179         }
180     }
181     /++
182      + Enable or disable ssl peer verification.
183      + $(B v) - enable if `true`, disable if `false`.
184      +
185      + Default - false.
186      + Example:
187      + ---
188      +     auto rq = Request();
189      +     rq.sslSetVerifyPeer(true);
190      +     auto rs = rq.get("https://localhost:4443/");
191      + ---
192      +/
193     @property void sslSetVerifyPeer(bool v) {
194         _http.sslSetVerifyPeer(v);
195     }
196 
197     /++
198      + Set path to ssl key file.
199      +
200      + file type can be SSLOptions.filetype.pem (default) or SSLOptions.filetype.der or SSLOptions.filetype.asn1.
201      +
202      + if you configured only key file or only cert file, we will try to load counterpart from the same file.
203      +
204      + Example:
205      + ---
206      +     auto rq = Request();
207      +     rq.sslSetKeyFile("client01.key");
208      +     auto rs = rq.get("https://localhost:4443/");
209      + ---
210      +/
211     @property void sslSetKeyFile(string path, SSLOptions.filetype type = SSLOptions.filetype.pem) pure @safe nothrow @nogc {
212         _http.sslSetKeyFile(path, type);
213     }
214 
215     /++
216      + Set path to ssl cert file.
217      +
218      + file type can be SSLOptions.filetype.pem (default) or SSLOptions.filetype.der or SSLOptions.filetype.asn1.
219      +
220      + if you configured only key file or only cert file, we will try to load counterpart from the same file.
221      +
222      + Example:
223      + ---
224      +     auto rq = Request();
225      +     rq.sslSetCertFile("client01.crt");
226      +     auto rs = rq.get("https://localhost:4443/");
227      + ---
228      +/
229     @property void sslSetCertFile(string path, SSLOptions.filetype type = SSLOptions.filetype.pem) pure @safe nothrow @nogc {
230         _http.sslSetCertFile(path, type);
231     }
232 
233     /++
234      + Set path to ssl ca cert file.
235      + Example:
236      + ---
237      +     auto rq = Request();
238      +     rq.sslSetCaCert("/opt/local/etc/openssl/cert.pem");
239      +     auto rs = rq.get("https://localhost:4443/");
240      + ---
241      +/
242     @property void sslSetCaCert(string path) pure @safe nothrow @nogc {
243         _http.sslSetCaCert(path);
244     }
245 
246     @property auto sslOptions() {
247         return _http.sslOptions();
248     }
249 
250     /++
251      + Set local address for any outgoing requests.
252      + $(B v) can be string with hostname or ip address.
253      +/
254     @property void bind(string v) {
255         _http.bind(v);
256         _ftp.bind(v);
257     }
258 
259     /++
260      + Add headers to request
261      + Params:
262      + headers = headers to send.
263      + Example:
264      + ---
265      +    rq = Request();
266      +    rq.keepAlive = true;
267      +    rq.addHeaders(["X-Header": "test"]);
268      + ---
269      +/
270     void addHeaders(in string[string] headers) {
271         _http.addHeaders(headers);
272     }
273     void clearHeaders() {
274         _http.clearHeaders();
275     }
276     /// Execute GET for http and retrieve file for FTP.
277     /// You have to provide at least $(B uri). All other arguments should conform to HTTPRequest.get or FTPRequest.get depending on the URI scheme.
278     /// When arguments do not conform scheme (for example you try to call get("ftp://somehost.net/pub/README", {"a":"b"}) which doesn't make sense)
279     /// you will receive Exception("Operation not supported for ftp")
280     ///
281     Response get(A...)(string uri, A args) {
282         if ( uri ) {
283             _uri = URI(uri);
284             _uri.idn_encode();
285         }
286         _method = "GET";
287         final switch ( _uri.scheme ) {
288             case "http", "https":
289                 _http.uri = _uri;
290                 static if (__traits(compiles, _http.get(null, args))) {
291                     return _http.get(null, args);
292                 } else {
293                     throw new Exception("Operation not supported for http");
294                 }
295             case "ftp":
296                 static if (args.length == 0) {
297                     return _ftp.get(uri);
298                 } else {
299                     throw new Exception("Operation not supported for ftp");
300                 }
301         }
302     }
303     /// Execute POST for http and STOR file for FTP.
304     /// You have to provide  $(B uri) and data. Data should conform to HTTPRequest.post or FTPRequest.post depending on the URI scheme.
305     /// When arguments do not conform scheme you will receive Exception("Operation not supported for ftp")
306     ///
307     Response post(A...)(string uri, A args) {
308         if ( uri ) {
309             _uri = URI(uri);
310             _uri.idn_encode();
311         }
312         _method = "POST";
313         final switch ( _uri.scheme ) {
314             case "http", "https":
315                 _http.uri = _uri;
316                 static if (__traits(compiles, _http.post(null, args))) {
317                     return _http.post(null, args);
318                 } else {
319                     throw new Exception("Operation not supported for http");
320                 }
321             case "ftp":
322                 static if (__traits(compiles, _ftp.post(uri, args))) {
323                     return _ftp.post(uri, args);
324                 } else {
325                     throw new Exception("Operation not supported for ftp");
326                 }
327         }
328     }
329     /++
330      + Execute request with method
331      +/
332     Response exec(string method="GET", A...)(string uri, A args) {
333         _method = method;
334         _uri = URI(uri);
335         _uri.idn_encode();
336         _http.uri = _uri;
337         return _http.exec!(method)(null, args);
338     }
339 
340     string toString() const {
341         return "Request(%s, %s)".format(_method, _uri.uri());
342     }
343     string format(string fmt) const {
344         final switch(_uri.scheme) {
345             case "http", "https":
346                 return _http.format(fmt);
347             case "ftp":
348                 return _ftp.format(fmt);
349         }
350     }
351 }