1 /*******************************************************************************
2 
3     Base class for a connection handler for use with SelectListener.
4 
5     Copyright:
6         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
7         All rights reserved.
8 
9     License:
10         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
11         Alternatively, this file may be distributed under the terms of the Tango
12         3-Clause BSD License (see LICENSE_BSD.txt for details).
13 
14 *******************************************************************************/
15 
16 module ocean.net.server.connection.IConnectionHandler;
17 
18 
19 
20 
21 import ocean.core.Verify;
22 
23 import ocean.io.select.EpollSelectDispatcher;
24 
25 import ocean.net.server.connection.IConnectionHandlerInfo;
26 import ocean.io.select.client.model.ISelectClient : IAdvancedSelectClient;
27 
28 import ocean.io.select.protocol.generic.ErrnoIOException: SocketError;
29 
30 import ocean.io.device.IODevice: IInputDevice, IOutputDevice;
31 
32 import ocean.io.model.IConduit: ISelectable;
33 
34 import ocean.sys.socket.model.ISocket;
35 
36 debug ( ConnectionHandler ) import ocean.io.Stdout : Stderr;
37 
38 
39 /*******************************************************************************
40 
41     Connection handler abstract base class.
42 
43 *******************************************************************************/
44 
45 abstract class IConnectionHandler : IConnectionHandlerInfo,
46     IAdvancedSelectClient.IErrorReporter
47 {
48     /***************************************************************************
49 
50         Object pool index.
51 
52     ***************************************************************************/
53 
54     public size_t object_pool_index;
55 
56     /***************************************************************************
57 
58         Local aliases to avoid public imports.
59 
60     ***************************************************************************/
61 
62     public alias .EpollSelectDispatcher EpollSelectDispatcher;
63 
64     protected alias IAdvancedSelectClient.Event Event;
65 
66     /***************************************************************************
67 
68         Client connection socket, exposed to subclasses downcast to Conduit.
69 
70     ***************************************************************************/
71 
72     protected ISocket socket;
73 
74     /***************************************************************************
75 
76         SocketError instance to throw on error and query the current socket
77         error status.
78 
79     ***************************************************************************/
80 
81     protected SocketError socket_error;
82 
83     /***************************************************************************
84 
85         Alias for a finalizer delegate, which can be specified externally and is
86         called when the connection is shut down.
87 
88     ***************************************************************************/
89 
90     public alias void delegate ( typeof (this) instance ) FinalizeDg;
91 
92     /***************************************************************************
93 
94         Finalizer delegate which can be specified externally and is called when
95         the connection is shut down.
96 
97     ***************************************************************************/
98 
99     private FinalizeDg finalize_dg_ = null;
100 
101     /***************************************************************************
102 
103         Alias for an error delegate, which can be specified externally and is
104         called when a connection error occurs.
105 
106     ***************************************************************************/
107 
108     public alias void delegate ( Exception exception, Event event,
109         IConnectionHandlerInfo ) ErrorDg;
110 
111     /***************************************************************************
112 
113         Error delegate, which can be specified externally and is called when a
114         connection error occurs.
115 
116     ***************************************************************************/
117 
118     private ErrorDg error_dg_ = null;
119 
120     /***************************************************************************
121 
122         Instance id number in debug builds.
123 
124     ***************************************************************************/
125 
126     debug
127     {
128         static private uint connection_count;
129         public uint connection_id;
130     }
131 
132     /***************************************************************************
133 
134         Constructor
135 
136         Params:
137             socket       = the socket
138             error_dg_    = optional user-specified error handler, called when a
139                            connection error occurs
140 
141      ***************************************************************************/
142 
143     protected this ( ISocket socket, scope ErrorDg error_dg_ = null )
144     {
145         this(socket, null, error_dg_);
146     }
147 
148     /***************************************************************************
149 
150         Constructor
151 
152         Params:
153             socket       = the socket
154             finalize_dg_ = optional user-specified finalizer, called when the
155                            connection is shut down
156             error_dg_    = optional user-specified error handler, called when a
157                            connection error occurs
158 
159     ***************************************************************************/
160 
161     protected this ( ISocket socket, scope FinalizeDg finalize_dg_ = null,
162         scope ErrorDg error_dg_ = null )
163     {
164         verify(socket !is null);
165 
166         this.finalize_dg_ = finalize_dg_;
167         this.error_dg_ = error_dg_;
168 
169         this.socket = socket;
170 
171         this.socket_error = new SocketError(this.socket);
172 
173         debug this.connection_id = connection_count++;
174     }
175 
176     /***************************************************************************
177 
178         Sets the finalizer callback delegate which is called when the
179         connection is shut down. Setting to null disables the finalizer.
180 
181         Params:
182             finalize_dg_ = finalizer callback delegate
183 
184         Returns:
185             finalize_dg_
186 
187     ***************************************************************************/
188 
189     public FinalizeDg finalize_dg ( scope FinalizeDg finalize_dg_ )
190     {
191         return this.finalize_dg_ = finalize_dg_;
192     }
193 
194     /***************************************************************************
195 
196         Sets the error handler callback delegate which is called when a
197         connection error occurs. Setting to null disables the error handler.
198 
199         Params:
200             error_dg_ = error callback delegate
201 
202         Returns:
203             error_dg_
204 
205     ***************************************************************************/
206 
207     public ErrorDg error_dg ( scope ErrorDg error_dg_ )
208     {
209         return this.error_dg_ = error_dg_;
210     }
211 
212     /***************************************************************************
213 
214         Returns:
215             true if a client connection is currently established or false if
216             not.
217 
218     ***************************************************************************/
219 
220     public bool connected ( )
221     {
222         return this.socket.fileHandle >= 0;
223     }
224 
225     /***************************************************************************
226 
227         Returns:
228             I/O device instance (file descriptor under linux)
229 
230     ***************************************************************************/
231 
232     public ISelectable.Handle fileHandle ( )
233     {
234         return this.socket.fileHandle;
235     }
236 
237     /***************************************************************************
238 
239         Accepts a pending connection from listening_socket and assigns it to the
240         socket of this instance.
241 
242         Params:
243             listening_socket = the listening server socket for which a client
244                                connection is pending
245 
246     ***************************************************************************/
247 
248     public void assign ( ISelectable listening_socket )
249     {
250         verify(!this.connected,
251             "client connection was open before assigning");
252 
253         debug ( ConnectionHandler ) Stderr.formatln("[{}]: New connection", this.connection_id);
254 
255         if (this.socket.accept(listening_socket, true) < 0)
256         {
257             this.error(this.socket_error.setSock("error accepting connection", __FILE__, __LINE__));
258         }
259     }
260 
261     /***************************************************************************
262 
263         Called by the select listener right after the client connection has been
264         assigned.
265         If this method throws an exception, error() and finalize() will be
266         called by the select listener.
267 
268     ***************************************************************************/
269 
270     public abstract void handleConnection ( );
271 
272     /***************************************************************************
273 
274         Must be called by the subclass when finished handling the connection.
275         Will be automatically called by the select listener if assign() or
276         handleConnection() throws an exception.
277 
278         The closure of the socket after handling a connection is quite
279         sensitive. If a connection has actually been assigned, the socket must
280         be shut down *unless* an I/O error has been reported for the socket
281         because then it will already have been shut down automatically. The
282         abstract io_error() method is used to determine whether the an I/O error
283         was reported for the socket or not.
284 
285     ***************************************************************************/
286 
287     public void finalize ( )
288     {
289         if ( this.connected )
290         {
291             debug ( ConnectionHandler ) Stderr.formatln("[{}]: Closing connection", this.connection_id);
292 
293             if (this.io_error) if (this.socket.shutdown())
294             {
295                 this.error(this.socket_error.setSock("error closing connection", __FILE__, __LINE__));
296             }
297 
298             // The socket is closed below. However, this does not always mean
299             // that it is unregistered from epoll -- in situations where the
300             // process has forked, the fork's reference to the underlying kernel
301             // file description will prevent it from being unregistered until
302             // the fork exits. Therefore, to be certain that the socket will not
303             // fire again in epoll, we need to explicitly unregister it.
304             this.unregisterSocket();
305 
306             this.socket.close();
307         }
308 
309         if ( this.finalize_dg_ ) try
310         {
311             this.finalize_dg_(this);
312         }
313         catch ( Exception e )
314         {
315             this.error(e);
316         }
317     }
318 
319     /***************************************************************************
320 
321         IAdvancedSelectClient.IErrorReporter interface method. Called when a
322         connection error occurs.
323 
324         Params:
325             exception = exception which caused the error
326             event = epoll select event during which error occurred, if any
327 
328     ***************************************************************************/
329 
330     public void error ( Exception exception, Event event = Event.init )
331     {
332         debug ( ConnectionHandler ) try if ( this.io_error )
333         {
334             Stderr.formatln("[{}]: Caught io exception while handling connection: '{}' @ {}:{}",
335                     this.connection_id, exception.toString(), exception.file, exception.line);
336         }
337         else
338         {
339             debug ( ConnectionHandler ) Stderr.formatln("[{}]: Caught non-io exception while handling connection: '{}' @ {}:{}",
340                     this.connection_id, exception.toString(), exception.file, exception.line);
341         }
342         catch (Exception) { /* Theoretically io_error() could throw. */ }
343 
344         if ( this.error_dg_ )
345         {
346             this.error_dg_(exception, event, this);
347         }
348     }
349 
350     /***************************************************************************
351 
352         Formats information about the connection into the provided buffer. This
353         method is called from the SelectListener in order to log information
354         about the state of all connections in the pool.
355 
356         We format the following here:
357             * the file descriptor of the socket of this connection
358             * the remote ip and port of the socket
359             * whether an I/O error has occurred for the socket since the last
360               call to assign()
361 
362         Params:
363             buf = buffer to format into
364 
365     ***************************************************************************/
366 
367     public void formatInfo ( ref char[] buf )
368     {
369         this.socket.formatInfo(buf, this.io_error);
370     }
371 
372     /***************************************************************************
373 
374         Called by `finalize` to unregister the connection socket from epoll
375         before closing it. This is done because closing a socket does not always
376         mean that it is unregistered from epoll -- in situations where the
377         process has forked, the fork's reference to the underlying kernel file
378         description will prevent it from being unregistered until the fork
379         exits. Therefore, to be certain that the socket will not fire again in
380         epoll, we need to explicitly unregister it.
381 
382     ***************************************************************************/
383 
384     abstract protected void unregisterSocket ( );
385 
386     /***************************************************************************
387 
388         Tells whether an I/O error has been reported for the socket since the
389         last assign() call.
390 
391         Returns:
392             true if an I/O error has been reported for the socket or false
393             otherwise.
394 
395     ***************************************************************************/
396 
397     protected abstract bool io_error ( );
398 }