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     @property void socketFactory(NetworkStream delegate(string, string, ushort) f) {
98         _http.socketFactory = f;
99     }
100     /++ Set Cookie for http requests.
101         $(B v) - array of cookie.
102 
103         You can set and read cookies. In the next example server set cookie and we read it.
104         Example:
105         ---
106         void main() {
107            rs = rq.get("http://httpbin.org/cookies/set?A=abcd&b=cdef");
108            assert(rs.code == 200);
109            auto json = parseJSON(cast(string)rs.responseBody.data).object["cookies"].object;
110            assert(json["A"].str == "abcd");
111            assert(json["b"].str == "cdef");
112            foreach(c; rq.cookie) {
113                final switch(c.attr) {
114                    case "A":
115                         assert(c.value == "abcd");
116                         break;
117                    case "b":
118                         assert(c.value == "cdef");
119                         break;
120                 }
121             }
122         }
123         ---
124     +/
125     @property void cookie(Cookie[] v) pure @nogc nothrow {
126         _http.cookie = v;
127     }
128     /// Get Cookie for http requests.
129     @property Cookie[] cookie()  pure @nogc nothrow {
130         return _http.cookie;
131     }
132     /++
133      set "streaming" property
134      $(B v) = value to set (true - use streaming).
135 
136      Use streaming when you do not want to keep whole response in memory.
137      Example:
138      ---
139      import requests;
140      import std.stdio;
141 
142      void main() {
143          Request rq = Request();
144 
145          rq.useStreaming = true;
146          auto rs = rq.get("http://example.com/SomeHugePicture.png");
147          auto stream = rs.receiveAsRange();
148          File file = File("SomeHugePicture.png", "wb");
149 
150          while(!stream.empty)  {
151              file.rawWrite(stream.front);
152              stream.popFront;
153          }
154          file.close();
155      }
156      ---
157     +/
158 
159     @property void useStreaming(bool v) pure @nogc nothrow {
160         _http.useStreaming = v;
161         _ftp.useStreaming = v;
162     }
163     ///
164     /// get length og actually received content.
165     /// this value increase over time, while we receive data
166     ///
167     @property long contentReceived() pure @nogc nothrow {
168         final switch ( _uri.scheme ) {
169             case "http", "https":
170                 return _http.contentReceived;
171             case "ftp":
172                 return _ftp.contentReceived;
173         }
174     }
175     /// get contentLength of the responce
176     @property long contentLength() pure @nogc nothrow {
177         final switch ( _uri.scheme ) {
178             case "http", "https":
179                 return _http.contentLength;
180             case "ftp":
181                 return _ftp.contentLength;
182         }
183     }
184     /++
185      + Enable or disable ssl peer verification.
186      + $(B v) - enable if `true`, disable if `false`.
187      +
188      + Default - false.
189      + Example:
190      + ---
191      +     auto rq = Request();
192      +     rq.sslSetVerifyPeer(true);
193      +     auto rs = rq.get("https://localhost:4443/");
194      + ---
195      +/
196     @property void sslSetVerifyPeer(bool v) {
197         _http.sslSetVerifyPeer(v);
198     }
199 
200     /++
201      + Set path to ssl key file.
202      +
203      + file type can be SSLOptions.filetype.pem (default) or SSLOptions.filetype.der or SSLOptions.filetype.asn1.
204      +
205      + if you configured only key file or only cert file, we will try to load counterpart from the same file.
206      +
207      + Example:
208      + ---
209      +     auto rq = Request();
210      +     rq.sslSetKeyFile("client01.key");
211      +     auto rs = rq.get("https://localhost:4443/");
212      + ---
213      +/
214     @property void sslSetKeyFile(string path, SSLOptions.filetype type = SSLOptions.filetype.pem) pure @safe nothrow @nogc {
215         _http.sslSetKeyFile(path, type);
216     }
217 
218     /++
219      + Set path to ssl cert file.
220      +
221      + file type can be SSLOptions.filetype.pem (default) or SSLOptions.filetype.der or SSLOptions.filetype.asn1.
222      +
223      + if you configured only key file or only cert file, we will try to load counterpart from the same file.
224      +
225      + Example:
226      + ---
227      +     auto rq = Request();
228      +     rq.sslSetCertFile("client01.crt");
229      +     auto rs = rq.get("https://localhost:4443/");
230      + ---
231      +/
232     @property void sslSetCertFile(string path, SSLOptions.filetype type = SSLOptions.filetype.pem) pure @safe nothrow @nogc {
233         _http.sslSetCertFile(path, type);
234     }
235 
236     /++
237      + Set path to ssl ca cert file.
238      + Example:
239      + ---
240      +     auto rq = Request();
241      +     rq.sslSetCaCert("/opt/local/etc/openssl/cert.pem");
242      +     auto rs = rq.get("https://localhost:4443/");
243      + ---
244      +/
245     @property void sslSetCaCert(string path) pure @safe nothrow @nogc {
246         _http.sslSetCaCert(path);
247     }
248 
249     @property auto sslOptions() {
250         return _http.sslOptions();
251     }
252 
253     /++
254      + Set local address for any outgoing requests.
255      + $(B v) can be string with hostname or ip address.
256      +/
257     @property void bind(string v) {
258         _http.bind(v);
259         _ftp.bind(v);
260     }
261 
262     /++
263      + Add headers to request
264      + Params:
265      + headers = headers to send.
266      + Example:
267      + ---
268      +    rq = Request();
269      +    rq.keepAlive = true;
270      +    rq.addHeaders(["X-Header": "test"]);
271      + ---
272      +/
273     void addHeaders(in string[string] headers) {
274         _http.addHeaders(headers);
275     }
276     void clearHeaders() {
277         _http.clearHeaders();
278     }
279     /// Execute GET for http and retrieve file for FTP.
280     /// You have to provide at least $(B uri). All other arguments should conform to HTTPRequest.get or FTPRequest.get depending on the URI scheme.
281     /// 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)
282     /// you will receive Exception("Operation not supported for ftp")
283     ///
284     Response get(A...)(string uri, A args) {
285         if ( uri ) {
286             _uri = URI(uri);
287             _uri.idn_encode();
288         }
289         _method = "GET";
290         final switch ( _uri.scheme ) {
291             case "http", "https":
292                 _http.uri = _uri;
293                 static if (__traits(compiles, _http.get(null, args))) {
294                     return _http.get(null, args);
295                 } else {
296                     throw new Exception("Operation not supported for http");
297                 }
298             case "ftp":
299                 static if (args.length == 0) {
300                     return _ftp.get(uri);
301                 } else {
302                     throw new Exception("Operation not supported for ftp");
303                 }
304         }
305     }
306     /// Execute POST for http and STOR file for FTP.
307     /// You have to provide  $(B uri) and data. Data should conform to HTTPRequest.post or FTPRequest.post depending on the URI scheme.
308     /// When arguments do not conform scheme you will receive Exception("Operation not supported for ftp")
309     ///
310     Response post(A...)(string uri, A args) {
311         if ( uri ) {
312             _uri = URI(uri);
313             _uri.idn_encode();
314         }
315         _method = "POST";
316         final switch ( _uri.scheme ) {
317             case "http", "https":
318                 _http.uri = _uri;
319                 static if (__traits(compiles, _http.post(null, args))) {
320                     return _http.post(null, args);
321                 } else {
322                     throw new Exception("Operation not supported for http");
323                 }
324             case "ftp":
325                 static if (__traits(compiles, _ftp.post(uri, args))) {
326                     return _ftp.post(uri, args);
327                 } else {
328                     throw new Exception("Operation not supported for ftp");
329                 }
330         }
331     }
332     /++
333      + Execute request with method
334      +/
335     Response exec(string method="GET", A...)(string uri, A args) {
336         _method = method;
337         _uri = URI(uri);
338         _uri.idn_encode();
339         _http.uri = _uri;
340         return _http.exec!(method)(null, args);
341     }
342 
343     string toString() const {
344         return "Request(%s, %s)".format(_method, _uri.uri());
345     }
346     string format(string fmt) const {
347         final switch(_uri.scheme) {
348             case "http", "https":
349                 return _http.format(fmt);
350             case "ftp":
351                 return _ftp.format(fmt);
352         }
353     }
354 }