mirror of
https://github.com/gnustep/libs-sqlclient.git
synced 2025-06-04 19:11:13 +00:00
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:
parent
96a28fa22b
commit
a9de728fa0
12 changed files with 36 additions and 3660 deletions
|
@ -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>
|
2005-10-27 Richard Frith-Macdonald <rfm@gnu.org>
|
||||||
|
|
||||||
* WebServer.m: Add more accurate timestamps and implement request
|
* WebServer.m: Add more accurate timestamps and implement request
|
||||||
|
|
30
GNUmakefile
30
GNUmakefile
|
@ -3,7 +3,7 @@ include $(GNUSTEP_MAKEFILES)/common.make
|
||||||
-include config.make
|
-include config.make
|
||||||
|
|
||||||
PACKAGE_NAME = SQLClient
|
PACKAGE_NAME = SQLClient
|
||||||
PACKAGE_VERSION = 1.1.0
|
PACKAGE_VERSION = 1.2.0
|
||||||
CVS_MODULE_NAME = gnustep/dev-libs/SQLClient
|
CVS_MODULE_NAME = gnustep/dev-libs/SQLClient
|
||||||
CVS_TAG_NAME = SQLClient
|
CVS_TAG_NAME = SQLClient
|
||||||
|
|
||||||
|
@ -12,35 +12,16 @@ TEST_TOOL_NAME=
|
||||||
LIBRARY_NAME=SQLClient
|
LIBRARY_NAME=SQLClient
|
||||||
DOCUMENT_NAME=SQLClient
|
DOCUMENT_NAME=SQLClient
|
||||||
|
|
||||||
SQLClient_INTERFACE_VERSION=1.1
|
SQLClient_INTERFACE_VERSION=1.2
|
||||||
|
|
||||||
SQLClient_OBJC_FILES = SQLClient.m
|
SQLClient_OBJC_FILES = SQLClient.m
|
||||||
SQLClient_LIBRARIES_DEPEND_UPON =
|
SQLClient_LIBRARIES_DEPEND_UPON = -lPerformance
|
||||||
SQLClient_HEADER_FILES = SQLClient.h
|
SQLClient_HEADER_FILES = SQLClient.h
|
||||||
SQLClient_AGSDOC_FILES = SQLClient.h
|
SQLClient_AGSDOC_FILES = SQLClient.h
|
||||||
|
|
||||||
# Optional Java wrappers for the library
|
# Optional Java wrappers for the library
|
||||||
JAVA_WRAPPER_NAME = SQLClient
|
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
|
SQLClient_HEADER_FILES_INSTALL_DIR = SQLClient
|
||||||
|
|
||||||
BUNDLE_NAME=
|
BUNDLE_NAME=
|
||||||
|
@ -148,11 +129,6 @@ Oracle_libs_BUNDLE_LIBS += -lclntsh \
|
||||||
Oracle_libs_PRINCIPAL_CLASS = SQLClientOracle_libs
|
Oracle_libs_PRINCIPAL_CLASS = SQLClientOracle_libs
|
||||||
endif
|
endif
|
||||||
|
|
||||||
TEST_TOOL_NAME+=testWebServer
|
|
||||||
testWebServer_OBJC_FILES = testWebServer.m
|
|
||||||
testWebServer_TOOL_LIBS += -lSQLClient
|
|
||||||
testWebServer_LIB_DIRS += -L./obj
|
|
||||||
|
|
||||||
-include GNUmakefile.preamble
|
-include GNUmakefile.preamble
|
||||||
|
|
||||||
include $(GNUSTEP_MAKEFILES)/library.make
|
include $(GNUSTEP_MAKEFILES)/library.make
|
||||||
|
|
3
Performance.import
Normal file
3
Performance.import
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
import gnu.gnustep.Performance.*;
|
||||||
|
|
32
README
32
README
|
@ -80,35 +80,3 @@ should be entered on the GNUstep project page
|
||||||
<http://savannah.gnu.org/support/?group-gnustep>
|
<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
|
|
||||||
|
|
||||||
|
|
144
SQLClient.h
144
SQLClient.h
|
@ -38,12 +38,6 @@
|
||||||
faster, easier to use, and easier to add new database backends for
|
faster, easier to use, and easier to add new database backends for
|
||||||
than JDBC).
|
than JDBC).
|
||||||
</p>
|
</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>
|
<p>
|
||||||
The major features of the SQLClient library are -
|
The major features of the SQLClient library are -
|
||||||
</p>
|
</p>
|
||||||
|
@ -177,12 +171,12 @@
|
||||||
|
|
||||||
#include <GNUstepBase/GNUstep.h>
|
#include <GNUstepBase/GNUstep.h>
|
||||||
|
|
||||||
|
@class GSCache;
|
||||||
@class NSData;
|
@class NSData;
|
||||||
@class NSDate;
|
@class NSDate;
|
||||||
@class NSMutableSet;
|
@class NSMutableSet;
|
||||||
@class NSRecursiveLock;
|
@class NSRecursiveLock;
|
||||||
@class NSString;
|
@class NSString;
|
||||||
@class SQLCache;
|
|
||||||
@class SQLTransaction;
|
@class SQLTransaction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -322,7 +316,7 @@ extern unsigned SQLClientTimeTick();
|
||||||
NSTimeInterval _lastOperation;
|
NSTimeInterval _lastOperation;
|
||||||
NSTimeInterval _duration;
|
NSTimeInterval _duration;
|
||||||
unsigned int _debugging; /** The current debugging level */
|
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;
|
- (void) setDurationLogging: (NSTimeInterval)threshold;
|
||||||
@end
|
@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
|
* This category porovides methods for caching the results of queries
|
||||||
* in order to reduce the number of client-server trips and the database
|
* 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
|
* Returns the cache used by the receiver for storing the results of
|
||||||
* requests made through it.
|
* requests made through it.
|
||||||
*/
|
*/
|
||||||
- (SQLCache*) cache;
|
- (GSCache*) cache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the result of the query is already cached and is still valid,
|
* 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
|
* 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.
|
* be automatically created as soon as there is a need to cache anything.
|
||||||
*/
|
*/
|
||||||
- (void) setCache: (SQLCache*)aCache;
|
- (void) setCache: (GSCache*)aCache;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
|
@ -1,36 +1,14 @@
|
||||||
{ /* -*-c-*- */
|
{ /* -*-c-*- */
|
||||||
"prerequisite libraries" = (
|
"prerequisite libraries" = (
|
||||||
"gnustep-base"
|
"gnustep-base",
|
||||||
|
"Performance"
|
||||||
);
|
);
|
||||||
types = {
|
types = {
|
||||||
NSTimeInterval = double;
|
NSTimeInterval = double;
|
||||||
};
|
};
|
||||||
classes = (
|
classes = (
|
||||||
{
|
{
|
||||||
"java name" = "gnu.gnustep.SQLClient.SQLCache";
|
"file to include in preamble java code" = "Performance.import";
|
||||||
"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:"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"java name" = "gnu.gnustep.SQLClient.SQLClient";
|
"java name" = "gnu.gnustep.SQLClient.SQLClient";
|
||||||
"objective-c name" = "SQLClient";
|
"objective-c name" = "SQLClient";
|
||||||
"class methods" = (
|
"class methods" = (
|
||||||
|
|
594
SQLClient.m
594
SQLClient.m
|
@ -48,6 +48,7 @@
|
||||||
#include <Foundation/NSTimer.h>
|
#include <Foundation/NSTimer.h>
|
||||||
|
|
||||||
#include <GNUstepBase/GSLock.h>
|
#include <GNUstepBase/GSLock.h>
|
||||||
|
#include <Performance/Performance.h>
|
||||||
|
|
||||||
#include "SQLClient.h"
|
#include "SQLClient.h"
|
||||||
|
|
||||||
|
@ -59,126 +60,6 @@ static NSNull *null = nil;
|
||||||
|
|
||||||
static Class NSStringClass = 0;
|
static Class NSStringClass = 0;
|
||||||
static Class NSDateClass = 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
|
@implementation SQLRecord
|
||||||
+ (id) allocWithZone: (NSZone*)aZone
|
+ (id) allocWithZone: (NSZone*)aZone
|
||||||
|
@ -189,7 +70,7 @@ inline unsigned SQLClientTimeTick()
|
||||||
|
|
||||||
+ (void) initialize
|
+ (void) initialize
|
||||||
{
|
{
|
||||||
SQLClientTimeNow();
|
GSTickerTimeNow();
|
||||||
if (null == nil)
|
if (null == nil)
|
||||||
{
|
{
|
||||||
null = [NSNull new];
|
null = [NSNull new];
|
||||||
|
@ -542,7 +423,7 @@ static unsigned int maxConnections = 8;
|
||||||
|
|
||||||
+ (void) initialize
|
+ (void) initialize
|
||||||
{
|
{
|
||||||
SQLClientTimeNow();
|
GSTickerTimeNow();
|
||||||
if (null == nil)
|
if (null == nil)
|
||||||
{
|
{
|
||||||
null = [NSNull new];
|
null = [NSNull new];
|
||||||
|
@ -1215,10 +1096,10 @@ static void quoteString(NSMutableString *s)
|
||||||
|
|
||||||
if (_duration >= 0)
|
if (_duration >= 0)
|
||||||
{
|
{
|
||||||
start = SQLClientTimeNow();
|
start = GSTickerTimeNow();
|
||||||
}
|
}
|
||||||
[self backendExecute: info];
|
[self backendExecute: info];
|
||||||
_lastOperation = SQLClientTimeNow();
|
_lastOperation = GSTickerTimeNow();
|
||||||
[_statements addObject: statement];
|
[_statements addObject: statement];
|
||||||
if (_duration >= 0)
|
if (_duration >= 0)
|
||||||
{
|
{
|
||||||
|
@ -1289,10 +1170,10 @@ static void quoteString(NSMutableString *s)
|
||||||
|
|
||||||
if (_duration >= 0)
|
if (_duration >= 0)
|
||||||
{
|
{
|
||||||
start = SQLClientTimeNow();
|
start = GSTickerTimeNow();
|
||||||
}
|
}
|
||||||
result = [self backendQuery: stmt];
|
result = [self backendQuery: stmt];
|
||||||
_lastOperation = SQLClientTimeNow();
|
_lastOperation = GSTickerTimeNow();
|
||||||
if (_duration >= 0)
|
if (_duration >= 0)
|
||||||
{
|
{
|
||||||
NSTimeInterval d;
|
NSTimeInterval d;
|
||||||
|
@ -1787,7 +1668,7 @@ static void quoteString(NSMutableString *s)
|
||||||
*/
|
*/
|
||||||
+ (void) _tick: (NSTimer*)t
|
+ (void) _tick: (NSTimer*)t
|
||||||
{
|
{
|
||||||
SQLClientTimeNow();
|
GSTickerTimeNow();
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -1906,11 +1787,11 @@ static void quoteString(NSMutableString *s)
|
||||||
|
|
||||||
@implementation SQLClient (Caching)
|
@implementation SQLClient (Caching)
|
||||||
|
|
||||||
- (SQLCache*) cache
|
- (GSCache*) cache
|
||||||
{
|
{
|
||||||
if (_cache == nil)
|
if (_cache == nil)
|
||||||
{
|
{
|
||||||
_cache = [SQLCache new];
|
_cache = [GSCache new];
|
||||||
}
|
}
|
||||||
return _cache;
|
return _cache;
|
||||||
}
|
}
|
||||||
|
@ -1942,8 +1823,8 @@ static void quoteString(NSMutableString *s)
|
||||||
[lock lock];
|
[lock lock];
|
||||||
NS_DURING
|
NS_DURING
|
||||||
{
|
{
|
||||||
NSTimeInterval start = SQLClientTimeNow();
|
NSTimeInterval start = GSTickerTimeNow();
|
||||||
SQLCache *c = [self cache];
|
GSCache *c = [self cache];
|
||||||
id toCache = nil;
|
id toCache = nil;
|
||||||
|
|
||||||
if (seconds < 0)
|
if (seconds < 0)
|
||||||
|
@ -1958,7 +1839,7 @@ static void quoteString(NSMutableString *s)
|
||||||
if (result == nil)
|
if (result == nil)
|
||||||
{
|
{
|
||||||
result = toCache = [self backendQuery: stmt];
|
result = toCache = [self backendQuery: stmt];
|
||||||
_lastOperation = SQLClientTimeNow();
|
_lastOperation = GSTickerTimeNow();
|
||||||
if (_duration >= 0)
|
if (_duration >= 0)
|
||||||
{
|
{
|
||||||
NSTimeInterval d;
|
NSTimeInterval d;
|
||||||
|
@ -2002,7 +1883,7 @@ static void quoteString(NSMutableString *s)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) setCache: (SQLCache*)aCache
|
- (void) setCache: (GSCache*)aCache
|
||||||
{
|
{
|
||||||
ASSIGN(_cache, aCache);
|
ASSIGN(_cache, aCache);
|
||||||
}
|
}
|
||||||
|
@ -2125,450 +2006,3 @@ static void quoteString(NSMutableString *s)
|
||||||
}
|
}
|
||||||
@end
|
@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
|
|
||||||
|
|
||||||
|
|
611
WebServer.h
611
WebServer.h
|
@ -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 ('<!--' 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 <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
|
|
||||||
|
|
1910
WebServer.m
1910
WebServer.m
File diff suppressed because it is too large
Load diff
|
@ -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
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <Foundation/Foundation.h>
|
#include <Foundation/Foundation.h>
|
||||||
|
#include <Performance/GSCache.h>
|
||||||
#include "SQLClient.h"
|
#include "SQLClient.h"
|
||||||
|
|
||||||
int
|
int
|
||||||
|
@ -233,7 +234,7 @@ main()
|
||||||
r0 = [db cache: 1 query: @"select * from xxx", nil];
|
r0 = [db cache: 1 query: @"select * from xxx", nil];
|
||||||
r1 = [db cache: 1 query: @"select * from xxx", nil];
|
r1 = [db cache: 1 query: @"select * from xxx", nil];
|
||||||
NSCAssert([r0 lastObject] == [r1 lastObject], @"Cache failed");
|
NSCAssert([r0 lastObject] == [r1 lastObject], @"Cache failed");
|
||||||
sleep(1);
|
sleep(2);
|
||||||
records = [db cache: 1 query: @"select * from xxx", nil];
|
records = [db cache: 1 query: @"select * from xxx", nil];
|
||||||
NSCAssert([r0 lastObject] != [records lastObject], @"Lifetime failed");
|
NSCAssert([r0 lastObject] != [records lastObject], @"Lifetime failed");
|
||||||
|
|
||||||
|
@ -258,7 +259,7 @@ main()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NSLog(@"Records - %@", [SQLCache class]);
|
NSLog(@"Records - %@", [GSCache class]);
|
||||||
}
|
}
|
||||||
|
|
||||||
RELEASE(pool);
|
RELEASE(pool);
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue