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 moduleocean.net.http.HttpConnectionHandler;
23 24 25 importocean.transition;
26 importocean.net.http.HttpRequest,
27 ocean.net.http.HttpResponse,
28 ocean.net.http.HttpException;
29 30 importocean.net.http.HttpConst: HttpResponseCode;
31 importocean.net.http.consts.HttpMethod: HttpMethod;
32 importocean.net.http.consts.HeaderFieldNames;
33 34 importocean.net.server.connection.IFiberConnectionHandler,
35 ocean.io.select.protocol.fiber.model.IFiberSelectProtocol;
36 37 importocean.sys.socket.AddressIPSocket;
38 importocean.sys.ErrnoException;
39 importocean.core.Enforce;
40 41 /******************************************************************************/42 43 abstractclassHttpConnectionHandler : IFiberConnectionHandler44 {
45 /**************************************************************************
46 47 Type aliases as convenience for a subclass
48 49 **************************************************************************/50 51 protectedalias .HttpResponseCodeHttpResponseCode;
52 protectedalias .HttpRequestHttpRequest;
53 protectedalias .HttpResponseHttpResponse;
54 protectedalias .HttpMethodHttpMethod;
55 protectedalias .HttpExceptionHttpException;
56 protectedalias .HttpServerExceptionHttpServerException;
57 protectedalias .ErrnoExceptionErrnoException;
58 protectedalias .IFiberSelectProtocol.IOErrorIOError;
59 protectedalias .IFiberSelectProtocol.IOWarningIOWarning;
60 61 /**************************************************************************
62 63 HTTP request message parser and response message generator
64 65 **************************************************************************/66 67 protectedHttpRequestrequest;
68 protectedHttpResponseresponse;
69 70 /**************************************************************************
71 72 Reused exception instance; may be thrown by a subclass as well.
73 74 **************************************************************************/75 76 protectedHttpExceptionhttp_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 protecteduintkeep_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 protectedautodefault_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 privatebool[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 protectedthis ( EpollSelectDispatcherdispatcher, scopeFinalizeDgfinalizer,
126 size_tstack_size,
127 HttpMethod[] supported_methods ... )
128 {
129 this(dispatcher, finalizer, stack_size,
130 newHttpRequest, newHttpResponse, 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 protectedthis ( EpollSelectDispatcherdispatcher, scopeFinalizeDgfinalizer,
151 size_tstack_size,
152 HttpRequestrequest, HttpResponseresponse,
153 HttpMethod[] supported_methods ... )
154 {
155 156 super(dispatcher, stack_size, newAddressIPSocket!(), 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 finalprotectedoverridevoidhandle ( )
177 {
178 boolkeep_alive = false;
179 180 uintn = 0;
181 182 try183 {
184 dotrytry185 {
186 HttpResponseCodestatus;
187 188 cstringresponse_msg_body;
189 190 try191 {
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 (HttpParseExceptione)
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 (HttpExceptione)
213 {
214 keep_alive &= this.handleHttpException(e);
215 status = e.status;
216 }
217 catch (HttpServerExceptione)
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 finally226 {
227 this.onResponseSent();
228 }
229 finally230 {
231 this.request.reset();
232 }
233 while (keep_alive);
234 }
235 catch (IOErrore)
236 {
237 this.notifyIOException(e, true);
238 }
239 catch (IOWarninge)
240 {
241 this.notifyIOException(e, false);
242 }
243 }
244 245 /**************************************************************************
246 247 Resettable interface method; resets the request.
248 249 **************************************************************************/250 251 publicoverridevoidreset ( )
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 abstractprotectedHttpResponseCodehandleRequest ( outcstringresponse_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 protectedvoidonResponseSent ( ) { }
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 privatevoidreceiveRequest ( )
303 {
304 super.reader.readConsume((void[] data)
305 {
306 size_tconsumed = this.request.parse(cast (char[]) data, this.request_msg_body_length);
307 308 returnthis.request.finished? consumed : data.length + 1;
309 });
310 311 enforce(this.http_exception.set(HttpResponseCode.NotImplemented),
312 this.request.methodinthis.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 privatevoidsendResponse ( HttpResponseCodestatus, cstringresponse_msg_body, boolkeep_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 protectedsize_trequest_msg_body_length ( )
367 {
368 return0;
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 protectedboolhandleHttpServerException ( HttpServerExceptione )
393 {
394 returntrue;
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 protectedboolhandleHttpException ( HttpExceptione )
421 {
422 returne.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 protectedvoidnotifyIOException ( ErrnoExceptione, boolis_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 privateboolkeep_alive ( )
452 {
453 switch (this.request.http_version)
454 {
455 casethis.request.http_version.v1_1:
456 return !this.request.matches("connection", "close");
457 458 casethis.request.http_version.v1_0:
459 default:
460 returnthis.request.matches("connection", "keep-alive");
461 }
462 }
463 }