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);