]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WORequestHandler.m
minor code cleanups
[sope] / sope-appserver / NGObjWeb / WORequestHandler.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE 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   SOPE 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 SOPE; 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             if (logger != nil) /* Note: required! do not remove */
252               [self debugWithFormat:@"add cookie to session: %@", session];
253             [self addCookiesForSession:session
254                   toResponse:response
255                   inContext:context];
256           }
257           
258           [self saveSession:session
259                 inContext:context
260                 withResponse:response
261                 application:app];
262         }
263         else
264           [self debugWithFormat:@"no session to store."];
265       }
266       NS_HANDLER {
267         response = [app handleException:localException inContext:context];
268       }
269       NS_ENDHANDLER;
270       
271       [session unlock];
272       
273     responseDone:
274       [session _sleepWithContext:context];
275       response = [response retain];
276       
277       [app sleep];
278     }  
279     NS_HANDLER {
280       response = [app handleException:localException inContext:context];
281       response = [response retain];
282     }
283     NS_ENDHANDLER;
284     
285     [app _setCurrentContext:nil];
286   }
287 #if USE_POOLS
288   [pool release]; pool = nil;
289 #endif
290
291   [app lock];
292   if ([app isRefusingNewSessions] &&
293       ([app activeSessionsCount] < [app minimumActiveSessionsCount])) {
294     [self logWithFormat:
295             @"application terminates because it refuses new sessions and "
296             @"the active session count (%i) is below the minimum (%i).",
297             [app activeSessionsCount], [app minimumActiveSessionsCount]];
298     [app terminate];
299   }
300   [app unlock];
301   
302   if (perfLogger) {
303     NSTimeInterval rt;
304     rt = [[NSDateClass date] timeIntervalSince1970] - startHandling;
305     [perfLogger logWithFormat:@"handleRequest took %4.3fs.",
306                   rt < 0.0 ? -1.0 : rt];
307   }
308   
309   return [response autorelease];
310 }
311
312 /* locking */
313
314 - (void)lock {
315   [self->lock lock];
316 }
317 - (void)unlock {
318   [self->lock unlock];
319 }
320
321 /* KVC */
322
323 - (id)valueForUndefinedKey:(NSString *)_key {
324   [self debugWithFormat:@"queried undefined KVC key (returning nil): '%@'",
325           _key];
326   return nil;
327 }
328
329 /* logging */
330
331 - (id)logger {
332   return logger;
333 }
334
335 @end /* WORequestHandler */
336
337 @implementation WORequestHandler(Cookies)
338
339 - (void)addCookiesForSession:(WOSession *)_sn
340   toResponse:(WOResponse *)_response
341   inContext:(WOContext *)_ctx
342 {
343   WOApplication *app;
344   WOCookie *cookie     = nil;
345   NSString *uri;
346   NSString *value;
347   
348   if (![_sn storesIDsInCookies])
349     return;
350   
351   app = [WOApplication application];
352   
353   // TODO: there is a DUP of this section in OpenGroupware.m to set an
354   //       expiration cookie
355   if (!doNotSetCookiePath) {
356     NSString *tmp;
357       
358     if ((uri = [[_ctx request] applicationName]) == nil)
359       uri = [app name];
360     uri = [@"/" stringByAppendingString:uri];
361     if ((tmp = [[_ctx request] adaptorPrefix]))
362       uri = [tmp stringByAppendingString:uri];
363   }
364   else
365     uri = @"/";
366   
367 #if 0 // TODO: explain!
368   uri = [_ctx urlSessionPrefix];
369   uri = [_ctx urlWithRequestHandlerKey:
370                 [WOApplication componentRequestHandlerKey]
371               path:@"/"
372               queryString:nil];
373 #endif
374     
375   value = [_sn isTerminating]
376     ? (id)@"nil"
377     : [_sn sessionID];
378     
379   cookie = [WOCookie cookieWithName:[app name]
380                      value:value
381                      path:uri
382                      domain:[_sn domainForIDCookies]
383                      expires:[_sn expirationDateForIDCookies]
384                      isSecure:NO];
385   if (cookie != nil)
386     [_response addCookie:cookie];
387 }
388
389 @end /* WORequestHandler(Cookies) */
390
391 @implementation WORequest(DblClickBrowser)
392
393 - (BOOL)isDoubleClickBrowser {
394   return NO;
395 }
396
397 @end /* WORequest(DblClickBrowser) */
398
399 @implementation WORequestHandler(Support)
400
401 - (WOResponse *)doubleClickResponseForContext:(WOContext *)_ctx {
402   // DEPRECATED
403   return nil;
404 }
405
406 - (void)saveSession:(WOSession *)_session
407   inContext:(WOContext *)_ctx
408   withResponse:(WOResponse *)_response
409   application:(WOApplication *)_app
410 {
411   static BOOL perflog = NO;
412   NSTimeInterval startSaveSn = 0.0;
413   
414   if (_session == nil) return;
415
416   if (perflog)
417     startSaveSn = [[NSDate date] timeIntervalSince1970];
418   
419   [_app saveSessionForContext:_ctx];
420   
421   if (perflog) {
422     NSTimeInterval rt;
423     rt = [[NSDate date] timeIntervalSince1970] - startSaveSn;
424     NSLog(@"[rq]: saving of session took %4.3fs.",
425           rt < 0.0 ? -1.0 : rt);
426   }
427 }
428
429 - (void)_fixupResponse:(WOResponse *)_response {
430   NSString *ctype;
431   NSString *cntype = nil;
432   
433   if ((ctype = [_response headerForKey:@"content-type"]) == nil) {
434     NSData *body;
435     
436     ctype = @"text/html";
437     
438     body = [_response content];
439     if ([body length] > 6) {
440       const unsigned char *bytes;
441
442       if ((bytes = [body bytes])) {
443         if ((bytes[0] == '<') && (bytes[1] == '?')) {
444           if ((bytes[2] == 'x') && (bytes[3] == 'm') && (bytes[4] == 'l'))
445             ctype = @"text/xml";
446         }
447       }
448     }
449     
450     [_response setHeader:ctype forKey:@"content-type"];
451   }
452
453   if ([ctype isEqualToString:@"text/html"]) {
454     switch ([_response contentEncoding]) {
455       case NSISOLatin1StringEncoding:
456         cntype = [ctype stringByAppendingString:@"; charset=iso-8859-1"];
457         break;
458       case NSUTF8StringEncoding:
459         cntype = [ctype stringByAppendingString:@"; charset=utf-8"];
460         break;
461         
462       default:
463         break;
464     }
465   }
466   if (cntype)
467     [_response setHeader:cntype forKey:@"content-type"];
468 }
469
470 - (WOResponse *)generateResponseForComponent:(WOComponent *)_component
471   inContext:(WOContext *)_ctx
472   application:(WOApplication *)_app
473 {
474   WOResponse *response;
475   
476   if (_component == nil) return nil;
477   
478   /* make the component the "response page" */
479   [_ctx setPage:_component];
480   
481   if ([_ctx hasSession]) {
482     response = [_ctx response];
483     [_app appendToResponse:response inContext:_ctx];
484   }
485   else {
486     //[self logWithFormat:@"generating component using -generateResponse"];
487     response = [_component generateResponse];
488     
489     /* generate statistics */
490     [[_app statisticsStore]
491            recordStatisticsForResponse:response
492            inContext:_ctx];
493   }
494   
495   [self _fixupResponse:response];
496   return response;
497 }
498
499 @end /* WORequestHandler(Support) */