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