mirror of
https://github.com/gnustep/libs-sqlclient.git
synced 2025-02-21 02:41:07 +00:00
experimental updates for performance and security
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/sqlclient/trunk@20826 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
parent
5474c4bba8
commit
221624ef4c
5 changed files with 406 additions and 5 deletions
|
@ -1,3 +1,12 @@
|
|||
2005-03-02 Richard Frith-Macdonald <rfm@gnu.org>
|
||||
|
||||
* WebServer.[hm]: Add support for basic http authentication either
|
||||
via username/password pairs in property list or in database table.
|
||||
* SQLClient.[hm]: Add methods to query database with local caching
|
||||
of results, for use on systems needing high performance, where
|
||||
database query (and/or database client-server comms) overheads are
|
||||
important.
|
||||
|
||||
2005-02-25 Adam Fedor <fedor@gnu.org>
|
||||
|
||||
* Version 1.1.0:
|
||||
|
|
56
SQLClient.h
56
SQLClient.h
|
@ -177,6 +177,7 @@
|
|||
|
||||
@class NSData;
|
||||
@class NSDate;
|
||||
@class NSMutableSet;
|
||||
@class NSRecursiveLock;
|
||||
@class NSString;
|
||||
@class SQLTransaction;
|
||||
|
@ -264,11 +265,12 @@ extern NSTimeInterval SQLClientTimeNow();
|
|||
NSMutableArray *_statements; /** Uncommitted statements */
|
||||
/**
|
||||
* Timestamp of last operation.<br />
|
||||
* Maintained by the -simpleExecute: and -simpleQuery: methods.
|
||||
* Maintained by -simpleExecute: -simpleQuery: -cache:simpleQuery:
|
||||
*/
|
||||
NSTimeInterval _lastOperation;
|
||||
NSTimeInterval _duration;
|
||||
unsigned int _debugging; /** The current debugging level */
|
||||
NSMutableSet *_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -906,6 +908,58 @@ extern NSTimeInterval SQLClientTimeNow();
|
|||
@end
|
||||
|
||||
|
||||
/**
|
||||
* This category porovides methods for caching the results of queries
|
||||
* in order to reduce the number of client-server trips and the database
|
||||
* load produced by an application which needs update its information
|
||||
* from the database frequently.
|
||||
*/
|
||||
@interface SQLClient (Caching)
|
||||
|
||||
/**
|
||||
* If the result of the query is already cached and is still valid,
|
||||
* return it. Otherwise, perform the query and cache the result
|
||||
* giving it the specified lifetime in seconds.<br />
|
||||
* If seconds is negative, the query is performed irrespective of
|
||||
* whether it is already cached, and its absolute value is used to
|
||||
* set the lifetime of the results.<br />
|
||||
* If seconds is zero, the cache for this query is emptied.
|
||||
*/
|
||||
- (NSMutableArray*) cache: (int)seconds
|
||||
query: (NSString*)stmt,...;
|
||||
|
||||
/**
|
||||
* If the result of the query is already cached and is still valid,
|
||||
* return it. Otherwise, perform the query and cache the result
|
||||
* giving it the specified lifetime in seconds.<br />
|
||||
* If seconds is negative, the query is performed irrespective of
|
||||
* whether it is already cached, and its absolute value is used to
|
||||
* set the lifetime of the results.<br />
|
||||
* If seconds is zero, the cache for this query is emptied.
|
||||
*/
|
||||
- (NSMutableArray*) cache: (int)seconds
|
||||
query: (NSString*)stmt
|
||||
with: (NSDictionary*)values;
|
||||
|
||||
/**
|
||||
* If the result of the query is already cached and is still valid,
|
||||
* return it. Otherwise, perform the query and cache the result
|
||||
* giving it the specified lifetime in seconds.<br />
|
||||
* If seconds is negative, the query is performed irrespective of
|
||||
* whether it is already cached, and its absolute value is used to
|
||||
* set the lifetime of the results.<br />
|
||||
* If seconds is zero, the cache for this query is emptied.<br />
|
||||
* Handles locking.<br />
|
||||
* Maintains -lastOperation date.
|
||||
*/
|
||||
- (NSMutableArray*) cache: (int)seconds simpleQuery: (NSString*)stmt;
|
||||
|
||||
/**
|
||||
* Purge any expired items from the cache.
|
||||
*/
|
||||
- (void) cachePurge;
|
||||
@end
|
||||
|
||||
/**
|
||||
* The SQLTransaction transaction class provides a convenient mechanism
|
||||
* for grouping together a series of SQL statements to be executed as a
|
||||
|
|
160
SQLClient.m
160
SQLClient.m
|
@ -42,6 +42,7 @@
|
|||
#include <Foundation/NSNull.h>
|
||||
#include <Foundation/NSDebug.h>
|
||||
#include <Foundation/NSPathUtilities.h>
|
||||
#include <Foundation/NSSet.h>
|
||||
|
||||
#include <GNUstepBase/GSLock.h>
|
||||
|
||||
|
@ -558,6 +559,7 @@ static unsigned int maxConnections = 8;
|
|||
DESTROY(_user);
|
||||
DESTROY(_name);
|
||||
DESTROY(_statements);
|
||||
DESTROY(_cache);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
|
@ -1632,6 +1634,164 @@ static void quoteString(NSMutableString *s)
|
|||
}
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@interface SQLClientCacheInfo : NSObject
|
||||
{
|
||||
@public
|
||||
NSString *query;
|
||||
NSMutableArray *result;
|
||||
NSTimeInterval expires;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SQLClientCacheInfo
|
||||
- (void) dealloc
|
||||
{
|
||||
DESTROY(query);
|
||||
DESTROY(result);
|
||||
[super dealloc];
|
||||
}
|
||||
- (unsigned) hash
|
||||
{
|
||||
return [query hash];
|
||||
}
|
||||
- (BOOL) isEqual: (SQLClientCacheInfo*)other
|
||||
{
|
||||
return [query isEqual: other->query];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SQLClient (Caching)
|
||||
- (NSMutableArray*) cache: (int)seconds
|
||||
query: (NSString*)stmt,...
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start (ap, stmt);
|
||||
stmt = [[self _prepare: stmt args: ap] objectAtIndex: 0];
|
||||
va_end (ap);
|
||||
|
||||
return [self cache: seconds simpleQuery: stmt];
|
||||
}
|
||||
|
||||
- (NSMutableArray*) cache: (int)seconds
|
||||
query: (NSString*)stmt
|
||||
with: (NSDictionary*)values
|
||||
{
|
||||
stmt = [[self _substitute: stmt with: values] objectAtIndex: 0];
|
||||
return [self cache: seconds simpleQuery: stmt];
|
||||
}
|
||||
|
||||
- (NSMutableArray*) cache: (int)seconds simpleQuery: (NSString*)stmt
|
||||
{
|
||||
NSMutableArray *result = nil;
|
||||
|
||||
[lock lock];
|
||||
NS_DURING
|
||||
{
|
||||
NSTimeInterval start;
|
||||
SQLClientCacheInfo *item;
|
||||
SQLClientCacheInfo *old;
|
||||
|
||||
item = AUTORELEASE([SQLClientCacheInfo new]);
|
||||
item->query = [stmt copy];
|
||||
old = [_cache member: item];
|
||||
start = SQLClientTimeNow();
|
||||
if (seconds > 0 && old != nil && old->expires > start)
|
||||
{
|
||||
// Cached item still valid ... just use it.
|
||||
result = old->result;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = [self backendQuery: stmt];
|
||||
_lastOperation = SQLClientTimeNow();
|
||||
if (_duration >= 0)
|
||||
{
|
||||
NSTimeInterval d;
|
||||
|
||||
d = _lastOperation - start;
|
||||
if (d >= _duration)
|
||||
{
|
||||
[self debug: @"Duration %g for query %@", d, stmt];
|
||||
}
|
||||
}
|
||||
if (seconds < 0)
|
||||
{
|
||||
seconds = -seconds;
|
||||
}
|
||||
if (old != nil)
|
||||
{
|
||||
if (seconds == 0)
|
||||
{
|
||||
// We hve been tod to remove the existing cached item.
|
||||
[_cache removeObject: old];
|
||||
}
|
||||
else
|
||||
{
|
||||
// We read a new value ... store it in cache
|
||||
ASSIGN(old->result, result);
|
||||
old->expires = _lastOperation + seconds;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Unless removing from cache (seconds == 0) we must
|
||||
* add the new item to the cache with the appropriate
|
||||
* expiry.
|
||||
*/
|
||||
if (seconds > 0)
|
||||
{
|
||||
ASSIGN(item->result, result);
|
||||
item->expires = _lastOperation + seconds;
|
||||
if (_cache == nil)
|
||||
{
|
||||
_cache = [NSMutableSet new];
|
||||
}
|
||||
[_cache addObject: item];
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Return an autoreleased copy ... not the original cached data.
|
||||
*/
|
||||
result = [NSMutableArray arrayWithArray: result];
|
||||
}
|
||||
NS_HANDLER
|
||||
{
|
||||
[lock unlock];
|
||||
[localException raise];
|
||||
}
|
||||
NS_ENDHANDLER
|
||||
[lock unlock];
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void) cachePurge
|
||||
{
|
||||
NSEnumerator *e;
|
||||
|
||||
[lock lock];
|
||||
e = [_cache objectEnumerator];
|
||||
if (e != nil)
|
||||
{
|
||||
NSTimeInterval start = SQLClientTimeNow();
|
||||
SQLClientCacheInfo *item;
|
||||
|
||||
while ((item = [e nextObject]) != nil)
|
||||
{
|
||||
if (item->expires < start)
|
||||
{
|
||||
[_cache removeObject: item];
|
||||
}
|
||||
}
|
||||
}
|
||||
[lock unlock];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SQLTransaction
|
||||
- (void) dealloc
|
||||
{
|
||||
|
|
47
WebServer.h
47
WebServer.h
|
@ -54,6 +54,7 @@
|
|||
<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>
|
||||
|
@ -201,6 +202,52 @@
|
|||
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 />
|
||||
|
|
139
WebServer.m
139
WebServer.m
|
@ -25,6 +25,7 @@
|
|||
|
||||
#include <Foundation/Foundation.h>
|
||||
#include "WebServer.h"
|
||||
#include "SQLClient.h"
|
||||
|
||||
@interface WebServerSession : NSObject
|
||||
{
|
||||
|
@ -176,6 +177,134 @@
|
|||
|
||||
@implementation WebServer
|
||||
|
||||
- (BOOL) accessRequest: (GSMimeDocument*)request
|
||||
response: (GSMimeDocument*)response
|
||||
{
|
||||
NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
|
||||
NSDictionary *conf = [defs dictionaryForKey: @"WebServerAccess"];
|
||||
NSString *path = [[request headerNamed: @"x-http-path"] value];
|
||||
NSDictionary *access = nil;
|
||||
NSString *stored;
|
||||
NSString *username;
|
||||
NSString *password;
|
||||
|
||||
while (access == nil)
|
||||
{
|
||||
access = [conf objectForKey: path];
|
||||
if ([access isKindOfClass: [NSDictionary class]] == NO)
|
||||
{
|
||||
NSRange r;
|
||||
|
||||
r = [path rangeOfString: @"/" options: NSBackwardsSearch];
|
||||
if (r.length > 0)
|
||||
{
|
||||
path = [path substringToIndex: r.location];
|
||||
}
|
||||
else
|
||||
{
|
||||
return YES; // No access dictionary - permit access
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
username = [[request headerNamed: @"x-http-username"] value];
|
||||
password = [[request headerNamed: @"x-http-password"] value];
|
||||
if ([access objectForKey: @"Users"] != nil)
|
||||
{
|
||||
stored = [[access objectForKey: @"Users"] objectForKey: username];
|
||||
}
|
||||
else if ([access objectForKey: @"UserDB"] != nil)
|
||||
{
|
||||
static Class c = nil;
|
||||
static BOOL beenHere = NO;
|
||||
|
||||
/*
|
||||
* We get the SQLClient class from thee runtime, so we don't have to
|
||||
* link the library directly ... which means that this class caan be
|
||||
* used without it as long as database accesss is not needed.
|
||||
*/
|
||||
if (beenHere == NO)
|
||||
{
|
||||
beenHere = YES;
|
||||
c = NSClassFromString(@"SQLClient");
|
||||
if (c == nil)
|
||||
{
|
||||
[self _alert: @"SQLClient library has not been linked"];
|
||||
}
|
||||
}
|
||||
|
||||
NS_DURING
|
||||
{
|
||||
NSDictionary *info = [access objectForKey: @"UserDB"];
|
||||
NSString *name = [info objectForKey: @"Name"];
|
||||
SQLClient *sql;
|
||||
|
||||
/*
|
||||
* try to re-use an existing client if possible.
|
||||
*/
|
||||
sql = [c existingClient: name];
|
||||
if (sql == nil)
|
||||
{
|
||||
sql = [c alloc];
|
||||
sql = [c initWithConfiguration: nil name: name];
|
||||
}
|
||||
stored = [sql queryString: @"SELECT ",
|
||||
[info objectForKey: @"Password"],
|
||||
@" FROM ",
|
||||
[info objectForKey: @"Table"],
|
||||
@" WHERE ",
|
||||
[info objectForKey: @"Username"],
|
||||
@" = ",
|
||||
[sql quote: username],
|
||||
nil];
|
||||
}
|
||||
NS_HANDLER
|
||||
{
|
||||
[self _alert: @"Read from database failed - %@", localException];
|
||||
stored = nil;
|
||||
}
|
||||
NS_ENDHANDLER
|
||||
}
|
||||
|
||||
|
||||
if (username == nil || password == nil || [password isEqual: stored] == NO)
|
||||
{
|
||||
NSString *realm = [access objectForKey: @"Realm"];
|
||||
NSString *auth;
|
||||
|
||||
auth = [NSString stringWithFormat: @"Basic realm=\"%@\"", realm];
|
||||
|
||||
/*
|
||||
* Return status code 401 (Aunauthorised)
|
||||
*/
|
||||
[response setHeader: @"http"
|
||||
value: @"HTTP/1.1 401 Unauthorised"
|
||||
parameters: nil];
|
||||
[response setHeader: @"WWW-authenticate"
|
||||
value: auth
|
||||
parameters: nil];
|
||||
|
||||
[response setContent:
|
||||
@"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"
|
||||
@"<html><head><title>401 Authorization Required</title></head><body>\n"
|
||||
@"<h1>Authorization Required</h1>\n"
|
||||
@"<p>This server could not verify that you "
|
||||
@"are authorized to access the resource "
|
||||
@"requested. Either you supplied the wrong "
|
||||
@"credentials (e.g., bad password), or your "
|
||||
@"browser doesn't understand how to supply "
|
||||
@"the credentials required.</p>\n"
|
||||
@"</body></html>\n"
|
||||
type: @"text/html"];
|
||||
|
||||
return NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
return YES; // OK to access
|
||||
}
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
if (_ticker != nil)
|
||||
|
@ -1312,7 +1441,6 @@ unescapeData(const unsigned char* bytes, unsigned length, unsigned char *buf)
|
|||
{
|
||||
GSMimeDocument *request;
|
||||
GSMimeDocument *response;
|
||||
BOOL responded = NO;
|
||||
NSString *str;
|
||||
NSString *con;
|
||||
NSMutableData *raw;
|
||||
|
@ -1390,9 +1518,12 @@ unescapeData(const unsigned char* bytes, unsigned length, unsigned char *buf)
|
|||
{
|
||||
[session setProcessing: YES];
|
||||
[session setTicked: _ticked];
|
||||
responded = [_delegate processRequest: request
|
||||
response: response
|
||||
for: self];
|
||||
if ([self accessRequest: request response: response] == YES)
|
||||
{
|
||||
[_delegate processRequest: request
|
||||
response: response
|
||||
for: self];
|
||||
}
|
||||
_ticked = [NSDate timeIntervalSinceReferenceDate];
|
||||
[session setTicked: _ticked];
|
||||
[session setProcessing: NO];
|
||||
|
|
Loading…
Reference in a new issue