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
This commit is contained in:
CaS 2005-11-14 20:37:33 +00:00
parent bbab465478
commit c9ed820419
12 changed files with 36 additions and 3660 deletions

View file

@ -1,3 +1,9 @@
2005-11-14 Richard Frith-Macdonald <rfm@gnu.org>
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 <rfm@gnu.org>
* WebServer.m: Add more accurate timestamps and implement request

View file

@ -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

3
Performance.import Normal file
View file

@ -0,0 +1,3 @@
import gnu.gnustep.Performance.*;

32
README
View file

@ -80,35 +80,3 @@ should be entered on the GNUstep project page
<http://savannah.gnu.org/support/?group-gnustep>
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

View file

@ -38,12 +38,6 @@
faster, easier to use, and easier to add new database backends for
than JDBC).
</p>
<p>
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.
</p>
<p>
The major features of the SQLClient library are -
</p>
@ -177,12 +171,12 @@
#include <GNUstepBase/GNUstep.h>
@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.<br />
* When full, old objects are removed to make room for new ones
* on a least-recently-used basis.<br />
* 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.<br />
* Objects stored in the cache may be given a limited lifetime,
* in which case an attempt to fetch an <em>expired</em> 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.<br />
* A value of zero means that items are not purged based on lifetime.
*/
- (unsigned) lifetime;
/**
* Return the maximum number of items in the cache.<br />
* A value of zero means there is no limit.
*/
- (unsigned) maxObjects;
/**
* Return the maximum tital size of items in the cache.<br />
* 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).<br />
* 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
* <em>expired</em> 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).<br />
* This will, if a lifetime is set (see the -setLifetime: method)
* first purge all <em>expired</em> 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.<br />
* If the objects argument is zero then all objects are removed from
* the cache.<br />
* The size argument is used <em>only</em> 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

View file

@ -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" = (

View file

@ -48,6 +48,7 @@
#include <Foundation/NSTimer.h>
#include <GNUstepBase/GSLock.h>
#include <Performance/Performance.h>
#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

View file

@ -1,611 +0,0 @@
/**
Copyright (C) 2004 Free Software Foundation, Inc.
Written by: Richard Frith-Macdonald <rfm@gnu.org>
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.
<title>WebServer documentation</title>
<chapter>
<heading>The WebServer class</heading>
<section>
<heading>What is the WebServer class?</heading>
<p>
The WebServer class provides the framework for a GNUstep program to
act as an HTTP or HTTPS server for simple applications.<br />
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.<br />
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.<br />
In particular of course, it may be used in conjunction with the
[SQLClient] class to implement web-based database applications.
</p>
<p>
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.
</p>
<p>
Built-in facilities include -
</p>
<list>
<item>Parsing of parameter string in request URL</item>
<item>Parsing of url encoded form data in a POST request</item>
<item>Parsing of form encoded data in a POST request</item>
<item>Substitution into template pages on output</item>
<item>SSL support</item>
<item>HTTP Basic authentication</item>
<item>Limit access by IP address</item>
<item>Limit total number of simultaneous connections</item>
<item>Limit number of simultaneous connectionsform one address</item>
<item>Limit idle time permitted on a connection</item>
<item>Limit size of request headers permitted</item>
<item>Limit size of request body permitted</item>
</list>
</section>
</chapter>
$Date$ $Revision$
*/
#ifndef INCLUDED_WEBSERVER_H
#define INCLUDED_WEBSERVER_H
#include <Foundation/NSObject.h>
#include <Foundation/NSMapTable.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSFileHandle.h>
#include <Foundation/NSNotification.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSSet.h>
#include <Foundation/NSTimer.h>
#include <GNUstepBase/GSMime.h>
@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.<br />
* Extra headers are created as follows -
* <deflist>
* <term>x-http-method</term>
* <desc>The method from the HTTP request (eg. GET or POST)</desc>
* <term>x-http-path</term>
* <desc>The path from the HTTP request, or an empty string if
* there was no path.</desc>
* <term>x-http-query</term>
* <desc>The query string from the HTTP request or an empty string
* if there was no query.</desc>
* <term>x-http-version</term>
* <desc>The version from the HTTP request.</desc>
* <term>x-local-address</term>
* <desc>The IP address of the local host receiving the request.</desc>
* <term>x-local-port</term>
* <desc>The port of the local host receiving the request.</desc>
* <term>x-remote-address</term>
* <desc>The IP address of the host that the request came from.</desc>
* <term>x-remote-port</term>
* <desc>The port of the host that the request came from.</desc>
* <term>x-http-username</term>
* <desc>The username from the 'authorization' header if the request
* supplied http basic authentication.</desc>
* <term>x-http-password</term>
* <desc>The password from the 'authorization' header if the request
* supplied http basic authentication.</desc>
* </deflist>
* On completion, the method must modify response to contain the data
* and headers to be sent out.<br />
* The 'content-length' header need not be set in the response as it will
* be overridden anyway.<br />
* 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.<br />
* 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
/**
* <p>You create an instance of the WebServer class in order to handle
* incoming http or https requests on a single port.
* </p>
* <p>Before use, it must be configured using the -setPort:secure: method
* to specify the port and if/how ssl is to be used.
* </p>
* <p>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.
* </p>
* <p>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 -
* </p>
* <deflist>
* <term>WebServerHosts</term>
* <desc>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.
* </desc>
* <term>WebServerQuiet</term>
* <desc>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.<br />
* 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.
* </desc>
* </deflist>
*/
@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).<br />
* The method returns YES if access is granted, or returns NO and sets the
* appropriate response values if access is refused.<br />
* 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.<br />
* The access control is managed by the <code>WebServerAccess</code>
* 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.<br />
* Each access control dictionary contains an authentication realm string
* (keyed on <em>Realm</em>) and a dictionary containing username/password
* pairs (keyed on <em>Users</em>) or a dictionary containing information
* to perform a database lookup of username and password
* (keyed on <em>UserDB</em>).<br />
* eg.
* <example>
* 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;
* };
* };
* };
* </example>
*/
- (BOOL) accessRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response;
/**
* Decode an application/x-www-form-urlencoded form and store its
* contents into the supplied dictionary.<br />
* The resulting dictionary keys are strings.<br />
* The resulting dictionary values are arrays of NSData objects.<br />
* You probably don't need to call this method yourself ... more likely
* you will use the -parameters: method instead.<br />
* NB. For forms POSTed using <code>multipart/form-data</code> 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.<br />
* 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.<br />
* 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.<br />
* 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.<br />
* All non data keys and values are convertd to data using utf-8 encoding.<br />
* 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.<br />
* 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.<br />
* Parameters from the request data are <em>added</em> to any found in the
* query string.<br />
* Values provided as <code>multipart/form-data</code> 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.<br />
* Matching of names is case-insensitive<br />
* 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).<br />
* 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'.<br />
* The argument aPath is a path relative to the root path set using
* the -setRoot: method.<br />
* Substitutes values into the template from map using the
* -substituteFrom:using:into:depth: method.<br />
* Returns NO if them template could not be read or if any substitution
* failed. In this case no value is set in the response.<br />
* 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.<br />
* If this is YES then the duration of requests and sessions will
* be logged using the [(WebServerDelegate)-webAlert:for:] method.<br />
* 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.<br />
* 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.<br />
* The default is 4M bytes.<br />
*/
- (void) setMaxBodySize: (unsigned)max;
/**
* Sets the maximum size of an incoming request (including all headers,
* but not the body).<br />
* The default is 8K bytes.<br />
*/
- (void) setMaxRequestSize: (unsigned)max;
/**
* Sets the maximum number of simultaneous sessions with clients.<br />
* The default is 32.<br />
* A value of zero permits unlimited connections.
*/
- (void) setMaxSessions: (unsigned)max;
/**
* Sets the maximum number of simultaneous sessions with a particular
* remote host.<br />
* The default is 8.<br />
* 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.<br />
* If secure is nil then the receiver listens on aPort for HTTP requests.<br />
* If secure is not nil, the receiver listens for HTTPS instead.<br />
* If secure is a dictionary containing <code>CertificateFile</code>,
* <code>KeyFile</code> and <code>Password</code> then the server will
* use the specified certificate and key files (which it will access
* using the password).<br />
* The <em>secure</em> 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.<br />
* This method returns YES on success, NO on failure ... if it returns NO
* then the receiver will <em>not</em> be capable of handling incoming
* web requests!<br />
* 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.<br />
* 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.<br />
* Default is 30.0
*/
- (void) setSessionTimeout: (NSTimeInterval)aDelay;
/**
* Sets a flag to determine whether verbose logging is to be performed.<br />
* If this is YES then all incoming requests and their responses will
* be logged using the [(WebServerDelegate)-webAlert:for:] method.<br />
* Setting this to YES automatically sets duration logging to YES as well,
* though you can then call -setDurationLogging: to set it back to NO.<br />
* 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.<br />
* 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).<br />
* Any value inside SGML comment delimiters ('&lt;!--' and '--&gt;') 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 <em>starts</em> with an SGML comment.<br />
* 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.<br />
* The method returns YES on success, NO on failure (depth too great).<br />
* 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.<br />
* 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.<br />
* 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.<br />
* The paths in the dictionary must <em>not</em> end with a slash...
* an empty string will match all requests which do not match a handler
* with a longer path.
* <example>
* </example>
*/
@interface WebServerBundles : NSObject <WebServerDelegate>
{
NSMutableDictionary *_handlers;
WebServer *_http;
}
/**
* Handle a notification that the defaults have been updated ... change
* WebServer configuration if necessary.<br />
* <list>
* <item>
* WebServerPort must be used to specify the port that the server
* listens on. See [WebServer-setPort:secure:] for details.
* </item>
* <item>
* WebServerSecure may be supplied to make the server operate as an
* HTTPS server rather than an HTTP server.
* See [WebServer-setPort:secure:] for details.
* </item>
* <item>
* 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 <code>.bundle</code> extension.
* </item>
* </list>
* 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.<br />
* 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;
/** <init />
* 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.<br />
* 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.<br />
* 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.<br />
* Before a request is passed on to a handler, two extra headers are set
* in it ... <code>x-http-path-base</code> and <code>x-http-path-info</code>
* 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.<br />
* 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

File diff suppressed because it is too large Load diff

View file

@ -1,248 +0,0 @@
/**
Copyright (C) 2004 Free Software Foundation, Inc.
Written by: Richard Frith-Macdonald <rfm@gnu.org>
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 <Foundation/Foundation.h>
#include "WebServer.h"
@implementation WebServerBundles
- (void) dealloc
{
RELEASE(_http);
RELEASE(_handlers);
[super dealloc];
}
- (BOOL) defaultsUpdate: (NSNotification *)aNotification
{
NSUserDefaults *defs = [aNotification object];
NSString *port;
NSDictionary *secure;
port = [defs stringForKey: @"WebServerPort"];
if ([port length] == 0)
{
return NO; // Can't make web server active.
}
secure = [defs dictionaryForKey: @"WebServerSecure"];
return [_http setPort: port secure: secure];
}
- (id) handlerForPath: (NSString*)path info: (NSString**)info
{
NSString *error = nil;
NSMutableDictionary *handlers;
id handler;
if (info != 0)
{
*info = path;
}
handlers = [self handlers];
handler = [handlers objectForKey: path];
if (handler == nil)
{
NSUserDefaults *defs;
NSDictionary *conf;
NSDictionary *byPath;
defs = [NSUserDefaults standardUserDefaults];
conf = [defs dictionaryForKey: @"WebServerBundles"];
byPath = [conf objectForKey: path];
if ([byPath isKindOfClass: [NSDictionary class]] == NO)
{
NSRange r;
r = [path rangeOfString: @"/" options: NSBackwardsSearch];
if (r.length > 0)
{
path = [path substringToIndex: r.location];
handler = [self handlerForPath: path info: info];
}
else
{
error = [NSString stringWithFormat:
@"Unable to find handler in Bundles config for '%@'", path];
}
}
else
{
NSString *name;
name = [byPath objectForKey: @"Name"];
if ([name length] == 0)
{
error = [NSString stringWithFormat:
@"Unable to find Name in Bundles config for '%@'", path];
}
else
{
NSBundle *mb = [NSBundle mainBundle];
NSString *p = [mb pathForResource: name ofType: @"bundle"];
NSBundle *b = [NSBundle bundleWithPath: p];
Class c = [b principalClass];
if (c == 0)
{
error = [NSString stringWithFormat:
@"Unable to find class in '%@' for '%@'", p, path];
}
else
{
handler = [c new];
[self registerHandler: handler forPath: path];
RELEASE(handler);
}
}
}
}
if (handler == nil && info != 0)
{
*info = error;
}
return handler;
}
- (NSMutableDictionary*) handlers
{
if (_handlers == nil)
{
_handlers = [NSMutableDictionary new];
}
return _handlers;
}
- (WebServer*) http
{
return _http;
}
- (id) init
{
return [self initAsDelegateOf: nil];
}
- (id) initAsDelegateOf: (WebServer*)http
{
if (http == nil)
{
DESTROY(self);
}
else
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
NSNotification *n;
ASSIGN(_http, http);
[_http setDelegate: self];
/*
* Watch for config changes, and set initial config by sending a
* faked change notification.
*/
[nc addObserver: self
selector: @selector(defaultsUpdate:)
name: NSUserDefaultsDidChangeNotification
object: defs];
n = [NSNotification
notificationWithName: NSUserDefaultsDidChangeNotification
object: defs
userInfo: nil];
if ([self defaultsUpdate: n] == NO)
{
DESTROY(self);
}
}
return self;
}
/**
* We handle the incoming requests here.
*/
- (BOOL) processRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (WebServer*)http
{
NSString *path;
NSString *info;
id handler;
path = [[request headerNamed: @"x-http-path"] value];
handler = [self handlerForPath: path info: &info];
if (handler == nil)
{
NSString *error = @"bad path";
/*
* Log the error message.
*/
[self webAlert: info for: (WebServer*)http];
/*
* Return status code 400 (Bad Request) with the informative error
*/
error = [NSString stringWithFormat: @"HTTP/1.0 400 %@", error];
[response setHeader: @"http" value: error parameters: nil];
return YES;
}
else
{
NSString *extra = [path substringFromIndex: [info length]];
/*
* Provide extra information about the exact path used to match
* the handler, and any remaining path information beyond it.
*/
[request setHeader: @"x-http-path-base"
value: info
parameters: nil];
[request setHeader: @"x-http-path-info"
value: extra
parameters: nil];
return [handler processRequest: request
response: response
for: http];
}
}
- (void) registerHandler: (id)handler forPath: (NSString*)path
{
if (handler == nil)
{
[[self handlers] removeObjectForKey: path];
}
else
{
[[self handlers] setObject: handler forKey: path];
}
}
- (void) webAlert: (NSString*)message for: (WebServer*)http
{
NSLog(@"%@", message);
}
@end

View file

@ -24,6 +24,7 @@
*/
#include <Foundation/Foundation.h>
#include <Performance/GSCache.h>
#include "SQLClient.h"
int
@ -233,7 +234,7 @@ main()
r0 = [db cache: 1 query: @"select * from xxx", nil];
r1 = [db cache: 1 query: @"select * from xxx", nil];
NSCAssert([r0 lastObject] == [r1 lastObject], @"Cache failed");
sleep(1);
sleep(2);
records = [db cache: 1 query: @"select * from xxx", nil];
NSCAssert([r0 lastObject] != [records lastObject], @"Lifetime failed");
@ -258,7 +259,7 @@ main()
}
}
NSLog(@"Records - %@", [SQLCache class]);
NSLog(@"Records - %@", [GSCache class]);
}
RELEASE(pool);

View file

@ -1,85 +0,0 @@
/**
Copyright (C) 2005 Free Software Foundation, Inc.
Written by: Richard Frith-Macdonald <rfm@gnu.org>
Date: September 2005
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 <Foundation/Foundation.h>
#include <GNUstepBase/GSMime.h>
#include "WebServer.h"
@interface Handler: NSObject
- (BOOL) processRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (WebServer*)http;
@end
@implementation Handler
- (BOOL) processRequest: (GSMimeDocument*)request
response: (GSMimeDocument*)response
for: (WebServer*)http
{
NSString *s;
s = [[NSString alloc] initWithData: [request rawMimeData]
encoding: NSISOLatin1StringEncoding];
NSLog(@"Got request -\n%@\n", s);
[response setContent: s type: @"text/plain" name: nil];
RELEASE(s);
return YES;
}
@end
int
main()
{
CREATE_AUTORELEASE_POOL(pool);
WebServer *server;
Handler *handler;
NSUserDefaults *defs;
defs = [NSUserDefaults standardUserDefaults];
[defs registerDefaults:
[NSDictionary dictionaryWithObjectsAndKeys:
@"80", @"Port",
nil]
];
server = [WebServer new];
{
NSData *d = [NSData dataWithContentsOfFile: @"/home/richard/web.log"];
NSMutableDictionary *p = [NSMutableDictionary dictionary];
[server decodeURLEncodedForm: d into: p];
NSLog(@"Params: %@", p);
exit(0);
}
handler = [Handler new];
[server setDelegate: handler];
[server setPort: [defs stringForKey: @"Port"] secure: nil];
[[NSRunLoop currentRunLoop] run];
RELEASE(pool);
return 0;
}