From c9ed8204191ea56e50fb2b51c7239dd8244bfd24 Mon Sep 17 00:00:00 2001 From: CaS Date: Mon, 14 Nov 2005 20:37:33 +0000 Subject: [PATCH] Simplify library ... move portions into WebServer and Performance libraries git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/sqlclient/trunk@22006 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 6 + GNUmakefile | 30 +- Performance.import | 3 + README | 32 - SQLClient.h | 144 +--- SQLClient.jigs | 28 +- SQLClient.m | 594 +------------- WebServer.h | 611 -------------- WebServer.m | 1910 -------------------------------------------- WebServerBundles.m | 248 ------ testPostgres.m | 5 +- testWebServer.m | 85 -- 12 files changed, 36 insertions(+), 3660 deletions(-) create mode 100644 Performance.import delete mode 100644 WebServer.h delete mode 100644 WebServer.m delete mode 100644 WebServerBundles.m delete mode 100644 testWebServer.m diff --git a/ChangeLog b/ChangeLog index e24e07b..9d1f46e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2005-11-14 Richard Frith-Macdonald + + Factor out WebServer into separate library, and timer and caching + stuff into Performance library. Make this library depend on the + Performance library. + 2005-10-27 Richard Frith-Macdonald * WebServer.m: Add more accurate timestamps and implement request diff --git a/GNUmakefile b/GNUmakefile index 9478b3d..8a778cb 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -3,7 +3,7 @@ include $(GNUSTEP_MAKEFILES)/common.make -include config.make PACKAGE_NAME = SQLClient -PACKAGE_VERSION = 1.1.0 +PACKAGE_VERSION = 1.2.0 CVS_MODULE_NAME = gnustep/dev-libs/SQLClient CVS_TAG_NAME = SQLClient @@ -12,35 +12,16 @@ TEST_TOOL_NAME= LIBRARY_NAME=SQLClient DOCUMENT_NAME=SQLClient -SQLClient_INTERFACE_VERSION=1.1 +SQLClient_INTERFACE_VERSION=1.2 SQLClient_OBJC_FILES = SQLClient.m -SQLClient_LIBRARIES_DEPEND_UPON = +SQLClient_LIBRARIES_DEPEND_UPON = -lPerformance SQLClient_HEADER_FILES = SQLClient.h SQLClient_AGSDOC_FILES = SQLClient.h # Optional Java wrappers for the library JAVA_WRAPPER_NAME = SQLClient -# -# Assume that the use of the gnu runtime means we have the gnustep -# base library and can use its extensions to build WebServer stuff. -# -ifeq ($(OBJC_RUNTIME_LIB),gnu) -APPLE=0 -else -APPLE=1 -endif - -ifeq ($(APPLE),1) -ADDITIONAL_OBJC_LIBS += -lgnustep-baseadd -SQLClient_LIBRARIES_DEPEND_UPON = -lgnustep-baseadd -else -SQLClient_OBJC_FILES += WebServer.m WebServerBundles.m -SQLClient_HEADER_FILES += WebServer.h -SQLClient_AGSDOC_FILES += WebServer.h -endif - SQLClient_HEADER_FILES_INSTALL_DIR = SQLClient BUNDLE_NAME= @@ -148,11 +129,6 @@ Oracle_libs_BUNDLE_LIBS += -lclntsh \ Oracle_libs_PRINCIPAL_CLASS = SQLClientOracle_libs endif -TEST_TOOL_NAME+=testWebServer -testWebServer_OBJC_FILES = testWebServer.m -testWebServer_TOOL_LIBS += -lSQLClient -testWebServer_LIB_DIRS += -L./obj - -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/library.make diff --git a/Performance.import b/Performance.import new file mode 100644 index 0000000..a8b238b --- /dev/null +++ b/Performance.import @@ -0,0 +1,3 @@ + +import gnu.gnustep.Performance.*; + diff --git a/README b/README index 236d20c..952e1df 100644 --- a/README +++ b/README @@ -80,35 +80,3 @@ should be entered on the GNUstep project page - -The WebServer class provides the framework for a GNUstep program to act as -an HTTP or HTTPS server for simple applications. -It does not attempt to be a general-purpose web server, but is rather -intended to permit a program to easily handle requests from automated -systems which are intended to control, monitor, or use the services -provided by the program in which the class is embedded. -The emphasis is on making it robust/reliable/simple, so you can rapidly -develop software using it. It is a single-threaded, single-process -system using asynchronous I/O, so you can easily run it under debug -in gdb to fix any bugs in your delegate object. -In particular of course, it may be used in conjunction with the -SQLClient class to implement web-based database applications. - -The class is controlled by a few straightforward settings and basically -operates by handing over requests to its delegate. The delegate must at -least implement the -processRequest:response:for: method. - -Built-in facilities include - - -* Parsing of parameter string in request URL -* Parsing of url encoded form data in a POST request -* Parsing of form encoded data in a POST request -* Substitution into template pages on output -* SSL support -* Limit access by IP address -* Limit total number of simultaneous connections -* Limit number of simultaneous connectionsform one address -* Limit idle time permitted on a connection -* Limit size of request headers permitted -* Limit size of request body permitted - diff --git a/SQLClient.h b/SQLClient.h index 78ae2be..44c8f65 100644 --- a/SQLClient.h +++ b/SQLClient.h @@ -38,12 +38,6 @@ faster, easier to use, and easier to add new database backends for than JDBC).

-

- The library also provides a framework for making such simple database - applications readily available as web applications using the - [WebServer] class, which allows your applications to act as stand-alone - web servers. -

The major features of the SQLClient library are -

@@ -177,12 +171,12 @@ #include +@class GSCache; @class NSData; @class NSDate; @class NSMutableSet; @class NSRecursiveLock; @class NSString; -@class SQLCache; @class SQLTransaction; /** @@ -322,7 +316,7 @@ extern unsigned SQLClientTimeTick(); NSTimeInterval _lastOperation; NSTimeInterval _duration; unsigned int _debugging; /** The current debugging level */ - SQLCache *_cache; /** The cache for query results */ + GSCache *_cache; /** The cache for query results */ } /** @@ -960,136 +954,6 @@ extern unsigned SQLClientTimeTick(); - (void) setDurationLogging: (NSTimeInterval)threshold; @end -/** - * The SQLCache class is used to maintain a cache of objects.
- * When full, old objects are removed to make room for new ones - * on a least-recently-used basis.
- * Cache sizes may be limited by the number of objects in the cache, - * or by the memory used by the cache, or both. Calculation of the - * size of items in the cache is relatively expensive, so caches are - * only limited by number of objects in the default case.
- * Objects stored in the cache may be given a limited lifetime, - * in which case an attempt to fetch an expired object - * from the cache will cause it to be removed from the cache instead. - */ -@interface SQLCache : NSObject - -/** - * Return all the current cache instances... useful if you want to do - * something to all cache instances in your process. - */ -+ (NSArray*) allCaches; - -/** - * Return a report on all SQLCache instances ... calls the -description - * method of the individual cache instances to get a report on each one. - */ -+ (NSString*) description; - -/** - * Return the count of objects currently in the cache. - */ -- (unsigned) currentObjects; - -/** - * Return the total size of the objects currently in the cache. - */ -- (unsigned) currentSize; - -/** - * Return the default lifetime for items set in the cache.
- * A value of zero means that items are not purged based on lifetime. - */ -- (unsigned) lifetime; - -/** - * Return the maximum number of items in the cache.
- * A value of zero means there is no limit. - */ -- (unsigned) maxObjects; - -/** - * Return the maximum tital size of items in the cache.
- * A value of zero means there is no limit. - */ -- (unsigned) maxSize; - -/** - * Return the name of this instance (as set using -setName:) - */ -- (NSString*) name; - -/** - * Return the cached value for the specified key, or nil if there - * is no value in the cache. - */ -- (id) objectForKey: (NSString*)aKey; - -/** - * Remove all items whose lifetimes have passed - * (if lifetimes are in use for the cache).
- * The 'current' timestamp is that of the last call to SQLClientTimeNow(), - * which is usually the timestmap of the most recent SQL query. - */ -- (void) purge; - -/** - * Sets the lifetime (seconds) for items added to the cache. If this - * is set to zero then items are not removed from the cache based on - * lifetimes when the cache is full and an object is added, though - * expired items are still removed when an attempt to retrieve - * them is made. - */ -- (void) setLifetime: (unsigned)max; - -/** - * Sets the maximum number of objects in the cache. If this is non-zero - * then an attempt to set an object in a full cache will result in the - * least recently used item in the cache being removed. - */ -- (void) setMaxObjects: (unsigned)max; - -/** - * Sets the maximum total size for objects in the cache. If this is non-zero - * then an attempt to set an object whose size would exceed the cache limit - * will result in the least recently used items in the cache being removed. - */ -- (void) setMaxSize: (unsigned)max; - -/** - * Sets the name of this instance. - */ -- (void) setName: (NSString*)name; - -/** - * Sets (or replaces)the cached value for the specified key. - */ -- (void) setObject: (id)anObject forKey: (NSString*)aKey; - -/** - * Sets (or replaces)the cached value for the specified key, giving - * the value the specified lifetime (in seconds). A lifetime of zero - * means that the item is not limited by lifetime. - */ -- (void) setObject: (id)anObject - forKey: (NSString*)aKey - lifetime: (unsigned)lifetime; - -/** - * Called by -setObject:forKey:lifetime: to make space for a new - * object in the cache (also when the cache is resized).
- * This will, if a lifetime is set (see the -setLifetime: method) - * first purge all expired objects from the cache, then - * (if necessary) remove objects from the cache until the number - * of objects and size of cache meet the limits specified.
- * If the objects argument is zero then all objects are removed from - * the cache.
- * The size argument is used only if a maximum size is set - * for the cache. - */ -- (void) shrinkObjects: (unsigned)objects andSize: (unsigned)size; -@end - /** * This category porovides methods for caching the results of queries * in order to reduce the number of client-server trips and the database @@ -1102,7 +966,7 @@ extern unsigned SQLClientTimeTick(); * Returns the cache used by the receiver for storing the results of * requests made through it. */ -- (SQLCache*) cache; +- (GSCache*) cache; /** * If the result of the query is already cached and is still valid, @@ -1148,7 +1012,7 @@ extern unsigned SQLClientTimeTick(); * If aCache is nil, the current cache is released, and a new cache will * be automatically created as soon as there is a need to cache anything. */ -- (void) setCache: (SQLCache*)aCache; +- (void) setCache: (GSCache*)aCache; @end diff --git a/SQLClient.jigs b/SQLClient.jigs index e44e22c..0a59d08 100644 --- a/SQLClient.jigs +++ b/SQLClient.jigs @@ -1,36 +1,14 @@ { /* -*-c-*- */ "prerequisite libraries" = ( - "gnustep-base" + "gnustep-base", + "Performance" ); types = { NSTimeInterval = double; }; classes = ( { - "java name" = "gnu.gnustep.SQLClient.SQLCache"; - "objective-c name" = "SQLCache"; - "class methods" = ( - "allCaches" - ); - "instance methods" = ( - "currentObjects", - "currentSize", - "lifetime", - "maxObjects", - "maxSize", - "name", - "objectForKey:", - "purge", - "setLifetime:", - "setMaxObjects:", - "setMaxSize:", - "setName:", - "setObject:forKey:", - "setObject:forKey:lifetime:", - "shrinkObjects:andSize:" - ); - }, - { + "file to include in preamble java code" = "Performance.import"; "java name" = "gnu.gnustep.SQLClient.SQLClient"; "objective-c name" = "SQLClient"; "class methods" = ( diff --git a/SQLClient.m b/SQLClient.m index 231bcc5..f729f2e 100644 --- a/SQLClient.m +++ b/SQLClient.m @@ -48,6 +48,7 @@ #include #include +#include #include "SQLClient.h" @@ -59,126 +60,6 @@ static NSNull *null = nil; static Class NSStringClass = 0; static Class NSDateClass = 0; -static SEL tiSel = 0; -static NSTimeInterval (*tiImp)(Class,SEL) = 0; - -static NSTimeInterval baseTime = 0; -static NSTimeInterval lastTime = 0; - -inline NSTimeInterval SQLClientTimeLast() -{ - return lastTime; -} - -inline NSTimeInterval SQLClientTimeNow() -{ - if (baseTime == 0) - { - NSDateClass = [NSDate class]; - tiSel = @selector(timeIntervalSinceReferenceDate); - tiImp - = (NSTimeInterval (*)(Class,SEL))[NSDateClass methodForSelector: tiSel]; - baseTime = lastTime = (*tiImp)(NSDateClass, tiSel); - return baseTime; - } - else - { - NSTimeInterval now = (*tiImp)(NSDateClass, tiSel); - - /* - * If the clock has been reset so that time has gone backwards, - * we adjust the baseTime so that lastTime >= baseTime is true. - */ - if (now < lastTime) - { - baseTime -= (lastTime - now); - } - lastTime = now; - return lastTime; - } -} - -inline NSTimeInterval SQLClientTimeStart() -{ - if (baseTime == 0) - { - return SQLClientTimeNow(); - } - return baseTime; -} - -inline unsigned SQLClientTimeTick() -{ - NSTimeInterval start = SQLClientTimeStart(); - - return (SQLClientTimeLast() - start) + 1; -} - -@interface NSArray (SizeInBytes) -- (unsigned) sizeInBytes: (NSMutableSet*)exclude; -@end - -@interface NSData (SizeInBytes) -- (unsigned) sizeInBytes: (NSMutableSet*)exclude; -@end - -@interface NSObject (SizeInBytes) -- (unsigned) sizeInBytes: (NSMutableSet*)exclude; -@end - -@interface NSString (SizeInBytes) -- (unsigned) sizeInBytes: (NSMutableSet*)exclude; -@end - -@implementation NSArray (SizeInBytes) -- (unsigned) sizeInBytes: (NSMutableSet*)exclude -{ - if ([exclude member: self] != nil) - { - return 0; - } - else - { - unsigned count = [self count]; - unsigned size = [super sizeInBytes: exclude] + count*sizeof(void*); - - if (exclude == nil) - { - exclude = [NSMutableSet setWithCapacity: 8]; - } - [exclude addObject: self]; - while (count-- > 0) - { - size += [[self objectAtIndex: count] sizeInBytes: exclude]; - } - return size; - } -} -@end - -@implementation NSData (SizeInBytes) -- (unsigned) sizeInBytes: (NSMutableSet*)exclude -{ - if ([exclude member: self] != nil) return 0; - return [super sizeInBytes: exclude] + [self length]; -} -@end - -@implementation NSObject (SizeInBytes) -- (unsigned) sizeInBytes: (NSMutableSet*)exclude -{ - if ([exclude member: self] != nil) return 0; - return isa->instance_size; -} -@end - -@implementation NSString (SizeInBytes) -- (unsigned) sizeInBytes: (NSMutableSet*)exclude -{ - if ([exclude member: self] != nil) return 0; - return [super sizeInBytes: exclude] + sizeof(unichar) * [self length]; -} -@end @implementation SQLRecord + (id) allocWithZone: (NSZone*)aZone @@ -189,7 +70,7 @@ inline unsigned SQLClientTimeTick() + (void) initialize { - SQLClientTimeNow(); + GSTickerTimeNow(); if (null == nil) { null = [NSNull new]; @@ -542,7 +423,7 @@ static unsigned int maxConnections = 8; + (void) initialize { - SQLClientTimeNow(); + GSTickerTimeNow(); if (null == nil) { null = [NSNull new]; @@ -1215,10 +1096,10 @@ static void quoteString(NSMutableString *s) if (_duration >= 0) { - start = SQLClientTimeNow(); + start = GSTickerTimeNow(); } [self backendExecute: info]; - _lastOperation = SQLClientTimeNow(); + _lastOperation = GSTickerTimeNow(); [_statements addObject: statement]; if (_duration >= 0) { @@ -1289,10 +1170,10 @@ static void quoteString(NSMutableString *s) if (_duration >= 0) { - start = SQLClientTimeNow(); + start = GSTickerTimeNow(); } result = [self backendQuery: stmt]; - _lastOperation = SQLClientTimeNow(); + _lastOperation = GSTickerTimeNow(); if (_duration >= 0) { NSTimeInterval d; @@ -1787,7 +1668,7 @@ static void quoteString(NSMutableString *s) */ + (void) _tick: (NSTimer*)t { - SQLClientTimeNow(); + GSTickerTimeNow(); } @end @@ -1906,11 +1787,11 @@ static void quoteString(NSMutableString *s) @implementation SQLClient (Caching) -- (SQLCache*) cache +- (GSCache*) cache { if (_cache == nil) { - _cache = [SQLCache new]; + _cache = [GSCache new]; } return _cache; } @@ -1942,8 +1823,8 @@ static void quoteString(NSMutableString *s) [lock lock]; NS_DURING { - NSTimeInterval start = SQLClientTimeNow(); - SQLCache *c = [self cache]; + NSTimeInterval start = GSTickerTimeNow(); + GSCache *c = [self cache]; id toCache = nil; if (seconds < 0) @@ -1958,7 +1839,7 @@ static void quoteString(NSMutableString *s) if (result == nil) { result = toCache = [self backendQuery: stmt]; - _lastOperation = SQLClientTimeNow(); + _lastOperation = GSTickerTimeNow(); if (_duration >= 0) { NSTimeInterval d; @@ -2002,7 +1883,7 @@ static void quoteString(NSMutableString *s) return result; } -- (void) setCache: (SQLCache*)aCache +- (void) setCache: (GSCache*)aCache { ASSIGN(_cache, aCache); } @@ -2125,450 +2006,3 @@ static void quoteString(NSMutableString *s) } @end - -@interface SQLCItem : NSObject -{ -@public - SQLCItem *next; - SQLCItem *prev; - unsigned when; - unsigned size; - NSString *key; - id object; -} -+ (SQLCItem*) newWithObject: (id)anObject forKey: (NSString*)aKey; -@end - -@implementation SQLCItem -+ (SQLCItem*) newWithObject: (id)anObject forKey: (NSString*)aKey -{ - SQLCItem *i; - - i = (SQLCItem*)NSAllocateObject(self, 0, NSDefaultMallocZone()); - i->object = RETAIN(anObject); - i->key = [aKey copy]; - return i; -} -- (void) dealloc -{ - RELEASE(key); - RELEASE(object); - NSDeallocateObject(self); -} -@end - - -@implementation SQLCache - -static NSHashTable *SQLCacheInstances = 0; -static NSLock *SQLCacheLock = nil; - -typedef struct { - unsigned currentObjects; - unsigned currentSize; - unsigned lifetime; - unsigned maxObjects; - unsigned maxSize; - unsigned hits; - unsigned misses; - NSMapTable *contents; - SQLCItem *first; - NSString *name; - NSMutableSet *exclude; -} SQLC; -#define my ((SQLC*)&self[1]) - -/* - * Add item to linked list starting at *first - */ -static void appendItem(SQLCItem *item, SQLCItem **first) -{ - if (*first == nil) - { - item->next = item->prev = item; - *first = item; - } - else - { - (*first)->prev->next = item; - item->prev = (*first)->prev; - (*first)->prev = item; - item->next = *first; - } -} - -/* - * Remove item from linked list starting at *first - */ -static void removeItem(SQLCItem *item, SQLCItem **first) -{ - if (*first == item) - { - if (item->next == item) - { - *first = nil; - } - else - { - *first = item->next; - } - } - item->next->prev = item->prev; - item->prev->next = item->next; - item->prev = item->next = item; -} - -+ (NSArray*) allCaches -{ - NSArray *a; - - [SQLCacheLock lock]; - a = NSAllHashTableObjects(SQLCacheInstances); - [SQLCacheLock unlock]; - return a; -} - -+ (id) alloc -{ - return [self allocWithZone: NSDefaultMallocZone()]; -} - -+ (id) allocWithZone: (NSZone*)z -{ - SQLCache *c; - - c = (SQLCache*)NSAllocateObject(self, sizeof(SQLC), z); - [SQLCacheLock lock]; - NSHashInsert(SQLCacheInstances, (void*)c); - [SQLCacheLock unlock]; - return c; -} - -+ (NSString*) description -{ - NSMutableString *ms; - NSHashEnumerator e; - SQLCache *c; - - ms = [NSMutableString stringWithString: [super description]]; - [SQLCacheLock lock]; - e = NSEnumerateHashTable(SQLCacheInstances); - while ((c = (SQLCache*)NSNextHashEnumeratorItem(&e)) != nil) - { - [ms appendFormat: @"\n%@", [c description]]; - } - NSEndHashTableEnumeration(&e); - [SQLCacheLock unlock]; - return ms; -} - -+ (void) initialize -{ - SQLClientTimeNow(); - if (null == nil) - { - null = [NSNull new]; - } - if (SQLCacheInstances == 0) - { - SQLCacheLock = [NSLock new]; - SQLCacheInstances - = NSCreateHashTable(NSNonRetainedObjectHashCallBacks, 0); - } -} - -- (unsigned) currentObjects -{ - return my->currentObjects; -} - -- (unsigned) currentSize -{ - return my->currentSize; -} - -- (void) dealloc -{ - [SQLCacheLock lock]; - if (my->contents != 0) - { - [self shrinkObjects: 0 andSize: 0]; - NSFreeMapTable(my->contents); - } - RELEASE(my->exclude); - RELEASE(my->name); - NSHashRemove(SQLCacheInstances, (void*)self); - NSDeallocateObject(self); - [SQLCacheLock unlock]; -} - -- (NSString*) description -{ - NSString *n = my->name; - - if (n == nil) - { - n = [super description]; - } - return [NSString stringWithFormat: - @" %@\n" - @" Items: %u(%u)\n" - @" Size: %u(%u)\n" - @" Life: %u\n" - @" Hit: %u\n" - @" Miss: %u\n", - n, - my->currentObjects, my->maxObjects, - my->currentSize, my->maxSize, - my->lifetime, - my->hits, - my->misses]; -} - -- (id) init -{ - my->contents = NSCreateMapTable(NSObjectMapKeyCallBacks, - NSObjectMapValueCallBacks, 0); - return self; -} - -- (unsigned) lifetime -{ - return my->lifetime; -} - -- (unsigned) maxObjects -{ - return my->maxObjects; -} - -- (unsigned) maxSize -{ - return my->maxSize; -} - -- (NSString*) name -{ - return my->name; -} - -- (id) objectForKey: (NSString*)aKey -{ - SQLCItem *item; - unsigned when = SQLClientTimeTick(); - - item = (SQLCItem*)NSMapGet(my->contents, aKey); - if (item == nil) - { - my->misses++; - return nil; - } - removeItem(item, &my->first); - if (item->when > 0 && item->when < when) - { - my->currentObjects--; - if (my->maxSize > 0) - { - my->currentSize -= item->size; - } - NSMapRemove(my->contents, (void*)item->key); - my->misses++; - return nil; // Lifetime expired. - } - - // Least recently used ... move to end of list. - appendItem(item, &my->first); - my->hits++; - return item->object; -} - -- (void) purge -{ - unsigned when = SQLClientTimeTick(); - - if (my->contents != 0) - { - NSMapEnumerator e; - SQLCItem *i; - NSString *k; - - e = NSEnumerateMapTable(my->contents); - while (NSNextMapEnumeratorPair(&e, (void**)&k, (void**)&i) != 0) - { - if (i->when > 0 && i->when < when) - { - removeItem(i, &my->first); - my->currentObjects--; - if (my->maxSize > 0) - { - my->currentSize -= i->size; - } - NSMapRemove(my->contents, (void*)i->key); - } - } - NSEndMapTableEnumeration(&e); - } -} - -- (void) setLifetime: (unsigned)max -{ - my->lifetime = max; -} - -- (void) setMaxObjects: (unsigned)max -{ - my->maxObjects = max; - if (my->currentObjects > my->maxObjects) - { - [self shrinkObjects: my->maxObjects - andSize: my->maxSize]; - } -} - -- (void) setMaxSize: (unsigned)max -{ - if (max > 0 && my->maxSize == 0) - { - NSMapEnumerator e = NSEnumerateMapTable(my->contents); - SQLCItem *i; - NSString *k; - unsigned size = 0; - - if (my->exclude == nil) - { - my->exclude = [NSMutableSet new]; - } - while (NSNextMapEnumeratorPair(&e, (void**)&k, (void**)&i) != 0) - { - if (i->size == 0) - { - [my->exclude removeAllObjects]; - i->size = [i->object sizeInBytes: my->exclude]; - } - if (i->size > max) - { - /* - * Item in cache is too big for new size limit ... - * Remove it. - */ - removeItem(i, &my->first); - NSMapRemove(my->contents, (void*)i->key); - my->currentObjects--; - continue; - } - size += i->size; - } - NSEndMapTableEnumeration(&e); - my->currentSize = size; - } - else if (max == 0) - { - my->currentSize = 0; - } - my->maxSize = max; - if (my->currentSize > my->maxSize) - { - [self shrinkObjects: my->maxObjects - andSize: my->maxSize]; - } -} - -- (void) setName: (NSString*)name -{ - ASSIGN(my->name, name); -} - -- (void) setObject: (id)anObject forKey: (NSString*)aKey -{ - [self setObject: anObject forKey: aKey lifetime: my->lifetime]; -} - -- (void) setObject: (id)anObject - forKey: (NSString*)aKey - lifetime: (unsigned)lifetime -{ - SQLCItem *item; - unsigned maxObjects = my->maxObjects; - unsigned maxSize = my->maxSize; - unsigned addObjects = (anObject == nil ? 0 : 1); - unsigned addSize = 0; - - item = (SQLCItem*)NSMapGet(my->contents, aKey); - if (item != nil) - { - removeItem(item, &my->first); - my->currentObjects--; - if (my->maxSize > 0) - { - my->currentSize -= item->size; - } - NSMapRemove(my->contents, (void*)aKey); - } - - if (maxSize > 0 || maxObjects > 0) - { - if (maxSize > 0) - { - if (my->exclude == nil) - { - my->exclude = [NSMutableSet new]; - } - [my->exclude removeAllObjects]; - addSize = [anObject sizeInBytes: my->exclude]; - if (addSize > maxSize) - { - return; // Object too big to cache. - } - } - } - - if (addObjects > 0) - { - /* - * Make room for new object. - */ - [self shrinkObjects: maxObjects - addObjects - andSize: maxSize - addSize]; - item = [SQLCItem newWithObject: anObject forKey: aKey]; - if (lifetime > 0) - { - item->when = SQLClientTimeTick() + lifetime; - } - item->size = addSize; - NSMapInsert(my->contents, (void*)item->key, (void*)item); - appendItem(item, &my->first); - my->currentObjects += addObjects; - my->currentSize += addSize; - RELEASE(item); - } -} - -- (void) shrinkObjects: (unsigned)objects andSize: (unsigned)size -{ - unsigned newSize = [self currentSize]; - unsigned newObjects = [self currentObjects]; - - if (newObjects > objects || (my->maxSize > 0 && newSize > size)) - { - [self purge]; - newSize = [self currentSize]; - newObjects = [self currentObjects]; - while (newObjects > objects || (my->maxSize > 0 && newSize > size)) - { - SQLCItem *item; - - item = my->first; - removeItem(item, &my->first); - newObjects--; - if (my->maxSize > 0) - { - newSize -= item->size; - } - NSMapRemove(my->contents, (void*)item->key); - } - my->currentObjects = newObjects; - my->currentSize = newSize; - } -} -@end - diff --git a/WebServer.h b/WebServer.h deleted file mode 100644 index cf1b91b..0000000 --- a/WebServer.h +++ /dev/null @@ -1,611 +0,0 @@ -/** - Copyright (C) 2004 Free Software Foundation, Inc. - - Written by: Richard Frith-Macdonald - Date: June 2004 - - This file is part of the SQLClient Library. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this library; if not, write to the Free - Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. - -WebServer documentation - - The WebServer class -
- What is the WebServer class? -

- The WebServer class provides the framework for a GNUstep program to - act as an HTTP or HTTPS server for simple applications.
- It does not attempt to be a general-purpose web server, but is rather - intended to permit a program to easily handle requests from automated - systems which are intended to control, monitor, or use the services - provided by the program in which the class is embedded.
- The emphasis is on making it robust/reliable/simple, so you can rapidly - develop software using it. It is a single-threaded, single-process - system using asynchronous I/O, so you can easily run it under - debug in gdb to fix any bugs in your delegate object.
- In particular of course, it may be used in conjunction with the - [SQLClient] class to implement web-based database applications. -

-

- The class is controlled by a few straightforward settings and - basically operates by handing over requests to its delegate. - The delegate must at least implement the - [(WebServerDelegate)-processRequest:response:for:] method. -

-

- Built-in facilities include - -

- - Parsing of parameter string in request URL - Parsing of url encoded form data in a POST request - Parsing of form encoded data in a POST request - Substitution into template pages on output - SSL support - HTTP Basic authentication - Limit access by IP address - Limit total number of simultaneous connections - Limit number of simultaneous connectionsform one address - Limit idle time permitted on a connection - Limit size of request headers permitted - Limit size of request body permitted - -
-
- - $Date$ $Revision$ - */ - -#ifndef INCLUDED_WEBSERVER_H -#define INCLUDED_WEBSERVER_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -@class WebServer; - -/** - * This protocol is implemented by a delegate of a WebServer instance - * in order to allow the delegate to process requests which arrive - * at the server. - */ -@protocol WebServerDelegate -/** - * Process the http request whose headers and data are provided in - * a GSMimeDocument.
- * Extra headers are created as follows - - * - * x-http-method - * The method from the HTTP request (eg. GET or POST) - * x-http-path - * The path from the HTTP request, or an empty string if - * there was no path. - * x-http-query - * The query string from the HTTP request or an empty string - * if there was no query. - * x-http-version - * The version from the HTTP request. - * x-local-address - * The IP address of the local host receiving the request. - * x-local-port - * The port of the local host receiving the request. - * x-remote-address - * The IP address of the host that the request came from. - * x-remote-port - * The port of the host that the request came from. - * x-http-username - * The username from the 'authorization' header if the request - * supplied http basic authentication. - * x-http-password - * The password from the 'authorization' header if the request - * supplied http basic authentication. - * - * On completion, the method must modify response to contain the data - * and headers to be sent out.
- * The 'content-length' header need not be set in the response as it will - * be overridden anyway.
- * The special 'http' header will be used as the response/status line. - * If not supplied, 'HTTP/1.1 200 Success' or 'HTTP/1.1 204 No Content' will - * be used as the response line, depending on whether the data is empty or - * not.
- * If an exception is raised by this method, the response produced will - * be set to 'HTTP/1.0 500 Internal Server Error' and the connection will - * be closed. - */ -- (BOOL) processRequest: (GSMimeDocument*)request - response: (GSMimeDocument*)response - for: (WebServer*)http; -/** - * Log an error or warning ... if the delegate does not implement this - * method, the message is logged to stderr using the NSLog function. - */ -- (void) webAlert: (NSString*)message for: (WebServer*)http; -@end - -/** - *

You create an instance of the WebServer class in order to handle - * incoming http or https requests on a single port. - *

- *

Before use, it must be configured using the -setPort:secure: method - * to specify the port and if/how ssl is to be used. - *

- *

You must also set a delegate to handle incoming requests, - * and may specify a maximum number of simultaneous connections - * which may be in progress etc. - *

- *

In addition to the options which may be set directly in the class, - * you can provide some configuration via the standard NSDefaults class. - * This information is set at initialisation of an instance and the - * class recognises the following defaults keys - - *

- * - * WebServerHosts - * An array of host IP addresses to list the mhosts permitted to - * send requests to the server. If defined, requests from other hosts - * will be rejected. It's better to use firewalling to control this - * sort of thing. - * - * WebServerQuiet - * An array of host IP addresses to refrain from logging ... - * this is useful if (for instance) you have a monitoring process which - * sends requests to the server to be sure it's alive, and don't want - * to log all the connections from this monitor.
- * Not only do we refrain from logging anything but exceptional events - * about these hosts, connections and requests by these hosts are not - * counted in statistics we generate. - *
- *
- */ -@interface WebServer : NSObject -{ -@private - NSNotificationCenter *_nc; - NSString *_port; - BOOL _accepting; - BOOL _verbose; - BOOL _durations; - NSDictionary *_sslConfig; - NSArray *_quiet; - NSArray *_hosts; - unsigned int _substitutionLimit; - unsigned int _maxBodySize; - unsigned int _maxRequestSize; - unsigned int _maxSessions; - unsigned int _maxPerHost; - id _delegate; - NSFileHandle *_listener; - NSMapTable *_sessions; - unsigned _handled; - unsigned _requests; - NSString *_root; - NSTimer *_ticker; - NSTimeInterval _sessionTimeout; - NSTimeInterval _ticked; - NSCountedSet *_perHost; -} - -/** - * This method is called for each incoming request, and checks that the - * requested resource is accessible (basic user/password access control).
- * The method returns YES if access is granted, or returns NO and sets the - * appropriate response values if access is refused.
- * If access is refused by this method, the delegate is not informed of the - * request at all ... so this forms an initial access control mechanism, - * but if it is passed, the delegate is still free to implement its own - * additional access control within the - * [(WebServerDelegate)-processRequest:response:for:] method.
- * The access control is managed by the WebServerAccess - * user default, which is a dictionary whose keys are paths, and whose - * values are dictionaries specifying the access control for those paths. - * Access control is done on the basis of the longest matching path.
- * Each access control dictionary contains an authentication realm string - * (keyed on Realm) and a dictionary containing username/password - * pairs (keyed on Users) or a dictionary containing information - * to perform a database lookup of username and password - * (keyed on UserDB).
- * eg. - * - * WebServerAccess = { - * "" = { - * Realm = "general"; - * Users = { - * Fred = 1942; - * }; - * }; - * "/private" = { - * Realm = "private"; - * UserDB = { - * // System will contact database using SQLClient and lookup password - * // The SQLClient library must be linked in and used by the tool - * // using WebServer ... it is not linked in by the WebServer library. - * Name = databasename; - * Table = tablename; - * UsernameField = fielname1; - * PasswordField = fielname2; - * }; - * }; - * }; - * - */ -- (BOOL) accessRequest: (GSMimeDocument*)request - response: (GSMimeDocument*)response; - -/** - * Decode an application/x-www-form-urlencoded form and store its - * contents into the supplied dictionary.
- * The resulting dictionary keys are strings.
- * The resulting dictionary values are arrays of NSData objects.
- * You probably don't need to call this method yourself ... more likely - * you will use the -parameters: method instead.
- * NB. For forms POSTed using multipart/form-data you don't - * need to perform any explicit decoding as this will already have been - * done for you and the decoded form will be presented as the request - * GSMimeDocument. The fields of the form will be the component parts - * of the content of the request and can be accessed using the standard - * GSMimeDocument methods.
- * This method returns the number of fields actually decoded. - */ -- (unsigned) decodeURLEncodedForm: (NSData*)data - into: (NSMutableDictionary*)dict; - -/** - * Encode an application/x-www-form-urlencoded form and store its - * representation in the supplied data object.
- * The dictionary contains the form, with keys as data objects or strings, - * and values as arrays of values to be placed in the data. - * Each value in the array may be a data object or a string.
- * As a special case, a value may be a data object or a string rather - * than an array ... this is treated like an array of one value.
- * All non data keys and values are convertd to data using utf-8 encoding.
- * This method returns the number of values actually encoded. - */ -- (unsigned) encodeURLEncodedForm: (NSDictionary*)dict - into: (NSMutableData*)data; - -/** - * Returns YES if the server is for HTTPS (encrypted connections), - * NO otherwise. - */ -- (BOOL) isSecure; - -/** - * Extracts request parameters from the http query string and from the - * request body (if it was application/x-www-form-urlencoded or - * multipart/form-data) and return the extracted parameters as a - * mutable dictionary whose keys are the parameter names and whose - * values are arrays containing the data for each parameter.
- * You should call this no more than once per request, storing the result - * and using it as an argument to the methods used to extract particular - * parameters.
- * Parameters from the request data are added to any found in the - * query string.
- * Values provided as multipart/form-data are also available - * in a more flexible format as the content of the request. - */ -- (NSMutableDictionary*) parameters: (GSMimeDocument*)request; - -/** - * Returns the index'th data parameter for the specified name.
- * Matching of names is case-insensitive
- * If there are no data items for the name, or if the index is - * too large for the number of items which exist, this returns nil. - */ -- (NSData*) parameter: (NSString*)name - at: (unsigned)index - from: (NSDictionary*)params; - -/** - * Calls -parameter:at:from: with an index of zero. - */ -- (NSData*) parameter: (NSString*)name from: (NSDictionary*)params; - -/** - * Calls -parameterString:at:from:charset: with a nil charset so that - * UTF-8 encoding is used for string conversion. - */ -- (NSString*) parameterString: (NSString*)name - at: (unsigned)index - from: (NSDictionary*)params; -/** - * Calls -parameter:at:from: and, if the result is non-nil - * converts the data to a string using the specified mime - * characterset, (if charset is nil, UTF-8 is used). - */ -- (NSString*) parameterString: (NSString*)name - at: (unsigned)index - from: (NSDictionary*)params - charset: (NSString*)charset; -/** - * Calls -parameterString:at:from:charset: with an index of zero and - * a nil value for charset (which causes data to be treated as UTF-8). - */ -- (NSString*) parameterString: (NSString*)name - from: (NSDictionary*)params; - -/** - * Calls -parameterString:at:from:charset: with an index of zero. - */ -- (NSString*) parameterString: (NSString*)name - from: (NSDictionary*)params - charset: (NSString*)charset; - -/** - * Loads a template file from disk and places it in aResponse as content - * whose mime type is determined from the file extension using the - * provided mapping (or a simple built-in default mapping if map is nil).
- * If you have a dedicated web server for handling static pages (eg images) - * it is better to use that rather than vending static pages using this - * method. It's unlikley that this method can be as efficient as a dedicated - * server. However this mechanism is adequate for moderate throughputs. - */ -- (BOOL) produceResponse: (GSMimeDocument*)aResponse - fromStaticPage: (NSString*)aPath - using: (NSDictionary*)map; - -/** - * Loads a template file from disk and places it in aResponse as content - * of type 'text/html' with a charset of 'utf-8'.
- * The argument aPath is a path relative to the root path set using - * the -setRoot: method.
- * Substitutes values into the template from map using the - * -substituteFrom:using:into:depth: method.
- * Returns NO if them template could not be read or if any substitution - * failed. In this case no value is set in the response.
- * If the response is actually text of another type, or you want another - * characterset used, you can change the content type header in the - * request after you call this method. - */ -- (BOOL) produceResponse: (GSMimeDocument*)aResponse - fromTemplate: (NSString*)aPath - using: (NSDictionary*)map; - -/** - * Sets the delegate object which processes requests for the receiver. - */ -- (void) setDelegate: (id)anObject; - -/** - * Sets a flag to determine whether logging of request and session - * durations is to be performed.
- * If this is YES then the duration of requests and sessions will - * be logged using the [(WebServerDelegate)-webAlert:for:] method.
- * The request duration is calculated from the point where the first byte - * of data in the request is read to the point where the response has - * been completely written.
- * This is useful for debugging and where a full audit trail is required. - */ -- (void) setDurationLogging: (BOOL)aFlag; - -/** - * Sets the maximum size of an uploaded request body.
- * The default is 4M bytes.
- */ -- (void) setMaxBodySize: (unsigned)max; - -/** - * Sets the maximum size of an incoming request (including all headers, - * but not the body).
- * The default is 8K bytes.
- */ -- (void) setMaxRequestSize: (unsigned)max; - -/** - * Sets the maximum number of simultaneous sessions with clients.
- * The default is 32.
- * A value of zero permits unlimited connections. - */ -- (void) setMaxSessions: (unsigned)max; - -/** - * Sets the maximum number of simultaneous sessions with a particular - * remote host.
- * The default is 8.
- * A value of zero permits unlimited connections. - */ -- (void) setMaxSessionsPerHost: (unsigned)max; - -/** - * Sets the port and security information for the receiver ... without - * this the receiver will not listen for incoming requests.
- * If secure is nil then the receiver listens on aPort for HTTP requests.
- * If secure is not nil, the receiver listens for HTTPS instead.
- * If secure is a dictionary containing CertificateFile, - * KeyFile and Password then the server will - * use the specified certificate and key files (which it will access - * using the password).
- * The secure dictionary may also contain other dictionaries - * keyed on IP addresses, and if the address that an incoming connection - * arrived on matches the key of a dictionary, that dictionary is used - * to provide the certificate information, with the top-level values - * being used as a fallback.
- * This method returns YES on success, NO on failure ... if it returns NO - * then the receiver will not be capable of handling incoming - * web requests!
- * Typically a failure will be due to an invalid port being specified ... - * a port may not already be in use and may not be in the range up to 1024 - * (unless running as the super-user). - */ -- (BOOL) setPort: (NSString*)aPort secure: (NSDictionary*)secure; - -/** - * Sets the maximum recursion depth allowed for subsititutions into - * templates. This defaults to 4. - */ -- (void) setSubstitutionLimit: (unsigned)depth; - -/** - * Set root path for loading template files from.
- * Templates may only be loaded from within this directory. - */ -- (void) setRoot: (NSString*)aPath; - -/** - * Sets the time after which an idle session should be shut down.
- * Default is 30.0 - */ -- (void) setSessionTimeout: (NSTimeInterval)aDelay; - -/** - * Sets a flag to determine whether verbose logging is to be performed.
- * If this is YES then all incoming requests and their responses will - * be logged using the [(WebServerDelegate)-webAlert:for:] method.
- * Setting this to YES automatically sets duration logging to YES as well, - * though you can then call -setDurationLogging: to set it back to NO.
- * This is useful for debugging and where a full audit trail is required. - */ -- (void) setVerbose: (BOOL)aFlag; - -/** - * Perform substituations replacing the markup in aTemplate with the - * values supplied by map and appending the results to the result.
- * Substitutions are recursive, and the depth argument is used to - * specify the current recursion depth (you should normally call this - * method with a depth of zero at the start of processing a template).
- * Any value inside SGML comment delimiters ('<!--' and '-->') is - * treated as a possible key in map and the entire comment is replaced - * by the corresponding map value (unless it is nil). Recursive substitution - * is done unless the mapped value starts with an SGML comment.
- * While the map is nominally a dictionary, in fact it may be any - * object which responds to the objectForKey: method by returning - * an NSString or nil.
- * The method returns YES on success, NO on failure (depth too great).
- * You don't normally need to use this method directly ... call the - * -produceResponse:fromTemplate:using: method instead. - */ -- (BOOL) substituteFrom: (NSString*)aTemplate - using: (NSDictionary*)map - into: (NSMutableString*)result - depth: (unsigned)depth; - -@end - -/** - * WebServerBundles is an example delegate for the WebServer class.
- * This is intended to act as a convenience for a scheme where the - * WebServer instance in a program is configured by values obtained - * from the user defaults system, and incoming requests may be handled - * by different delegate objects depending on the path information - * supplied in the request. The WebServerBundles intance is responsible - * for loading the bundles (based on information in the WebServerBundles - * dictionary in the user defaults system) and for forwarding requests - * to the appropriate bundles for processing.
- * If a request comes in which is not an exact match for the path of any - * handler, the request path is repeatedly shortened by chopping off the - * last path component until a matching handler is found.
- * The paths in the dictionary must not end with a slash... - * an empty string will match all requests which do not match a handler - * with a longer path. - * - * - */ -@interface WebServerBundles : NSObject -{ - NSMutableDictionary *_handlers; - WebServer *_http; -} - -/** - * Handle a notification that the defaults have been updated ... change - * WebServer configuration if necessary.
- * - * - * WebServerPort must be used to specify the port that the server - * listens on. See [WebServer-setPort:secure:] for details. - * - * - * WebServerSecure may be supplied to make the server operate as an - * HTTPS server rather than an HTTP server. - * See [WebServer-setPort:secure:] for details. - * - * - * WebServerBundles is a dictionary keyed on path strings, whose - * values are dictionaries, each containing per-handler configuration - * information and the name of the bundle containing the code to handle - * requests sent to the path. NB. the bundle name listed should - * omit the .bundle extension. - * - * - * Returns YES on success, NO on failure (if the port of the WebServer - * cannot be set). - */ -- (BOOL) defaultsUpdate: (NSNotification *)aNotification; - -/** - * Returns the handler to be used for the specified path, or nil if there - * is no handler available.
- * If the info argument is non-null, it is used to return additional - * information, either the path actually matched, or an error string. - */ -- (id) handlerForPath: (NSString*)path info: (NSString**)info; - -/** - * Return dictionary of all handlers by name (path in request which maps - * to that handler instance). - */ -- (NSMutableDictionary*) handlers; - -/** - * Return the WebServer instance that the receiver is acting as a - * delegate for. - */ -- (WebServer*) http; - -/** - * Initialises the receiver as the delegate of http and configures - * the WebServer based upon the settings found in the user defaults - * system by using the -defaultsUpdate: method. - */ -- (id) initAsDelegateOf: (WebServer*)http; - -/** - * Handles an incoming request by forwarding it to another handler.
- * If a direct mapping is available from the path in the request to - * an existing handler, that handler is used to process the request. - * Otherwise, the WebServerBundles dictionary (obtained from the - * defaults system) is used to map the request path to configuration - * information listing the bundle containing the handler to be used.
- * The configuration information is a dictionary containing the name - * of the bundle (keyed on 'Name'), and this is used to locate the - * bundle in the applications resources.
- * Before a request is passed on to a handler, two extra headers are set - * in it ... x-http-path-base and x-http-path-info - * being the actual path matched for the handler, and the remainder of the - * path after that base part. - */ -- (BOOL) processRequest: (GSMimeDocument*)request - response: (GSMimeDocument*)response - for: (WebServer*)http; - -/** - * Registers an object as the handler for a particular path.
- * Registering a nil handler destroys any existing handler for the path. - */ -- (void) registerHandler: (id)handler forPath: (NSString*)path; - -/** - * Just write to stderr using NSLog. - */ -- (void) webAlert: (NSString*)message for: (WebServer*)http; -@end - -#endif - diff --git a/WebServer.m b/WebServer.m deleted file mode 100644 index 61eff00..0000000 --- a/WebServer.m +++ /dev/null @@ -1,1910 +0,0 @@ -/** - Copyright (C) 2004 Free Software Foundation, Inc. - - Written by: Richard Frith-Macdonald - Date: June 2004 - - This file is part of the SQLClient Library. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this library; if not, write to the Free - Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. - - $Date$ $Revision$ - */ - -#include -#include "WebServer.h" -#include "SQLClient.h" - -@interface WebServerSession : NSObject -{ - NSString *address; - NSFileHandle *handle; - GSMimeParser *parser; - NSMutableData *buffer; - unsigned byteCount; - unsigned identity; - NSTimeInterval ticked; - NSTimeInterval requestStart; - NSTimeInterval sessionStart; - BOOL processing; - BOOL shouldEnd; - BOOL hasReset; -} -- (NSString*) address; -- (NSMutableData*) buffer; -- (NSFileHandle*) handle; -- (BOOL) hasReset; -- (unsigned) identity; -- (unsigned) moreBytes: (unsigned)count; -- (GSMimeParser*) parser; -- (BOOL) processing; -- (NSTimeInterval) requestDuration: (NSTimeInterval)now; -- (void) reset; -- (NSTimeInterval) sessionDuration: (NSTimeInterval)now; -- (void) setAddress: (NSString*)aString; -- (void) setBuffer: (NSMutableData*)aBuffer; -- (void) setHandle: (NSFileHandle*)aHandle; -- (void) setParser: (GSMimeParser*)aParser; -- (void) setProcessing: (BOOL)aFlag; -- (void) setRequestStart: (NSTimeInterval)when; -- (void) setSessionStart: (NSTimeInterval)when; -- (void) setShouldEnd: (BOOL)aFlag; -- (void) setTicked: (NSTimeInterval)when; -- (BOOL) shouldEnd; -- (NSTimeInterval) ticked; -@end - -@implementation WebServerSession -- (NSString*) address -{ - return address; -} - -- (NSMutableData*) buffer -{ - return buffer; -} - -- (void) dealloc -{ - [handle closeFile]; - DESTROY(address); - DESTROY(buffer); - DESTROY(handle); - DESTROY(parser); - [super dealloc]; -} - -- (NSString*) description -{ - return [NSString stringWithFormat: @"WebServerSession: %08x [%@] ", - [self identity], [self address]]; -} - -- (NSFileHandle*) handle -{ - return handle; -} - -- (BOOL) hasReset -{ - return hasReset; -} - -- (unsigned) identity -{ - return identity; -} - -- (id) init -{ - static unsigned sessionIdentity = 0; - - identity = ++sessionIdentity; - return self; -} - -- (unsigned) moreBytes: (unsigned)count -{ - byteCount += count; - return byteCount; -} - -- (GSMimeParser*) parser -{ - return parser; -} - -- (BOOL) processing -{ - return processing; -} - -- (NSTimeInterval) requestDuration: (NSTimeInterval)now -{ - if (requestStart > 0.0) - { - return now - requestStart; - } - return 0.0; -} - -- (void) reset -{ - hasReset = YES; - [self setRequestStart: 0.0]; - [self setBuffer: [NSMutableData dataWithCapacity: 1024]]; - [self setParser: nil]; - [self setProcessing: NO]; -} - -- (NSTimeInterval) sessionDuration: (NSTimeInterval)now -{ - if (sessionStart > 0.0) - { - return now - sessionStart; - } - return 0.0; -} - -- (void) setAddress: (NSString*)aString -{ - ASSIGN(address, aString); -} - -- (void) setBuffer: (NSMutableData*)aBuffer -{ - ASSIGN(buffer, aBuffer); -} - -- (void) setHandle: (NSFileHandle*)aHandle -{ - ASSIGN(handle, aHandle); -} - -- (void) setParser: (GSMimeParser*)aParser -{ - ASSIGN(parser, aParser); -} - -- (void) setProcessing: (BOOL)aFlag -{ - processing = aFlag; -} - -- (void) setRequestStart: (NSTimeInterval)when -{ - requestStart = when; -} - -- (void) setSessionStart: (NSTimeInterval)when -{ - sessionStart = when; -} - -- (void) setShouldEnd: (BOOL)aFlag -{ - shouldEnd = aFlag; -} - -- (void) setTicked: (NSTimeInterval)when -{ - ticked = when; -} - -- (BOOL) shouldEnd -{ - return shouldEnd; -} - -- (NSTimeInterval) ticked -{ - return ticked; -} -@end - -@interface WebServer (Private) -- (void) _alert: (NSString*)fmt, ...; -- (void) _didConnect: (NSNotification*)notification; -- (void) _didRead: (NSNotification*)notification; -- (void) _didWrite: (NSNotification*)notification; -- (void) _endSession: (WebServerSession*)session; -- (void) _process: (WebServerSession*)session; -- (void) _timeout: (NSTimer*)timer; -@end - -@implementation WebServer - -- (BOOL) accessRequest: (GSMimeDocument*)request - response: (GSMimeDocument*)response -{ - NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; - NSDictionary *conf = [defs dictionaryForKey: @"WebServerAccess"]; - NSString *path = [[request headerNamed: @"x-http-path"] value]; - NSDictionary *access = nil; - NSString *stored; - NSString *username; - NSString *password; - - while (access == nil) - { - access = [conf objectForKey: path]; - if ([access isKindOfClass: [NSDictionary class]] == NO) - { - NSRange r; - - r = [path rangeOfString: @"/" options: NSBackwardsSearch]; - if (r.length > 0) - { - path = [path substringToIndex: r.location]; - } - else - { - return YES; // No access dictionary - permit access - } - } - } - - username = [[request headerNamed: @"x-http-username"] value]; - password = [[request headerNamed: @"x-http-password"] value]; - if ([access objectForKey: @"Users"] != nil) - { - NSDictionary *users = [access objectForKey: @"Users"]; - - stored = [users objectForKey: username]; - } - else if ([access objectForKey: @"UserDB"] != nil) - { - static Class c = nil; - static BOOL beenHere = NO; - - /* - * We get the SQLClient class from thee runtime, so we don't have to - * link the library directly ... which means that this class caan be - * used without it as long as database accesss is not needed. - */ - if (beenHere == NO) - { - beenHere = YES; - c = NSClassFromString(@"SQLClient"); - if (c == nil) - { - [self _alert: @"SQLClient library has not been linked"]; - } - } - - NS_DURING - { - NSDictionary *info = [access objectForKey: @"UserDB"]; - NSString *name = [info objectForKey: @"Name"]; - SQLClient *sql; - - /* - * try to re-use an existing client if possible. - */ - sql = [c existingClient: name]; - if (sql == nil) - { - sql = [c alloc]; - sql = [sql initWithConfiguration: nil name: name]; - } - stored = [sql queryString: @"SELECT ", - [info objectForKey: @"Password"], - @" FROM ", - [info objectForKey: @"Table"], - @" WHERE ", - [info objectForKey: @"Username"], - @" = ", - [sql quote: username], - nil]; - } - NS_HANDLER - { - [self _alert: @"Read from database failed - %@", localException]; - stored = nil; - } - NS_ENDHANDLER - } - - - if (username == nil || password == nil || [password isEqual: stored] == NO) - { - NSString *realm = [access objectForKey: @"Realm"]; - NSString *auth; - - auth = [NSString stringWithFormat: @"Basic realm=\"%@\"", realm]; - - /* - * Return status code 401 (Aunauthorised) - */ - [response setHeader: @"http" - value: @"HTTP/1.1 401 Unauthorised" - parameters: nil]; - [response setHeader: @"WWW-authenticate" - value: auth - parameters: nil]; - - [response setContent: -@"\n" -@"401 Authorization Required\n" -@"

Authorization Required

\n" -@"

This server could not verify that you " -@"are authorized to access the resource " -@"requested. Either you supplied the wrong " -@"credentials (e.g., bad password), or your " -@"browser doesn't understand how to supply " -@"the credentials required.

\n" -@"\n" - type: @"text/html"]; - - return NO; - } - else - { - return YES; // OK to access - } -} - -- (void) dealloc -{ - if (_ticker != nil) - { - [_ticker invalidate]; - _ticker = nil; - } - [self setPort: nil secure: nil]; - DESTROY(_nc); - DESTROY(_root); - DESTROY(_quiet); - DESTROY(_hosts); - DESTROY(_perHost); - if (_sessions != 0) - { - NSFreeMapTable(_sessions); - _sessions = 0; - } - [super dealloc]; -} - -static unsigned -unescapeData(const unsigned char* bytes, unsigned length, unsigned char *buf) -{ - unsigned int to = 0; - unsigned int from = 0; - - while (from < length) - { - unsigned char c = bytes[from++]; - - if (c == '+') - { - c = ' '; - } - else if (c == '%' && from < length - 1) - { - unsigned char tmp; - - c = 0; - tmp = bytes[from++]; - if (tmp <= '9' && tmp >= '0') - { - c = tmp - '0'; - } - else if (tmp <= 'F' && tmp >= 'A') - { - c = tmp + 10 - 'A'; - } - else if (tmp <= 'f' && tmp >= 'a') - { - c = tmp + 10 - 'a'; - } - else - { - c = 0; - } - c <<= 4; - tmp = bytes[from++]; - if (tmp <= '9' && tmp >= '0') - { - c += tmp - '0'; - } - else if (tmp <= 'F' && tmp >= 'A') - { - c += tmp + 10 - 'A'; - } - else if (tmp <= 'f' && tmp >= 'a') - { - c += tmp + 10 - 'a'; - } - else - { - c = 0; - } - } - buf[to++] = c; - } - return to; -} - -- (unsigned) decodeURLEncodedForm: (NSData*)data - into: (NSMutableDictionary*)dict -{ - const unsigned char *bytes = (const unsigned char*)[data bytes]; - unsigned length = [data length]; - unsigned pos = 0; - unsigned fields = 0; - - while (pos < length) - { - unsigned int keyStart = pos; - unsigned int keyEnd; - unsigned int valStart; - unsigned int valEnd; - unsigned char *buf; - unsigned int buflen; - BOOL escape = NO; - NSData *d; - NSString *k; - NSMutableArray *a; - - while (pos < length && bytes[pos] != '&') - { - pos++; - } - valEnd = pos; - if (pos < length) - { - pos++; // Step past '&' - } - - keyEnd = keyStart; - while (keyEnd < pos && bytes[keyEnd] != '=') - { - if (bytes[keyEnd] == '%' || bytes[keyEnd] == '+') - { - escape = YES; - } - keyEnd++; - } - - if (escape == YES) - { - buf = NSZoneMalloc(NSDefaultMallocZone(), keyEnd - keyStart); - buflen = unescapeData(&bytes[keyStart], keyEnd - keyStart, buf); - d = [[NSData alloc] initWithBytesNoCopy: buf - length: buflen - freeWhenDone: YES]; - } - else - { - d = [[NSData alloc] initWithBytesNoCopy: (void*)&bytes[keyStart] - length: keyEnd - keyStart - freeWhenDone: NO]; - } - k = [[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding]; - if (k == nil) - { - [NSException raise: NSInvalidArgumentException - format: @"Bad UTF-8 form data (key of field %d)", fields]; - } - RELEASE(d); - - valStart = keyEnd; - if (valStart < pos) - { - valStart++; // Step past '=' - } - if (valStart < valEnd) - { - buf = NSZoneMalloc(NSDefaultMallocZone(), valEnd - valStart); - buflen = unescapeData(&bytes[valStart], valEnd - valStart, buf); - d = [[NSData alloc] initWithBytesNoCopy: buf - length: buflen - freeWhenDone: YES]; - } - else - { - d = [NSData new]; - } - a = [dict objectForKey: k]; - if (a == nil) - { - a = [[NSMutableArray alloc] initWithCapacity: 1]; - [dict setObject: a forKey: k]; - RELEASE(a); - } - [a addObject: d]; - RELEASE(d); - RELEASE(k); - fields++; - } - return fields; -} - -static NSMutableData* -escapeData(const unsigned char* bytes, unsigned length, NSMutableData *d) -{ - unsigned char *dst; - unsigned int spos = 0; - unsigned int dpos = [d length]; - - [d setLength: dpos + 3 * length]; - dst = (unsigned char*)[d mutableBytes]; - while (spos < length) - { - unsigned char c = bytes[spos++]; - unsigned int hi; - unsigned int lo; - - switch (c) - { - case ',': - case ';': - case '"': - case '\'': - case '&': - case '=': - case '(': - case ')': - case '<': - case '>': - case '?': - case '#': - case '{': - case '}': - case '%': - case ' ': - case '+': - dst[dpos++] = '%'; - hi = (c & 0xf0) >> 4; - dst[dpos++] = (hi > 9) ? 'A' + hi - 10 : '0' + hi; - lo = (c & 0x0f); - dst[dpos++] = (lo > 9) ? 'A' + lo - 10 : '0' + lo; - break; - - default: - if (c < ' ' || c > 127) - { - dst[dpos++] = '%'; - hi = (c & 0xf0) >> 4; - dst[dpos++] = (hi > 9) ? 'A' + hi - 10 : '0' + hi; - lo = (c & 0x0f); - dst[dpos++] = (lo > 9) ? 'A' + lo - 10 : '0' + lo; - } - else - { - dst[dpos++] = c; - } - break; - } - } - [d setLength: dpos]; - return d; -} - -- (unsigned) encodeURLEncodedForm: (NSDictionary*)dict - into: (NSMutableData*)data -{ - CREATE_AUTORELEASE_POOL(arp); - NSEnumerator *keyEnumerator; - id key; - unsigned valueCount = 0; - NSMutableData *md = [NSMutableData dataWithCapacity: 100]; - - keyEnumerator = [dict keyEnumerator]; - while ((key = [keyEnumerator nextObject]) != nil) - { - id values = [dict objectForKey: key]; - NSData *keyData; - NSEnumerator *valueEnumerator; - id value; - - if ([key isKindOfClass: [NSData class]] == YES) - { - keyData = key; - } - else - { - key = [key description]; - keyData = [key dataUsingEncoding: NSUTF8StringEncoding]; - } - [md setLength: 0]; - escapeData([keyData bytes], [keyData length], md); - keyData = md; - - if ([values isKindOfClass: [NSArray class]] == NO) - { - values = [NSArray arrayWithObject: values]; - } - - valueEnumerator = [values objectEnumerator]; - - while ((value = [valueEnumerator nextObject]) != nil) - { - NSData *valueData; - - if ([data length] > 0) - { - [data appendBytes: "&" length: 1]; - } - [data appendData: keyData]; - [data appendBytes: "=" length: 1]; - if ([value isKindOfClass: [NSData class]] == YES) - { - valueData = value; - } - else - { - value = [value description]; - valueData = [value dataUsingEncoding: NSUTF8StringEncoding]; - } - escapeData([valueData bytes], [valueData length], data); - valueCount++; - } - } - RELEASE(arp); - return valueCount; -} - -- (NSString*) description -{ - return [NSString stringWithFormat: @"%@ on %@(%@), %u of %u sessions active," - @" %u ended, %u requests, listening: %@", - [super description], _port, ([self isSecure] ? @"https" : @"http"), - NSCountMapTable(_sessions), - _maxSessions, _handled, _requests, _accepting == YES ? @"yes" : @"no"]; -} - -- (id) init -{ - NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; - - _hosts = RETAIN([defs arrayForKey: @"WebServerHosts"]); - _quiet = RETAIN([defs arrayForKey: @"WebServerQuiet"]); - _nc = RETAIN([NSNotificationCenter defaultCenter]); - _sessionTimeout = 30.0; - _maxPerHost = 8; - _maxSessions = 32; - _maxBodySize = 8*1024; - _maxRequestSize = 4*1024*1024; - _substitutionLimit = 4; - _sessions = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks, - NSObjectMapValueCallBacks, 0); - _perHost = [NSCountedSet new]; - _ticker = [NSTimer scheduledTimerWithTimeInterval: 0.8 - target: self - selector: @selector(_timeout:) - userInfo: 0 - repeats: YES]; - return self; -} - -- (BOOL) isSecure -{ - if (_sslConfig == nil) - { - return NO; - } - return YES; -} - -- (BOOL) produceResponse: (GSMimeDocument*)aResponse - fromStaticPage: (NSString*)aPath - using: (NSDictionary*)map -{ - CREATE_AUTORELEASE_POOL(arp); - NSString *path = (_root == nil) ? (id)@"" : (id)_root; - NSString *ext = [aPath pathExtension]; - NSString *type; - NSString *str; - id data; - NSFileManager *mgr; - BOOL string = NO; - BOOL result = YES; - - if (map == nil) - { - static NSDictionary *defaultMap = nil; - - if (defaultMap == nil) - { - defaultMap = [[NSDictionary alloc] initWithObjectsAndKeys: - @"image/gif", @"gif", - @"image/png", @"png", - @"image/jpeg", @"jpeg", - @"text/html", @"html", - @"text/plain", @"txt", - @"text/xml", @"xml", - nil]; - } - map = defaultMap; - } - - type = [map objectForKey: ext]; - if (type == nil) - { - type = [map objectForKey: [ext lowercaseString]]; - } - if (type == nil) - { - type = @"application/octet-stream"; - } - string = [type hasPrefix: @"text/"]; - - path = [path stringByAppendingString: @"/"]; - str = [path stringByStandardizingPath]; - path = [path stringByAppendingPathComponent: aPath]; - path = [path stringByStandardizingPath]; - mgr = [NSFileManager defaultManager]; - if ([path hasPrefix: str] == NO) - { - [self _alert: @"Illegal static page '%@' ('%@')", aPath, path]; - result = NO; - } - else if ([mgr isReadableFileAtPath: path] == NO) - { - [self _alert: @"Can't read static page '%@' ('%@')", aPath, path]; - result = NO; - } - else if (string == YES - && (data = [NSString stringWithContentsOfFile: path]) == nil) - { - [self _alert: @"Failed to load string '%@' ('%@')", aPath, path]; - result = NO; - } - else if (string == NO - && (data = [NSData dataWithContentsOfFile: path]) == nil) - { - [self _alert: @"Failed to load data '%@' ('%@')", aPath, path]; - result = NO; - } - else - { - [aResponse setContent: data type: type name: nil]; - } - DESTROY(arp); - return result; -} - -- (BOOL) produceResponse: (GSMimeDocument*)aResponse - fromTemplate: (NSString*)aPath - using: (NSDictionary*)map -{ - CREATE_AUTORELEASE_POOL(arp); - NSString *path = (_root == nil) ? (id)@"" : (id)_root; - NSString *str; - NSFileManager *mgr; - BOOL result; - - path = [path stringByAppendingString: @"/"]; - str = [path stringByStandardizingPath]; - path = [path stringByAppendingPathComponent: aPath]; - path = [path stringByStandardizingPath]; - mgr = [NSFileManager defaultManager]; - if ([path hasPrefix: str] == NO) - { - [self _alert: @"Illegal template '%@' ('%@')", aPath, path]; - result = NO; - } - else if ([mgr isReadableFileAtPath: path] == NO) - { - [self _alert: @"Can't read template '%@' ('%@')", aPath, path]; - result = NO; - } - else if ((str = [NSString stringWithContentsOfFile: path]) == nil) - { - [self _alert: @"Failed to load template '%@' ('%@')", aPath, path]; - result = NO; - } - else - { - NSMutableString *m = [NSMutableString stringWithCapacity: [str length]]; - - result = [self substituteFrom: str - using: map - into: m - depth: 0]; - if (result == YES) - { - [aResponse setContent: m type: @"text/html" name: nil]; - [[aResponse headerNamed: @"content-type"] setParameter: @"utf-8" - forKey: @"charset"]; - } - } - DESTROY(arp); - return result; -} - -- (NSMutableDictionary*) parameters: (GSMimeDocument*)request -{ - NSMutableDictionary *params; - NSString *str = [[request headerNamed: @"x-http-query"] value]; - NSData *data; - - params = [NSMutableDictionary dictionaryWithCapacity: 32]; - if ([str length] > 0) - { - data = [str dataUsingEncoding: NSASCIIStringEncoding]; - [self decodeURLEncodedForm: data into: params]; - } - - str = [[request headerNamed: @"content-type"] value]; - if ([str isEqualToString: @"application/x-www-form-urlencoded"] == YES) - { - data = [request convertToData]; - [self decodeURLEncodedForm: data into: params]; - } - else if ([str isEqualToString: @"multipart/form-data"] == YES) - { - NSArray *contents = [request content]; - unsigned count = [contents count]; - unsigned i; - - for (i = 0; i < count; i++) - { - GSMimeDocument *doc = [contents objectAtIndex: i]; - GSMimeHeader *hdr = [doc headerNamed: @"content-type"]; - NSString *k = [hdr parameterForKey: @"name"]; - - if (k == nil) - { - hdr = [doc headerNamed: @"content-disposition"]; - k = [hdr parameterForKey: @"name"]; - } - if (k != nil) - { - NSMutableArray *a; - - a = [params objectForKey: k]; - if (a == nil) - { - a = [[NSMutableArray alloc] initWithCapacity: 1]; - [params setObject: a forKey: k]; - RELEASE(a); - } - [a addObject: [doc convertToData]]; - } - } - } - - return params; -} - -- (NSData*) parameter: (NSString*)name - at: (unsigned)index - from: (NSDictionary*)params -{ - NSArray *a = [params objectForKey: name]; - - if (a == nil) - { - NSEnumerator *e = [params keyEnumerator]; - NSString *k; - - while ((k = [e nextObject]) != nil) - { - if ([k caseInsensitiveCompare: name] == NSOrderedSame) - { - a = [params objectForKey: k]; - break; - } - } - } - if (index >= [a count]) - { - return nil; - } - return [a objectAtIndex: index]; -} - -- (NSData*) parameter: (NSString*)name from: (NSDictionary*)params -{ - return [self parameter: name at: 0 from: params]; -} - -- (NSString*) parameterString: (NSString*)name - at: (unsigned)index - from: (NSDictionary*)params -{ - return [self parameterString: name at: index from: params charset: nil]; -} - -- (NSString*) parameterString: (NSString*)name - at: (unsigned)index - from: (NSDictionary*)params - charset: (NSString*)charset -{ - NSData *d = [self parameter: name at: index from: params]; - NSString *s = nil; - - if (d != nil) - { - s = [NSString alloc]; - if (charset == nil || [charset length] == 0) - { - s = [s initWithData: d encoding: NSUTF8StringEncoding]; - } - else - { - NSStringEncoding enc; - - enc = [GSMimeDocument encodingFromCharset: charset]; - s = [s initWithData: d encoding: enc]; - } - } - return AUTORELEASE(s); -} - -- (NSString*) parameterString: (NSString*)name from: (NSDictionary*)params -{ - return [self parameterString: name at: 0 from: params charset: nil]; -} - -- (NSString*) parameterString: (NSString*)name - from: (NSDictionary*)params - charset: (NSString*)charset -{ - return [self parameterString: name at: 0 from: params charset: charset]; -} - -- (void) setDelegate: (id)anObject -{ - _delegate = anObject; -} - -- (void) setDurationLogging: (BOOL)aFlag -{ - _durations = aFlag; -} - -- (void) setMaxBodySize: (unsigned)max -{ - _maxBodySize = max; -} - -- (void) setMaxRequestSize: (unsigned)max -{ - _maxRequestSize = max; -} - -- (void) setMaxSessions: (unsigned)max -{ - _maxSessions = max; -} - -- (void) setMaxSessionsPerHost: (unsigned)max -{ - _maxPerHost = max; -} - -- (BOOL) setPort: (NSString*)aPort secure: (NSDictionary*)secure -{ - BOOL ok = YES; - BOOL update = NO; - - if (aPort == nil || [aPort isEqual: _port] == NO) - { - update = YES; - } - if ((secure == nil && _sslConfig != nil) - || (secure != nil && [secure isEqual: _sslConfig] == NO)) - { - update = YES; - } - - if (update == YES) - { - ASSIGN(_sslConfig, secure); - if (_listener != nil) - { - [_nc removeObserver: self - name: NSFileHandleConnectionAcceptedNotification - object: _listener]; - DESTROY(_listener); - } - _accepting = NO; // No longer listening for connections. - DESTROY(_port); - if (aPort != nil) - { - _port = [aPort copy]; - if (_sslConfig != nil) - { - _listener = [[NSFileHandle sslClass] - fileHandleAsServerAtAddress: nil - service: _port - protocol: @"tcp"]; - } - else - { - _listener = [NSFileHandle fileHandleAsServerAtAddress: nil - service: _port - protocol: @"tcp"]; - } - - if (_listener == nil) - { - [self _alert: @"Failed to listen on port %@", _port]; - DESTROY(_port); - ok = NO; - } - else - { - RETAIN(_listener); - [_nc addObserver: self - selector: @selector(_didConnect:) - name: NSFileHandleConnectionAcceptedNotification - object: _listener]; - if (_accepting == NO && (_maxSessions <= 0 - || NSCountMapTable(_sessions) < _maxSessions)) - { - [_listener acceptConnectionInBackgroundAndNotify]; - _accepting = YES; - } - } - } - } - return ok; -} - -- (void) setRoot: (NSString*)aPath -{ - ASSIGN(_root, aPath); -} - -- (void) setSessionTimeout: (NSTimeInterval)aDelay -{ - _sessionTimeout = aDelay; -} - -- (void) setSubstitutionLimit: (unsigned)depth -{ - _substitutionLimit = depth; -} - -- (void) setVerbose: (BOOL)aFlag -{ - _verbose = aFlag; - if (aFlag == YES) - { - [self setDurationLogging: YES]; - } -} - -- (BOOL) substituteFrom: (NSString*)aTemplate - using: (NSDictionary*)map - into: (NSMutableString*)result - depth: (unsigned)depth -{ - unsigned length; - unsigned pos = 0; - NSRange r = NSMakeRange(pos, length); - - if (depth > _substitutionLimit) - { - [self _alert: @"Substitution exceeded limit (%u)", _substitutionLimit]; - return NO; - } - - length = [aTemplate length]; - r = NSMakeRange(pos, length); - r = [aTemplate rangeOfString: @"" - options: NSLiteralSearch - range: r]; - if (r.length > 0) - { - unsigned end = NSMaxRange(r); - NSString *subFrom; - NSString *subTo; - - r = NSMakeRange(start + 4, r.location - start - 4); - subFrom = [aTemplate substringWithRange: r]; - subTo = [map objectForKey: subFrom]; - if (subTo == nil) - { - [result appendString: @"