]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WORequestHandler.m
bind rq handler debug logging to WODebuggingEnabled
[sope] / sope-appserver / NGObjWeb / WORequestHandler.m
1 /*
2   Copyright (C) 2000-2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with OGo; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "WORequestHandler+private.h"
23 #include "WOApplication+private.h"
24 #include "WOContext+private.h"
25 #include <NGObjWeb/WOApplication.h>
26 #include <NGObjWeb/WOStatisticsStore.h>
27 #include <NGObjWeb/WOContext.h>
28 #include <NGObjWeb/WOCookie.h>
29 #include <NGObjWeb/WOComponent.h>
30 #include <NGObjWeb/WORequest.h>
31 #include <NGObjWeb/WOResponse.h>
32 #include <NGObjWeb/WOSession.h>
33 #include "common.h"
34
35 //#define USE_POOLS 1
36
37 #if APPLE_FOUNDATION_LIBRARY || NeXT_Foundation_LIBRARY
38 @interface NSObject(Miss)
39 - (id)subclassResponsibility:(SEL)cmd;
40 @end
41 #endif
42
43 @interface WOApplication(Privates)
44 - (void)_setCurrentContext:(WOContext *)_ctx;
45 @end
46
47 @implementation WORequestHandler
48
49 static BOOL     doNotSetCookiePath = NO;
50 static Class    NSDateClass        = Nil;
51 static NGLogger *logger            = nil;
52 static NGLogger *perfLogger        = nil;
53
54 + (int)version {
55   return 2;
56 }
57 + (void)initialize {
58   NSUserDefaults  *ud;
59   NGLoggerManager *lm;
60   static BOOL didInit = NO;
61
62   if (didInit)
63     return;
64   didInit = YES;
65
66   NSDateClass = [NSDate class];
67
68   lm         = [NGLoggerManager defaultLoggerManager];
69   logger     = [lm loggerForDefaultKey:@"WODebuggingEnabled"];
70   perfLogger = [lm loggerForDefaultKey:@"WOProfileRequestHandler"];
71
72   ud                 = [NSUserDefaults standardUserDefaults];
73   doNotSetCookiePath = [ud boolForKey:@"WOUseGlobalCookiePath"];
74 }
75
76 - (id)init {
77   if ((self = [super init])) {
78     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
79     
80     if ([ud boolForKey:@"WORunMultithreaded"])
81       self->lock = [[NSRecursiveLock alloc] init];
82   }
83   return self;
84 }
85
86 - (void)dealloc {
87   [self->lock release];
88   [super dealloc];
89 }
90
91 /* request handling */
92
93 - (BOOL)restoreSessionUsingIDs {
94   /* restore a session if an ID was given */
95   return YES;
96 }
97 - (BOOL)autocreateSessionForRequest:(WORequest *)_request {
98   /* autocreate a session if none was restored */
99   return NO;
100 }
101 - (BOOL)requiresSessionForRequest:(WORequest *)_request {
102   /* _ensure_ that a session is available */
103   return NO;
104 }
105
106 - (NSString *)sessionIDFromRequest:(WORequest *)_request
107   application:(WOApplication *)_app
108 {
109   NSString *sid;
110   
111   if ((sid = [_app sessionIDFromRequest:_request]) == nil)
112     return nil;
113   
114 #if DEBUG
115   NSAssert1([sid isKindOfClass:[NSString class]],
116             @"invalid session ID: %@", sid);
117 #endif
118   return sid;
119 }
120
121 - (WOResponse *)handleRequest:(WORequest *)_request
122   inContext:(WOContext *)context
123   session:(WOSession *)session
124   application:(WOApplication *)app
125 {
126   return [self subclassResponsibility:_cmd];
127 }
128
129 - (BOOL)doesRejectFavicon {
130   return YES;
131 }
132
133 - (WOResponse *)handleRequest:(WORequest *)_request {
134   NSTimeInterval    startHandling = 0.0;
135 #if USE_POOLS
136   NSAutoreleasePool *pool = nil;
137 #endif
138   WOApplication *app;
139   WOResponse    *response   = nil;
140   WOContext     *context    = nil;
141   NSThread      *thread;
142   NSString      *sessionId  = nil;
143   WOSession     *session    = nil;
144   NSString *uri;
145   
146   /* first check URI for favicon requests ... */
147   uri = [_request uri];
148   if ([self doesRejectFavicon] && uri != nil) {
149     if ([@"/favicon.ico" isEqualToString:uri]) {
150       response = [WOResponse responseWithRequest:_request];
151       [response setStatus:404 /* not found */];
152       [self debugWithFormat:@"rejected favicon request: %@", uri];
153       return response;
154     }
155   }
156   
157   if (perfLogger)
158     startHandling = [[NSDateClass date] timeIntervalSince1970];
159   
160   thread = [NSThread currentThread];
161   NSAssert(thread, @"missing current thread ...");
162   
163   if (_request == nil) return nil;
164
165   *(&app) = nil;
166   app = [WOApplication application];
167   
168 #if USE_POOLS
169   *(&pool) = [[NSAutoreleasePool alloc] init];
170 #endif
171   {
172     /* setup context */
173     context = [WOContext contextWithRequest:_request];
174     NSAssert(context, @"no context assigned ..");
175     [app _setCurrentContext:context];
176     
177     /* check session id */
178     *(&session)   = nil;
179     *(&sessionId) = [self sessionIDFromRequest:_request application:app];
180     
181     if ([sessionId length] == 0)
182       sessionId = nil;
183     else if ([sessionId isEqualToString:@"nil"])
184       sessionId = nil;
185     
186     NS_DURING {
187       [app awake];
188       
189       /* retrieve session */
190       if ([self restoreSessionUsingIDs]) {
191         SYNCHRONIZED(app) {
192           if (sessionId) {
193             session = [app restoreSessionWithID:sessionId
194                            inContext:context];
195             if (session == nil) {
196               response  = [app handleSessionRestorationErrorInContext:context];
197               sessionId = nil;
198             }
199           }
200         }
201         END_SYNCHRONIZED;
202         
203         [[session retain] autorelease];
204         
205         if (response != nil)
206           /* some kind of error response from above ... */
207           goto responseDone;
208         
209         if (session == nil) {
210           /* session autocreation .. */
211           if ([self autocreateSessionForRequest:_request]) {
212             if (![app isRefusingNewSessions]) {
213               session = [app _initializeSessionInContext:context];
214               
215               [self debugWithFormat:@"autocreated session: %@", session];
216               
217               if (session == nil)
218                 response =[app handleSessionRestorationErrorInContext:context];
219             }
220             else { /* app refuses new sessions */
221               [self logWithFormat:@"app is refusing new sessions ..."];
222               response = [app handleSessionRestorationErrorInContext:context];
223             }
224           }
225           if (response)
226             /* some kind of error response from above ... */
227             goto responseDone;
228           
229           /* check whether session is required ... */
230           if ([self requiresSessionForRequest:_request] && (session == nil)) {
231             response = [app handleSessionCreationErrorInContext:context];
232             goto responseDone;
233           }
234         }
235       }
236       
237       [session lock];
238       
239       NS_DURING {
240         response = [self handleRequest:_request
241                          inContext:context
242                          session:session
243                          application:app];
244         
245         session = [context hasSession]
246           ? [context session]
247           : nil;
248         
249         if (session != nil) {
250           if ([session storesIDsInCookies]) {
251             [self debugWithFormat:@"add cookie to session: %@", session];
252             [self addCookiesForSession:session
253                   toResponse:response
254                   inContext:context];
255           }
256           
257           [self saveSession:session
258                 inContext:context
259                 withResponse:response
260                 application:app];
261         }
262         else
263           [self debugWithFormat:@"no session to store."];
264       }
265       NS_HANDLER {
266         response = [app handleException:localException inContext:context];
267       }
268       NS_ENDHANDLER;
269       
270       [session unlock];
271       
272     responseDone:
273       [session _sleepWithContext:context];
274       response = [response retain];
275       
276       [app sleep];
277     }  
278     NS_HANDLER {
279       response = [app handleException:localException inContext:context];
280       response = [response retain];
281     }
282     NS_ENDHANDLER;
283     
284     [app _setCurrentContext:nil];
285   }
286 #if USE_POOLS
287   [pool release]; pool = nil;
288 #endif
289
290   [app lock];
291   if ([app isRefusingNewSessions] &&
292       ([app activeSessionsCount] < [app minimumActiveSessionsCount])) {
293     [self logWithFormat:
294             @"application terminates because it refuses new sessions and "
295             @"the active session count (%i) is below the minimum (%i).",
296             [app activeSessionsCount], [app minimumActiveSessionsCount]];
297     [app terminate];
298   }
299   [app unlock];
300   
301   if (perfLogger) {
302     NSTimeInterval rt;
303     rt = [[NSDateClass date] timeIntervalSince1970] - startHandling;
304     [perfLogger logWithFormat:@"handleRequest took %4.3fs.",
305                   rt < 0.0 ? -1.0 : rt];
306   }
307   
308   return [response autorelease];
309 }
310
311 /* locking */
312
313 - (void)lock {
314   [self->lock lock];
315 }
316 - (void)unlock {
317   [self->lock unlock];
318 }
319
320 /* KVC */
321
322 - (id)valueForUndefinedKey:(NSString *)_key {
323   [self debugWithFormat:@"queried undefined KVC key (returning nil): '%@'",
324           _key];
325   return nil;
326 }
327
328 /* logging */
329
330 - (id)logger {
331   return logger;
332 }
333
334 @end /* WORequestHandler */
335
336 @implementation WORequestHandler(Cookies)
337
338 - (void)addCookiesForSession:(WOSession *)_sn
339   toResponse:(WOResponse *)_response
340   inContext:(WOContext *)_ctx
341 {
342   WOApplication *app;
343   WOCookie *cookie     = nil;
344   NSString *uri;
345   NSString *value;
346   
347   if (![_sn storesIDsInCookies])
348     return;
349   
350   app = [WOApplication application];
351   
352   // TODO: there is a DUP of this section in OpenGroupware.m to set an
353   //       expiration cookie
354   if (!doNotSetCookiePath) {
355     NSString *tmp;
356       
357     if ((uri = [[_ctx request] applicationName]) == nil)
358       uri = [app name];
359     uri = [@"/" stringByAppendingString:uri];
360     if ((tmp = [[_ctx request] adaptorPrefix]))
361       uri = [tmp stringByAppendingString:uri];
362   }
363   else
364     uri = @"/";
365   
366 #if 0 // TODO: explain!
367   uri = [_ctx urlSessionPrefix];
368   uri = [_ctx urlWithRequestHandlerKey:
369                 [WOApplication componentRequestHandlerKey]
370               path:@"/"
371               queryString:nil];
372 #endif
373     
374   value = [_sn isTerminating]
375     ? (id)@"nil"
376     : [_sn sessionID];
377     
378   cookie = [WOCookie cookieWithName:[app name]
379                      value:value
380                      path:uri
381                      domain:[_sn domainForIDCookies]
382                      expires:[_sn expirationDateForIDCookies]
383                      isSecure:NO];
384   if (cookie != nil)
385     [_response addCookie:cookie];
386 }
387
388 @end /* WORequestHandler(Cookies) */
389
390 @implementation WORequest(DblClickBrowser)
391
392 - (BOOL)isDoubleClickBrowser {
393   return NO;
394 }
395
396 @end /* WORequest(DblClickBrowser) */
397
398 @implementation WORequestHandler(Support)
399
400 - (WOResponse *)doubleClickResponseForContext:(WOContext *)_ctx {
401   // DEPRECATED
402   return nil;
403 }
404
405 - (void)saveSession:(WOSession *)_session
406   inContext:(WOContext *)_ctx
407   withResponse:(WOResponse *)_response
408   application:(WOApplication *)_app
409 {
410   static BOOL perflog = NO;
411   NSTimeInterval startSaveSn = 0.0;
412   
413   if (_session == nil) return;
414
415   if (perflog)
416     startSaveSn = [[NSDate date] timeIntervalSince1970];
417   
418   [_app saveSessionForContext:_ctx];
419   
420   if (perflog) {
421     NSTimeInterval rt;
422     rt = [[NSDate date] timeIntervalSince1970] - startSaveSn;
423     NSLog(@"[rq]: saving of session took %4.3fs.",
424           rt < 0.0 ? -1.0 : rt);
425   }
426 }
427
428 - (void)_fixupResponse:(WOResponse *)_response {
429   NSString *ctype;
430   NSString *cntype = nil;
431   
432   if ((ctype = [_response headerForKey:@"content-type"]) == nil) {
433     NSData *body;
434     
435     ctype = @"text/html";
436     
437     body = [_response content];
438     if ([body length] > 6) {
439       const unsigned char *bytes;
440
441       if ((bytes = [body bytes])) {
442         if ((bytes[0] == '<') && (bytes[1] == '?')) {
443           if ((bytes[2] == 'x') && (bytes[3] == 'm') && (bytes[4] == 'l'))
444             ctype = @"text/xml";
445         }
446       }
447     }
448     
449     [_response setHeader:ctype forKey:@"content-type"];
450   }
451
452   if ([ctype isEqualToString:@"text/html"]) {
453     switch ([_response contentEncoding]) {
454       case NSISOLatin1StringEncoding:
455         cntype = [ctype stringByAppendingString:@"; charset=iso-8859-1"];
456         break;
457       case NSUTF8StringEncoding:
458         cntype = [ctype stringByAppendingString:@"; charset=utf-8"];
459         break;
460         
461       default:
462         break;
463     }
464   }
465   if (cntype)
466     [_response setHeader:cntype forKey:@"content-type"];
467 }
468
469 - (WOResponse *)generateResponseForComponent:(WOComponent *)_component
470   inContext:(WOContext *)_ctx
471   application:(WOApplication *)_app
472 {
473   WOResponse *response;
474   
475   if (_component == nil) return nil;
476   
477   /* make the component the "response page" */
478   [_ctx setPage:_component];
479   
480   if ([_ctx hasSession]) {
481     response = [_ctx response];
482     [_app appendToResponse:response inContext:_ctx];
483   }
484   else {
485     //[self logWithFormat:@"generating component using -generateResponse"];
486     response = [_component generateResponse];
487     
488     /* generate statistics */
489     [[_app statisticsStore]
490            recordStatisticsForResponse:response
491            inContext:_ctx];
492   }
493   
494   [self _fixupResponse:response];
495   return response;
496 }
497
498 @end /* WORequestHandler(Support) */