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 }