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