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