1 /****************************************************************************** 2 3 HTTP connection handler base class for use with the SelectListener 4 5 Fiber based HTTP server base class, derived from IFiberConnectionHandler. 6 7 To build a HTTP server, create a HttpConnectionHandler subclass which 8 implements handleRequest() and use that subclass as connection handler in 9 the SelectListener. 10 11 Copyright: 12 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 13 All rights reserved. 14 15 License: 16 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 17 Alternatively, this file may be distributed under the terms of the Tango 18 3-Clause BSD License (see LICENSE_BSD.txt for details). 19 20 ******************************************************************************/ 21 22 module ocean.net.http.HttpConnectionHandler; 23 24 25 import ocean.meta.types.Qualifiers; 26 import ocean.net.http.HttpRequest, 27 ocean.net.http.HttpResponse, 28 ocean.net.http.HttpException; 29 30 import ocean.net.http.HttpConst: HttpResponseCode; 31 import ocean.net.http.consts.HttpMethod: HttpMethod; 32 import ocean.net.http.consts.HeaderFieldNames; 33 34 import ocean.net.server.connection.IFiberConnectionHandler, 35 ocean.io.select.protocol.fiber.model.IFiberSelectProtocol; 36 37 import ocean.sys.socket.AddressIPSocket; 38 import ocean.sys.ErrnoException; 39 import ocean.core.Enforce; 40 41 /******************************************************************************/ 42 43 abstract class HttpConnectionHandler : IFiberConnectionHandler 44 { 45 /************************************************************************** 46 47 Type aliases as convenience for a subclass 48 49 **************************************************************************/ 50 51 protected alias .HttpResponseCode HttpResponseCode; 52 protected alias .HttpRequest HttpRequest; 53 protected alias .HttpResponse HttpResponse; 54 protected alias .HttpMethod HttpMethod; 55 protected alias .HttpException HttpException; 56 protected alias .HttpServerException HttpServerException; 57 protected alias .ErrnoException ErrnoException; 58 protected alias .IFiberSelectProtocol.IOError IOError; 59 protected alias .IFiberSelectProtocol.IOWarning IOWarning; 60 61 /************************************************************************** 62 63 HTTP request message parser and response message generator 64 65 **************************************************************************/ 66 67 protected HttpRequest request; 68 protected HttpResponse response; 69 70 /************************************************************************** 71 72 Reused exception instance; may be thrown by a subclass as well. 73 74 **************************************************************************/ 75 76 protected HttpException http_exception; 77 78 /************************************************************************** 79 80 Maximum number of requests through the same connection when using 81 persistent connections; 0 disables using persistent connections. 82 83 **************************************************************************/ 84 85 protected uint keep_alive_maxnum = 0; 86 87 /************************************************************************** 88 89 Status code for the case when a required message header parameters are 90 missing. 91 92 **************************************************************************/ 93 94 protected auto default_exception_status_code = HttpResponseCode.InternalServerError; 95 96 /************************************************************************** 97 98 Supported HTTP methods, set in the constructor (only checked for element 99 existence; the actual value is irrelevant) 100 101 **************************************************************************/ 102 103 private bool[HttpMethod] supported_methods; 104 105 /************************************************************************** 106 107 Constructor 108 109 Uses the default request message parser/response generator settings. 110 That means, the request parser will be set up for request methods 111 without a message body, such as GET or HEAD (in contrast to POST or PUT 112 which have a message body). 113 114 Params: 115 dispatcher = select dispatcher instance to register to 116 finalizer = called when the connection is shut down 117 (optional, may be null) 118 stack_size = fiber stack size, use 119 HttpConnectionHandler.default_stack_size for the 120 default value 121 supported_methods = list of supported HTTP methods 122 123 **************************************************************************/ 124 125 protected this ( EpollSelectDispatcher dispatcher, scope FinalizeDg finalizer, 126 size_t stack_size, 127 HttpMethod[] supported_methods ... ) 128 { 129 this(dispatcher, finalizer, stack_size, 130 new HttpRequest, new HttpResponse, supported_methods); 131 } 132 133 /************************************************************************** 134 135 Constructor 136 137 Params: 138 dispatcher = select dispatcher instance to register to 139 request = request message parser 140 response = response message generator 141 finalizer = called when the connection is shut down 142 (optional, may be null) 143 supported_methods = list of supported HTTP methods 144 stack_size = fiber stack size, use 145 HttpConnectionHandler.default_stack_size for the 146 default value 147 148 **************************************************************************/ 149 150 protected this ( EpollSelectDispatcher dispatcher, scope FinalizeDg finalizer, 151 size_t stack_size, 152 HttpRequest request, HttpResponse response, 153 HttpMethod[] supported_methods ... ) 154 { 155 156 super(dispatcher, stack_size, new AddressIPSocket!(), finalizer); 157 158 this.request = request; 159 this.response = response; 160 this.http_exception = request.http_exception; 161 162 foreach (method; supported_methods) 163 { 164 this.supported_methods[method] = true; 165 } 166 167 this.supported_methods.rehash; 168 } 169 170 /*************************************************************************** 171 172 Connection handler method. 173 174 ***************************************************************************/ 175 176 final protected override void handle ( ) 177 { 178 bool keep_alive = false; 179 180 uint n = 0; 181 182 try 183 { 184 do try try 185 { 186 HttpResponseCode status; 187 188 cstring response_msg_body; 189 190 try 191 { 192 this.receiveRequest(); 193 194 keep_alive = n? n < this.keep_alive_maxnum : 195 this.keep_alive_maxnum && this.keep_alive; 196 197 n++; 198 199 status = this.handleRequest(response_msg_body); 200 } 201 catch (HttpParseException e) 202 { 203 this.handleHttpException(e); 204 /* 205 * On request parse error this connection cannot stay alive 206 * because when the request was not completely parsed, its 207 * end and therefore the beginning of the next request is 208 * unknown, so the server-client communication is broken. 209 */ 210 break; 211 } 212 catch (HttpException e) 213 { 214 keep_alive &= this.handleHttpException(e); 215 status = e.status; 216 } 217 catch (HttpServerException e) 218 { 219 keep_alive &= this.handleHttpServerException(e); 220 status = this.default_exception_status_code; 221 } 222 223 this.sendResponse(status, response_msg_body, keep_alive); 224 } 225 finally 226 { 227 this.onResponseSent(); 228 } 229 finally 230 { 231 this.request.reset(); 232 } 233 while (keep_alive); 234 } 235 catch (IOError e) 236 { 237 this.notifyIOException(e, true); 238 } 239 catch (IOWarning e) 240 { 241 this.notifyIOException(e, false); 242 } 243 } 244 245 /************************************************************************** 246 247 Resettable interface method; resets the request. 248 249 **************************************************************************/ 250 251 public override void reset ( ) 252 { 253 super.reset(); 254 255 this.request.reset(); 256 } 257 258 /************************************************************************** 259 260 Handles the request. 261 262 Params: 263 response_msg_body = body of the response body 264 265 Returns: 266 HTTP status code 267 268 **************************************************************************/ 269 270 abstract protected HttpResponseCode handleRequest ( out cstring response_msg_body ); 271 272 /*************************************************************************** 273 274 Called after handleRequest() has returned and when the response message 275 buffer is no longer referenced or after handleRequest() has thrown an 276 exception. 277 A subclass may override this method to release resources. This is useful 278 especially when a large number of persistent connections is open where 279 each connection is only used sporadically. 280 281 ***************************************************************************/ 282 283 protected void onResponseSent ( ) { } 284 285 /************************************************************************** 286 287 Receives the HTTP request message. 288 289 Throws: 290 - HttpParseException on request message parse error, 291 - HttpException if the request contains parameter values that are 292 invalid, of range or not supported (unsupported HTTP version or 293 method, for example), 294 - HeaderParameterException if a required header parameter is missing 295 or has an invalid value (a misformatted number, for example), 296 - IOWarning when a socket read/write operation results in an 297 end-of-flow or hung-up condition, 298 - IOError when an error event is triggered for a socket. 299 300 **************************************************************************/ 301 302 private void receiveRequest ( ) 303 { 304 super.reader.readConsume((void[] data) 305 { 306 size_t consumed = this.request.parse(cast (char[]) data, this.request_msg_body_length); 307 308 return this.request.finished? consumed : data.length + 1; 309 }); 310 311 enforce(this.http_exception.set(HttpResponseCode.NotImplemented), 312 this.request.method in this.supported_methods); 313 } 314 315 /************************************************************************** 316 317 Sends the HTTP response message. 318 319 Params: 320 status = HTTP status 321 response_msg_body = response message body, if any 322 keep_alive = tell the client that this connection will 323 - true: stay persistent or 324 - false: be closed 325 after the response message has been sent. 326 327 Throws: 328 IOError on socket I/O error. 329 330 **************************************************************************/ 331 332 private void sendResponse ( HttpResponseCode status, cstring response_msg_body, bool keep_alive ) 333 { 334 with (this.response) 335 { 336 http_version = this.request.http_version; 337 338 set(HeaderFieldNames.General.Names.Connection, keep_alive? "keep-alive" : "close"); 339 340 super.writer.send(render(status, response_msg_body)).flush(); 341 } 342 } 343 344 /************************************************************************** 345 346 Tells the request message body length. 347 This method should be overridden when a request message body is 348 expected. It is invoked when the message header is completely parsed. 349 The default behaviour is expecting no request message body. 350 351 Returns: 352 the request message body length in bytes (0 indicates that no 353 request message body is expected) 354 355 Throws: 356 HttpException (use the http_exception member) with status set to 357 - status.RequestEntityTooLarge to reject a request whose message 358 body is too long or 359 - an appropriate status to abort request processing and 360 immediately send the response if the message body length 361 cannot be determined, e.g. because required request header 362 parameters are missing. 363 364 **************************************************************************/ 365 366 protected size_t request_msg_body_length ( ) 367 { 368 return 0; 369 } 370 371 /************************************************************************** 372 373 Handles HTTP server exception e which was thrown while parsing the 374 request message or from handleRequest() or request_msg_body_length() and 375 is not a HttpException. 376 A subclass may override this method to be notified when an exception is 377 thrown and decide whether the connection may stay persistent or should 378 be closed after the response has been sent. 379 The default behaviour is allowing the connection to stay persistent. 380 381 Params: 382 e = HTTP server exception e which was thrown while parsing the 383 request message or from handleRequest() or 384 request_msg_body_length() and is not a HttpException. 385 386 Returns: 387 true if the connection may stay persistent or false if it must be 388 closed after the response has been sent. 389 390 **************************************************************************/ 391 392 protected bool handleHttpServerException ( HttpServerException e ) 393 { 394 return true; 395 } 396 397 /************************************************************************** 398 399 Handles HTTP exception e which was thrown while parsing the request 400 message or from handleRequest() or request_msg_body_length(). 401 A subclass may override this method to be notified when an exception is 402 thrown and decide whether the connection may stay persistent or should 403 be closed after the response has been sent. 404 The default behaviour is allowing the connection being persistent unless 405 the status code indicated by the exception is 413: "Request Entity Too 406 Large". 407 408 Params: 409 e = HTTP server exception e which was thrown while parsing the 410 request message or from handleRequest() or 411 request_msg_body_length(). e.status reflects the response status 412 code and may be changed when overriding this method. 413 414 Returns: 415 true if the connection may stay persistent or false if it should be 416 closed after the response has been sent. 417 418 **************************************************************************/ 419 420 protected bool handleHttpException ( HttpException e ) 421 { 422 return e.status != e.status.RequestEntityTooLarge; 423 } 424 425 /************************************************************************** 426 427 Called when an IOWarning or IOError is caught. May be overridden by a 428 subclass to be notified. 429 430 An IOWarning is thrown when a socket read/write operation results in an 431 end-of-flow or hung-up condition, an IOError when an error event is 432 triggered for a socket. 433 434 Params: 435 e = caught IOWarning or IOError 436 is_error = true: e was an IOError, false: e was an IOWarning 437 438 **************************************************************************/ 439 440 protected void notifyIOException ( ErrnoException e, bool is_error ) { } 441 442 /************************************************************************** 443 444 Detects whether the connection should stay persistent or not. 445 446 Returns: 447 true if the connection should stay persistent or false if not 448 449 **************************************************************************/ 450 451 private bool keep_alive ( ) 452 { 453 switch (this.request.http_version) 454 { 455 case this.request.http_version.v1_1: 456 return !this.request.matches("connection", "close"); 457 458 case this.request.http_version.v1_0: 459 default: 460 return this.request.matches("connection", "keep-alive"); 461 } 462 } 463 }