2 * Copyright (C) 2007 Martin Willi
3 * Hochschule fuer Technik Rapperswil
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 #include "dispatcher.h"
26 #include <threading/thread.h>
27 #include <threading/condvar.h>
28 #include <threading/mutex.h>
29 #include <utils/linked_list.h>
30 #include <utils/hashtable.h>
32 /** Intervall to check for expired sessions, in seconds */
33 #define CLEANUP_INTERVAL 30
35 typedef struct private_dispatcher_t private_dispatcher_t
;
38 * private data of the task manager
40 struct private_dispatcher_t
{
58 * number of threads in "threads"
63 * session locking mutex
68 * Hahstable with active sessions
70 hashtable_t
*sessions
;
78 * timestamp of last session cleanup round
83 * running in debug mode?
88 * List of controllers controller_constructor_t
90 linked_list_t
*controllers
;
93 * List of filters filter_constructor_t
95 linked_list_t
*filters
;
98 * constructor function to create session context (in controller_entry_t)
100 context_constructor_t context_constructor
;
103 * user param to context constructor
109 /** constructor function */
110 controller_constructor_t constructor
;
111 /** parameter to constructor */
113 } controller_entry_t
;
116 /** constructor function */
117 filter_constructor_t constructor
;
118 /** parameter to constructor */
123 /** session instance */
125 /** condvar to wait for session */
127 /** client host address, to prevent session hijacking */
129 /** TRUE if session is in use */
131 /** last use of the session */
133 /** has the session been closed by the handler? */
138 * create a session and instanciate controllers
140 static session_t
* load_session(private_dispatcher_t
*this)
142 enumerator_t
*enumerator
;
143 controller_entry_t
*centry
;
144 filter_entry_t
*fentry
;
146 context_t
*context
= NULL
;
147 controller_t
*controller
;
150 if (this->context_constructor
)
152 context
= this->context_constructor(this->param
);
154 session
= session_create(context
);
156 enumerator
= this->controllers
->create_enumerator(this->controllers
);
157 while (enumerator
->enumerate(enumerator
, ¢ry
))
159 controller
= centry
->constructor(context
, centry
->param
);
160 session
->add_controller(session
, controller
);
162 enumerator
->destroy(enumerator
);
164 enumerator
= this->filters
->create_enumerator(this->filters
);
165 while (enumerator
->enumerate(enumerator
, &fentry
))
167 filter
= fentry
->constructor(context
, fentry
->param
);
168 session
->add_filter(session
, filter
);
170 enumerator
->destroy(enumerator
);
176 * create a new session entry
178 static session_entry_t
*session_entry_create(private_dispatcher_t
*this,
181 session_entry_t
*entry
;
184 .cond
= condvar_create(CONDVAR_TYPE_DEFAULT
),
185 .session
= load_session(this),
186 .used
= time_monotonic(NULL
),
187 .host
= strdup(host
),
195 static void session_entry_destroy(session_entry_t
*entry
)
197 entry
->session
->destroy(entry
->session
);
198 entry
->cond
->destroy(entry
->cond
);
203 METHOD(dispatcher_t
, add_controller
, void,
204 private_dispatcher_t
*this, controller_constructor_t constructor
,
207 controller_entry_t
*entry
;
210 .constructor
= constructor
,
213 this->controllers
->insert_last(this->controllers
, entry
);
216 METHOD(dispatcher_t
, add_filter
, void,
217 private_dispatcher_t
*this, filter_constructor_t constructor
, void *param
)
219 filter_entry_t
*entry
;
222 .constructor
= constructor
,
225 this->filters
->insert_last(this->filters
, entry
);
229 * Hashtable hash function
231 static u_int
session_hash(char *sid
)
233 return chunk_hash(chunk_create(sid
, strlen(sid
)));
237 * Hashtable equals function
239 static bool session_equals(char *sid1
, char *sid2
)
241 return streq(sid1
, sid2
);
245 * Cleanup unused sessions
247 static void cleanup_sessions(private_dispatcher_t
*this, time_t now
)
249 if (this->last_cleanup
< now
- CLEANUP_INTERVAL
)
252 session_entry_t
*entry
;
253 enumerator_t
*enumerator
;
254 linked_list_t
*remove
;
256 this->last_cleanup
= now
;
257 remove
= linked_list_create();
258 enumerator
= this->sessions
->create_enumerator(this->sessions
);
259 while (enumerator
->enumerate(enumerator
, &sid
, &entry
))
261 /* check all sessions for timeout or close flag */
262 if (!entry
->in_use
&&
263 (entry
->used
< now
- this->timeout
|| entry
->closed
))
265 remove
->insert_last(remove
, sid
);
268 enumerator
->destroy(enumerator
);
270 while (remove
->remove_last(remove
, (void**)&sid
) == SUCCESS
)
272 entry
= this->sessions
->remove(this->sessions
, sid
);
275 session_entry_destroy(entry
);
278 remove
->destroy(remove
);
283 * Actual dispatching code
285 static void dispatch(private_dispatcher_t
*this)
287 thread_cancelability(FALSE
);
292 session_entry_t
*found
= NULL
;
296 thread_cancelability(TRUE
);
297 request
= request_create(this->fd
, this->debug
);
298 thread_cancelability(FALSE
);
304 now
= time_monotonic(NULL
);
305 sid
= request
->get_cookie(request
, "SID");
307 this->mutex
->lock(this->mutex
);
310 found
= this->sessions
->get(this->sessions
, sid
);
312 if (found
&& !streq(found
->host
, request
->get_host(request
)))
318 /* wait until session is unused */
319 while (found
->in_use
)
321 found
->cond
->wait(found
->cond
, this->mutex
);
325 { /* create a new session if not found */
326 found
= session_entry_create(this, request
->get_host(request
));
327 sid
= found
->session
->get_sid(found
->session
);
328 this->sessions
->put(this->sessions
, sid
, found
);
330 found
->in_use
= TRUE
;
331 this->mutex
->unlock(this->mutex
);
333 /* start processing */
334 found
->session
->process(found
->session
, request
);
335 found
->used
= time_monotonic(NULL
);
337 /* release session */
338 this->mutex
->lock(this->mutex
);
339 found
->in_use
= FALSE
;
340 found
->closed
= request
->session_closed(request
);
341 found
->cond
->signal(found
->cond
);
342 cleanup_sessions(this, now
);
343 this->mutex
->unlock(this->mutex
);
345 request
->destroy(request
);
349 METHOD(dispatcher_t
, run
, void,
350 private_dispatcher_t
*this, int threads
)
352 this->thread_count
= threads
;
353 this->threads
= malloc(sizeof(thread_t
*) * threads
);
356 this->threads
[threads
- 1] = thread_create((thread_main_t
)dispatch
,
358 if (this->threads
[threads
- 1])
365 METHOD(dispatcher_t
, waitsignal
, void,
366 private_dispatcher_t
*this)
372 sigaddset(&set
, SIGINT
);
373 sigaddset(&set
, SIGTERM
);
374 sigaddset(&set
, SIGHUP
);
375 sigprocmask(SIG_BLOCK
, &set
, NULL
);
379 METHOD(dispatcher_t
, destroy
, void,
380 private_dispatcher_t
*this)
383 session_entry_t
*entry
;
384 enumerator_t
*enumerator
;
386 FCGX_ShutdownPending();
387 while (this->thread_count
--)
389 thread_t
*thread
= this->threads
[this->thread_count
];
390 thread
->cancel(thread
);
391 thread
->join(thread
);
393 enumerator
= this->sessions
->create_enumerator(this->sessions
);
394 while (enumerator
->enumerate(enumerator
, &sid
, &entry
))
396 session_entry_destroy(entry
);
398 enumerator
->destroy(enumerator
);
399 this->sessions
->destroy(this->sessions
);
400 this->controllers
->destroy_function(this->controllers
, free
);
401 this->filters
->destroy_function(this->filters
, free
);
402 this->mutex
->destroy(this->mutex
);
410 dispatcher_t
*dispatcher_create(char *socket
, bool debug
, int timeout
,
411 context_constructor_t constructor
, void *param
)
413 private_dispatcher_t
*this;
417 .add_controller
= _add_controller
,
418 .add_filter
= _add_filter
,
420 .waitsignal
= _waitsignal
,
423 .sessions
= hashtable_create((void*)session_hash
,
424 (void*)session_equals
, 4096),
425 .controllers
= linked_list_create(),
426 .filters
= linked_list_create(),
427 .context_constructor
= constructor
,
428 .mutex
= mutex_create(MUTEX_TYPE_DEFAULT
),
431 .last_cleanup
= time_monotonic(NULL
),
440 this->fd
= FCGX_OpenSocket(socket
, 10);
442 return &this->public;