1 /*
2  * DSFML - The Simple and Fast Multimedia Library for D
3  *
4  * Copyright (c) 2013 - 2018 Jeremy DeHaan (dehaan.jeremiah@gmail.com)
5  *
6  * This software is provided 'as-is', without any express or implied warranty.
7  * In no event will the authors be held liable for any damages arising from the
8  * use of this software.
9  *
10  * Permission is granted to anyone to use this software for any purpose,
11  * including commercial applications, and to alter it and redistribute it
12  * freely, subject to the following restrictions:
13  *
14  * 1. The origin of this software must not be misrepresented; you must not claim
15  * that you wrote the original software. If you use this software in a product,
16  * an acknowledgment in the product documentation would be appreciated but is
17  * not required.
18  *
19  * 2. Altered source versions must be plainly marked as such, and must not be
20  * misrepresented as being the original software.
21  *
22  * 3. This notice may not be removed or altered from any source distribution
23  *
24  *
25  * DSFML is based on SFML (Copyright Laurent Gomila)
26  */
27 
28 /**
29  * The Http class is a very simple HTTP client that allows you to communicate
30  * with a web server. You can retrieve web pages, send data to an interactive
31  * resource, download a remote file, etc. The HTTPS protocol is not supported.
32  *
33  * The HTTP client is split into 3 classes:
34  * $(UL
35  * $(LI Http.Request)
36  * $(LI Http.Response)
37  * $(LI Http))
38  *
39  * $(PARA Http.Request builds the request that will be sent to the server. A
40  * request is made of:)
41  * $(UL
42  * $(LI a method (what you want to do))
43  * $(LI a target URI (usually the name of the web page or file))
44  * $(LI one or more header fields (options that you can pass to the server))
45  * $(LI an optional body (for POST requests)))
46  *
47  * $(PARA Http.Response parses the response from the web server and provides
48  * getters to read them. The response contains:)
49  * $(UL
50  * $(LI a status code)
51  * $(LI header fields (that may be answers to the ones that you requested))
52  * $(LI a body, which contains the contents of the requested resource))
53  *
54  * $(PARA $(U Http) provides a simple function, `sendRequest`, to send a
55  * Http.Request and return the corresponding Http.Response from the server.)
56  *
57  * Example:
58  * ---
59  * // Create a new HTTP client
60  * auto http = new Http();
61  *
62  * // We'll work on http://www.sfml-dev.org
63  * http.setHost("http://www.sfml-dev.org");
64  *
65  * // Prepare a request to get the 'features.php' page
66  * auto request = new Http.Request("features.php");
67  *
68  * // Send the request
69  * auto response = http.sendRequest(request);
70  *
71  * // Check the status code and display the result
72  * auto status = response.getStatus();
73  * if (status == Http.Response.Status.Ok)
74  * {
75  *     writeln(response.getBody());
76  * }
77  * else
78  * {
79  *     writeln("Error ", status);
80  * }
81  * ---
82  */
83 module nudsfml.network.http;
84 
85 public import nudsfml.system.time;
86 
87 /**
88  * An HTTP client.
89  */
90 class Http
91 {
92     package sfHttp* sfPtr;
93 
94     ///Default constructor
95     this()
96     {
97         sfPtr = sfHttp_create();
98     }
99 
100     /**
101      * Construct the HTTP client with the target host.
102      *
103      * This is equivalent to calling `setHost(host, port)`. The port has a
104      * default value of 0, which means that the HTTP client will use the right
105      * port according to the protocol used (80 for HTTP, 443 for HTTPS). You
106      * should leave it like this unless you really need a port other than the
107      * standard one, or use an unknown protocol.
108      *
109      * Params:
110      * 		host = Web server to connect to
111      * 		port = Port to use for connection
112      */
113     this(const(char)[] host, ushort port = 0)
114     {
115         sfPtr = sfHttp_create();
116         sfHttp_setHost(sfPtr, host.ptr, host.length, port);
117     }
118 
119     ///Destructor
120     ~this()
121     {
122         import nudsfml.system.config;
123         mixin(destructorOutput);
124         sfHttp_destroy(sfPtr);
125     }
126 
127     /**
128      * Set the target host.
129      *
130      * This function just stores the host address and port, it doesn't actually
131      * connect to it until you send a request. The port has a default value of
132      * 0, which means that the HTTP client will use the right port according to
133      * the protocol used (80 for HTTP, 443 for HTTPS). You should leave it like
134      * this unless you really need a port other than the standard one, or use an
135      * unknown protocol.
136      *
137      * Params:
138      * 		host = Web server to connect to
139      * 		port = Port to use for connection
140      */
141     void setHost(const(char)[] host, ushort port = 0)
142     {
143         sfHttp_setHost(sfPtr, host.ptr, host.length,port);
144     }
145 
146     /**
147      * Send a HTTP request and return the server's response.
148      *
149      * You must have a valid host before sending a request (see setHost). Any
150      * missing mandatory header field in the request will be added with an
151      * appropriate value. Warning: this function waits for the server's response
152      * and may not return instantly; use a thread if you don't want to block
153      * your application, or use a timeout to limit the time to wait. A value of
154      * Time.Zero means that the client will use the system defaut timeout
155      * (which is usually pretty long).
156      *
157      * Params:
158      * 		request = Request to send
159      * 		timeout = Maximum time to wait
160      */
161     Response sendRequest(Request request, Time timeout = Time.Zero)
162     {
163         return new Response(sfHttp_sendRequest(sfPtr,request.sfPtrRequest,timeout.asMicroseconds()));
164     }
165 
166     /// Define a HTTP request.
167     static class Request
168     {
169         /// Enumerate the available HTTP methods for a request.
170         enum Method
171         {
172             /// Request in get mode, standard method to retrieve a page.
173             Get,
174             /// Request in post mode, usually to send data to a page.
175             Post,
176             /// Request a page's header only.
177             Head,
178             /// Request in put mode, useful for a REST API
179             Put,
180             /// Request in delete mode, useful for a REST API
181             Delete
182         }
183 
184         package sfHttpRequest* sfPtrRequest;
185 
186         /**
187          * This constructor creates a GET request, with the root URI ("/") and
188          * an empty body.
189          *
190          * Params:
191          * 	uri    = Target URI
192          * 	method = Method to use for the request
193          * 	requestBody   = Content of the request's body
194          */
195         this(const(char)[] uri = "/", Method method = Method.Get, const(char)[] requestBody = "")
196         {
197             sfPtrRequest = sfHttpRequest_create();
198             sfHttpRequest_setUri(sfPtrRequest, uri.ptr, uri.length);
199             sfHttpRequest_setMethod(sfPtrRequest, method);
200             sfHttpRequest_setBody(sfPtrRequest, requestBody.ptr, requestBody.length);
201         }
202 
203         /// Destructor
204         ~this()
205         {
206             import nudsfml.system.config;
207             mixin(destructorOutput);
208             sfHttpRequest_destroy(sfPtrRequest);
209         }
210 
211         /**
212          * Set the body of the request.
213          *
214          * The body of a request is optional and only makes sense for POST
215          * requests. It is ignored for all other methods. The body is empty by
216          * default.
217          *
218          * Params:
219          * 		requestBody = Content of the body
220          */
221         void setBody(const(char)[] requestBody)
222         {
223             sfHttpRequest_setBody(sfPtrRequest, requestBody.ptr, requestBody.length);
224         }
225 
226         /**
227          * Set the value of a field.
228          *
229          * The field is created if it doesn't exist. The name of the field is
230          * case insensitive. By default, a request doesn't contain any field
231          * (but the mandatory fields are added later by the HTTP client when
232          * sending the request).
233          *
234          * Params:
235          * 	field = Name of the field to set
236          * 	value = Value of the field
237          */
238         void setField(const(char)[] field, const(char)[] value)
239         {
240             sfHttpRequest_setField(sfPtrRequest, field.ptr, field.length , value.ptr, value.length);
241         }
242 
243         /**
244          * Set the HTTP version for the request.
245          *
246          * The HTTP version is 1.0 by default.
247          *
248          * Parameters
249          * 	major = Major HTTP version number
250          * 	minor = Minor HTTP version number
251          */
252         void setHttpVersion(uint major, uint minor)
253         {
254             sfHttpRequest_setHttpVersion(sfPtrRequest,major, minor);
255         }
256 
257         /**
258          * Set the request method.
259          *
260          * See the Method enumeration for a complete list of all the availale
261          * methods. The method is Http.Request.Method.Get by default.
262          *
263          * Params
264          * 	method = Method to use for the request
265          */
266         void setMethod(Method method)
267         {
268             sfHttpRequest_setMethod(sfPtrRequest,method);
269         }
270 
271         /**
272          * Set the requested URI.
273          *
274          * The URI is the resource (usually a web page or a file) that you want
275          * to get or post. The URI is "/" (the root page) by default.
276          *
277          * Params
278          * 	uri = URI to request, relative to the host
279          */
280         void setUri(const(char)[] uri)
281         {
282             sfHttpRequest_setUri(sfPtrRequest, uri.ptr, uri.length);
283         }
284     }
285 
286     /// Define a HTTP response.
287     static class Response
288     {
289         /// Enumerate all the valid status codes for a response.
290         enum Status
291         {
292             Ok = 200,
293             Created = 201,
294             Accepted = 202,
295             NoContent = 204,
296             ResetContent = 205,
297             PartialContent = 206,
298 
299             MultipleChoices = 300,
300             MovedPermanently = 301,
301             MovedTemporarily = 302,
302             NotModified = 304,
303 
304             BadRequest = 400,
305             Unauthorized = 401,
306             Forbidden = 403,
307             NotFound = 404,
308             RangeNotSatisfiable = 407,
309 
310             InternalServerError = 500,
311             NotImplemented = 501,
312             BadGateway = 502,
313             ServiceNotAvailable = 503,
314             GatewayTimeout = 504,
315             VersionNotSupported = 505,
316 
317             InvalidResponse = 1000,
318             ConnectionFailed = 1001
319         }
320 
321         package sfHttpResponse* sfPtrResponse;
322 
323         //Internally used constructor
324         package this(sfHttpResponse* response)
325         {
326             sfPtrResponse = response;
327         }
328 
329         /**
330          * Get the body of the response.
331          *
332          * Returns: The response body.
333          */
334         string getBody() const
335         {
336             import nudsfml.system.string;
337             return nudsfml.system..string.toString(sfHttpResponse_getBody(sfPtrResponse));
338         }
339 
340         /**
341          * Get the value of a field.
342          *
343          * If the field field is not found in the response header, the empty
344          * string is returned. This function uses case-insensitive comparisons.
345          *
346          * Params:
347          * 	field = Name of the field to get
348          *
349          * Returns: Value of the field, or empty string if not found.
350          */
351         string getField(const(char)[] field) const
352         {
353             import nudsfml.system.string;
354             return nudsfml.system..string.toString(sfHttpResponse_getField(sfPtrResponse, field.ptr, field.length));
355         }
356 
357         /**
358          * Get the major HTTP version number of the response.
359          *
360          * Returns: Major HTTP version number.
361          */
362         uint getMajorHttpVersion() const
363         {
364             return sfHttpResponse_getMajorVersion(sfPtrResponse);
365         }
366 
367         /**
368          * Get the minor HTTP version number of the response.
369          *
370          * Returns: Minor HTTP version number.
371          */
372         uint getMinorHttpVersion() const
373         {
374             return sfHttpResponse_getMinorVersion(sfPtrResponse);
375         }
376 
377         /**
378          * Get the response status code.
379          *
380          * The status code should be the first thing to be checked after
381          * receiving a response, it defines whether it is a success, a failure
382          * or anything else (see the Status enumeration).
383          *
384          * Returns: Status code of the response.
385          */
386         Status getStatus() const
387         {
388             return sfHttpResponse_getStatus(sfPtrResponse);
389         }
390     }
391 }
392 
393 unittest
394 {
395     version(DSFML_Unittest_Network)
396     {
397         import std.stdio;
398 
399         writeln("Unittest for Http");
400 
401         auto http = new Http();
402 
403         http.setHost("http://www.sfml-dev.org");
404 
405         // Prepare a request to get the 'features.php' page
406         auto request = new Http.Request("learn.php");
407 
408         // Send the request
409         auto response = http.sendRequest(request);
410 
411         // Check the status code and display the result
412         auto status = response.getStatus();
413 
414         if (status == Http.Response.Status.Ok)
415         {
416             writeln("Found the site!");
417         }
418         else
419         {
420             writeln("Error: ", status);
421         }
422         writeln();
423     }
424 }
425 
426 private extern(C):
427 
428 struct sfHttpRequest;
429 struct sfHttpResponse;
430 struct sfHttp;
431 
432 //Create a new HTTP request
433 sfHttpRequest* sfHttpRequest_create();
434 
435 //Destroy a HTTP request
436 void sfHttpRequest_destroy(sfHttpRequest* httpRequest);
437 
438 //Set the value of a header field of a HTTP request
439 void sfHttpRequest_setField(sfHttpRequest* httpRequest, const(char)* field, size_t fieldLength, const(char)* value, size_t valueLength);
440 
441 //Set a HTTP request method
442 void sfHttpRequest_setMethod(sfHttpRequest* httpRequest, int method);
443 
444 //Set a HTTP request URI
445 void sfHttpRequest_setUri(sfHttpRequest* httpRequest, const(char)* uri, size_t length);
446 
447 //Set the HTTP version of a HTTP request
448 void sfHttpRequest_setHttpVersion(sfHttpRequest* httpRequest,uint major, uint minor);
449 
450 //Set the body of a HTTP request
451 void sfHttpRequest_setBody(sfHttpRequest* httpRequest, const(char)* ody, size_t length);
452 
453 //HTTP Response Functions
454 
455 //Destroy a HTTP response
456 void sfHttpResponse_destroy(sfHttpResponse* httpResponse);
457 
458 //Get the value of a field of a HTTP response
459 const(char)* sfHttpResponse_getField(const sfHttpResponse* httpResponse, const(char)* field, size_t length);
460 
461 //Get the status code of a HTTP reponse
462 Http.Response.Status sfHttpResponse_getStatus(const sfHttpResponse* httpResponse);
463 
464 //Get the major HTTP version number of a HTTP response
465 uint sfHttpResponse_getMajorVersion(const sfHttpResponse* httpResponse);
466 
467 //Get the minor HTTP version number of a HTTP response
468 uint sfHttpResponse_getMinorVersion(const sfHttpResponse* httpResponse);
469 
470 //Get the body of a HTTP response
471 const(char)* sfHttpResponse_getBody(const sfHttpResponse* httpResponse);
472 
473 //HTTP Functions
474 
475 //Create a new Http object
476 sfHttp* sfHttp_create();
477 
478 //Destroy a Http object
479 void sfHttp_destroy(sfHttp* http);
480 
481 //Set the target host of a HTTP object
482 void sfHttp_setHost(sfHttp* http, const(char)* host, size_t length, ushort port);
483 
484 //Send a HTTP request and return the server's response.
485 sfHttpResponse* sfHttp_sendRequest(sfHttp* http, const(sfHttpRequest)* request, long timeout);