/* Copyright (C) 2000-2005 SKYRIX Software AG This file is part of SOPE. SOPE is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. SOPE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with SOPE; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include "common.h" #include "WORunLoop.h" #include "NGHttp+WO.h" #if LIB_FOUNDATION_LIBRARY # import #else # include "UnixSignalHandler.h" #endif //#define USE_POOLS 1 #if USE_POOLS # warning extensive pools are enabled ... #endif #include "WOHttpAdaptor.h" #include "WORecordRequestStream.h" #include "WOHttpTransaction.h" @interface WOHttpAdaptor(Server) /* accessors */ - (id)socket; - (id)serverAddress; - (void)setSendTimeout:(NSTimeInterval)_timeout; - (NSTimeInterval)sendTimeout; - (void)setReceiveTimeout:(NSTimeInterval)_timeout; - (NSTimeInterval)receiveTimeout; @end /* WOHttpAdaptor */ @interface WOCoreApplication(Port) - (NSNumber *)port; @end @implementation WOHttpAdaptor static NGLogger *logger = nil; static NGLogger *perfLogger = nil; static BOOL WOHttpAdaptor_LogStream = NO; static BOOL WOContactSNS = NO; static BOOL WOCoreOnHTTPAdaptorException = NO; static NSString *WOPort = nil; static int WOHttpAdaptorSendTimeout = 10; static int WOHttpAdaptorReceiveTimeout = 10; static id allow = nil; static BOOL debugOn = NO; + (BOOL)optionLogStream { return WOHttpAdaptor_LogStream; } + (BOOL)optionLogPerf { return perfLogger != nil ? YES : NO; } + (int)version { return [super version] + 1 /* v2 */; } + (void)initialize { NSUserDefaults *ud; NGLoggerManager *lm; static BOOL didInit = NO; if (didInit) return; didInit = YES; NSAssert2([super version] == 1, @"invalid superclass (%@) version %i !", NSStringFromClass([self superclass]), [super version]); ud = [NSUserDefaults standardUserDefaults]; lm = [NGLoggerManager defaultLoggerManager]; logger = [lm loggerForClass:self]; perfLogger = [lm loggerForDefaultKey:@"WOProfileHttpAdaptor"]; WOHttpAdaptor_LogStream = [ud boolForKey:@"WOHttpAdaptor_LogStream"]; // TODO: those two should be queried on demand to allow different defaults WOPort = [[ud stringForKey:@"WOPort"] copy]; WOContactSNS = [[ud objectForKey:@"WOContactSNS"] boolValue]; WOCoreOnHTTPAdaptorException = [[ud objectForKey:@"WOCoreOnHTTPAdaptorException"] boolValue] ? 1 : 0; WOHttpAdaptorSendTimeout = [ud integerForKey:@"WOHttpAdaptorSendTimeout"]; WOHttpAdaptorReceiveTimeout = [ud integerForKey:@"WOHttpAdaptorReceiveTimeout"]; if (allow == nil) { allow = [ud objectForKey:@"WOHttpAllowHost"]; if (allow == nil) { allow = [NSArray arrayWithObjects: @"localhost", @"localhost.localdomain", nil]; } if (![allow isKindOfClass:[NSArray class]]) allow = [NSArray arrayWithObject:allow]; allow = [allow copy]; } if (WOCoreOnHTTPAdaptorException) [logger warnWithFormat:@"will dump core on HTTP adaptor exception!"]; } - (id)autoBindAddress { NGInternetSocketAddress *addr; addr = [[NGInternetSocketAddress alloc] initWithPort:0 onHost:@"127.0.0.1"]; return [addr autorelease]; } - (id)initWithName:(NSString *)_name arguments:(NSDictionary *)_args application:(WOCoreApplication *)_application { if ((self = [super initWithName:_name arguments:_args application:_application])) { id arg = nil; if ([[_application recordingPath] length] > 0) WOHttpAdaptor_LogStream = YES; #if !defined(__MINGW32__) { UnixSignalHandler *us = [UnixSignalHandler sharedHandler]; [us addObserver:self selector:@selector(handleSIGPIPE:) forSignal:SIGPIPE immediatelyNotifyOnSignal:NO]; } #endif if ([_args count] < 1) { const char *cstr; self->address = nil; if ([WOPort isEqualToString:@"auto"]) self->address = [self autoBindAddress]; if ((self->address == nil) && ((cstr = [WOPort cString]) != NULL)) { if (isdigit(*cstr) && index(cstr, ':') == NULL) { NSNumber *p; p = [(WOCoreApplication *)[_application class] port]; if (p == nil) p = (id)WOPort; self->address = [NGInternetSocketAddress wildcardAddressWithPort:[p intValue]]; } } if (self->address == nil) self->address = NGSocketAddressFromString(WOPort); } else { NSString *arg = nil; if ((arg = [_args objectForKey:@"-p"])) { self->address = [NGInternetSocketAddress wildcardAddressWithPort:[arg intValue]]; } else if ((arg = [_args objectForKey:@"-WOPort"])) { const char *cstr; self->address = nil; if ([arg isEqualToString:@"auto"]) self->address = [self autoBindAddress]; if ((self->address == nil) && (cstr = [arg cString])) { if (isdigit(*cstr)) { self->address = [NGInternetSocketAddress wildcardAddressWithPort:[arg intValue]]; } } if (self->address == nil) self->address = NGSocketAddressFromString(arg); } } self->address = [self->address retain]; if (self->address == nil) { [_application errorWithFormat: @"got no address for HTTP server (using arg '%@')", arg]; [self release]; return nil; } if (!WOContactSNS) { [_application logWithFormat:@"%@ listening on address %@", NSStringFromClass([self class]), [(id)self->address stringValue]]; } self->lock = [[NSRecursiveLock alloc] init]; self->maxThreadCount = [[WOCoreApplication workerThreadCount] intValue]; [self setSendTimeout:WOHttpAdaptorSendTimeout]; [self setReceiveTimeout:WOHttpAdaptorReceiveTimeout]; } return self; } - (void)dealloc { [[UnixSignalHandler sharedHandler] removeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [self->lock release]; [self->socket release]; [self->address release]; [super dealloc]; } /* accessors */ - (id)socketAddress { /* used by sns */ return self->address; } /* events */ - (void)handleSIGPIPE:(int)_signal { [self warnWithFormat:@"caught SIGPIPE !"]; } - (void)registerForEvents { int backlog; backlog = [[WOCoreApplication listenQueueSize] intValue]; if (backlog == 0) backlog = 5; [self->socket release]; self->socket = nil; self->socket = [[NGPassiveSocket alloc] initWithDomain:[self->address domain]]; [self->socket bindToAddress:self->address]; if ([[self->address domain] isEqual:[NGInternetSocketDomain domain]]) { if ([(NGInternetSocketAddress *)self->address port] == 0) { /* let the kernel choose an IP address */ [self debugWithFormat:@"bound to wildcard: %@", self->address]; [self debugWithFormat:@"got local: %@", [self->socket localAddress]]; self->address = [[self->socket localAddress] retain]; [self logWithFormat:@"bound to kernel assigned address %@: %@", self->address, self->socket]; } } [self->socket listenWithBacklog:backlog]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(acceptConnection:) name:NSFileObjectBecameActiveNotificationName object:self->socket]; [(WORunLoop *)[WORunLoop currentRunLoop] addFileObject:self->socket activities:NSPosixReadableActivity forMode:NSDefaultRunLoopMode]; } - (void)unregisterForEvents { [(WORunLoop *)[WORunLoop currentRunLoop] removeFileObject:self->socket forMode:NSDefaultRunLoopMode]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [self->lock release]; self->lock = nil; [self->socket release]; self->socket = nil; } /* debugging */ - (BOOL)isDebuggingEnabled { return debugOn; } /* description */ - (NSString *)description { return [NSString stringWithFormat:@"<%@[0x%08X]: address=%@>", NSStringFromClass([self class]), self, self->address]; } /* Server */ /* accessors */ - (id)socket { return self->socket; } - (id)serverAddress { return [self->socket localAddress]; } - (void)setSendTimeout:(NSTimeInterval)_timeout { self->sendTimeout = _timeout; } - (NSTimeInterval)sendTimeout { return self->sendTimeout; } - (void)setReceiveTimeout:(NSTimeInterval)_timeout { self->receiveTimeout = _timeout; } - (NSTimeInterval)receiveTimeout { return self->receiveTimeout; } - (void)setMaxThreadCount:(int)_count { self->maxThreadCount = _count; } - (int)maxThreadCount { return self->maxThreadCount; } /* run-loop */ - (void)_serverCatched:(NSException *)_exc { [self errorWithFormat:@"http server caught: %@", _exc]; if (WOCoreOnHTTPAdaptorException) abort(); } - (BOOL)runConnection:(id)_socket { WOHttpTransaction *tx; if (_socket == nil) { [self errorWithFormat:@"got no socket for transaction ??"]; return NO; } tx = [[WOHttpTransaction alloc] initWithSocket:_socket application:self->application]; if (![tx run]) [self _serverCatched:[tx lastException]]; [tx release]; if ([self->application isTerminating]) self->isTerminated = YES; return YES; } - (void)_handleAcceptedConnection:(NGActiveSocket *)_connection { #if USE_POOLS NSAutoreleasePool *pool = nil; #endif NSTimeInterval t; if (perfLogger) *(&t) = [[NSDate date] timeIntervalSince1970]; [self->lock lock]; self->activeThreadCount++; [self->lock unlock]; #if USE_POOLS pool = [[NSAutoreleasePool alloc] init]; #endif { [*(&_connection) autorelease]; NS_DURING { [_connection setReceiveTimeout:self->receiveTimeout]; [_connection setSendTimeout:self->sendTimeout]; [self runConnection:_connection]; } NS_HANDLER { [self _serverCatched:localException]; } NS_ENDHANDLER; } #if USE_POOLS [pool release]; pool = nil; #endif [self->lock lock]; self->activeThreadCount--; [self->lock unlock]; if (perfLogger) { t = [[NSDate date] timeIntervalSince1970] - t; [perfLogger logWithFormat:@"handling of request took %4.3fs.", t < 0.0 ? -1.0 : t]; } } - (NSArray *)allowedHostNames { return allow; } - (id)_checkAccessOnConnection:(id)_connection{ static NGInternetSocketDomain *ipDomain = nil; id remote; if ((remote = [_connection remoteAddress]) == nil) { [self errorWithFormat:@"missing remote address for connection: %@", _connection]; return nil; } if (ipDomain == nil) ipDomain = [[NGInternetSocketDomain domain] retain]; /* always allow access for Unix domain sockets */ if (![[remote domain] isEqual:ipDomain]) return _connection; { /* check access */ NGInternetSocketAddress *ipAddr = (id)remote; NSArray *allow = nil; unsigned i, count; NSString *rh, *ra; allow = [self allowedHostNames]; rh = [ipAddr hostName]; ra = [ipAddr address]; /* first check address */ for (i = 0, count = [allow count]; i < count; i++) { NSString *h; h = [[allow objectAtIndex:i] stringValue]; if ([h isEqualToString:ra]) return _connection; } /* now check DNS names */ for (i = 0, count = [allow count]; i < count; i++) { NSString *h; h = [[allow objectAtIndex:i] stringValue]; if ([h isEqualToString:rh]) return _connection; } [self errorWithFormat:@"ACCESS DENIED: %@", ipAddr]; _connection = nil; } return _connection; } - (void)acceptConnection:(id)_notification { #if USE_POOLS NSAutoreleasePool *pool; *(&pool) = [[NSAutoreleasePool alloc] init]; #endif { NGActiveSocket *connection; NS_DURING { *(&connection) = (NGActiveSocket *)[self->socket accept]; if (connection == nil) [self _serverCatched:[self->socket lastException]]; else [self debugWithFormat:@"accepted connection: %@", connection]; } NS_HANDLER { connection = nil; [self _serverCatched:localException]; } NS_ENDHANDLER; connection = (NGActiveSocket *)[self _checkAccessOnConnection:connection]; if (connection != nil) { if (self->maxThreadCount <= 1) { NS_DURING [self _handleAcceptedConnection:[connection retain]]; NS_HANDLER [self _serverCatched:localException]; NS_ENDHANDLER; } else { [NSThread detachNewThreadSelector:@selector(_handleAcceptedConnection:) toTarget:self withObject:[connection retain]]; [self logWithFormat:@"detached new thread for request."]; //[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]; } connection = nil; } } #if USE_POOLS [pool release]; pool = nil; #endif if (self->isTerminated) { if (self->socket) { [[NSNotificationCenter defaultCenter] removeObserver:self name:NSFileObjectBecameActiveNotificationName object:self->socket]; [self->socket close]; [self->socket release]; self->socket = nil; } [self logWithFormat:@"adaptor stops application: %@ ...", self->application]; exit(0); } } @end /* WOHttpAdaptor */