libs-base/Tests/base/NSURLConnection/Helpers/TestWebServer.m
Richard Frith-MacDonald 4935550f1b Add NSURLConnection patch by Sergei Golovin
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@38214 72102866-910b-0410-8b05-ffd578937521
2014-11-29 11:39:38 +00:00

468 lines
11 KiB
Objective-C

/*
* Author: Sergei Golovin <Golovin.SV@gmail.com>
*/
#import "TestWebServer.h"
#import "RequestHandler.h"
@interface TestWebServer (Private)
/**
* Starts the detached thread. It is the entry point of the thread.
*/
- (void)_startDetached:(id)extra;
/**
* Starts the SimpleWebServer.
*/
- (void)_startHTTPServer:(id)extra;
/**
* Stops the SimpleWebServer.
*/
- (void)_stopHTTPServer;
@end
/* default 'constants' */
#define DEFAULTADDRESS @"127.0.0.1"
#define DEFAULTPORT @"54321"
#define DEFAULTMODE NO
#define DEFAULTLOGIN @"login"
#define DEFAULTPASSWORD @"password"
/* lock's conditions */
#define READY 0 /* the initial condition */
#define STARTED 1 /* the SimpleWebServer has been started */
#define STOPPED 2 /* the SimpleWebServer has been stopped */
/* the time step for the runloop */
#define TIMING 0.1
/* the maximum duration of running of the SimpleWebServer instance
after which the server must be shut down */
#define MAXDURATION 3.0
@implementation TestWebServer
- (id)init
{
return [self initWithAddress: DEFAULTADDRESS
port: DEFAULTPORT
mode: DEFAULTMODE
extra: nil];
}
- (id)initWithAddress:(NSString *)address
port:(NSString *)port
mode:(BOOL)detached
extra:(id)extra
{
if((self = [super init]) != nil)
{
_debug = NO;
_address = [[NSHost hostWithName: address] address];
RETAIN(_address);
ASSIGN(_port, port);
ASSIGN(_extra, extra);
_password = nil;
_login = nil;
_isSecure = NO;
if([extra isKindOfClass: [NSDictionary class]])
{
NSDictionary *d = extra;
NSString *proto;
if((_password = [d objectForKey: @"Password"]) != nil)
{
RETAIN(_password);
}
if((_login = [d objectForKey: @"Login"]) != nil)
{
RETAIN(_login);
}
if((proto = [d objectForKey: @"Protocol"]) != nil &&
[[proto lowercaseString] isEqualToString: @"https"])
{
_isSecure = YES;
}
}
if(nil == _login)
{
_login = DEFAULTLOGIN; RETAIN(_login);
}
if(nil == _password)
{
_password = DEFAULTPASSWORD; RETAIN(_password);
}
_traversalMap = [[NSMutableDictionary alloc]
initWithObjectsAndKeys:
[Handler200 class], @"200",
[Handler204 class], @"204",
[Handler301 class], @"301",
[Handler400 class], @"400",
[Handler401 class], @"401",
[Handler402 class], @"402",
[Handler404 class], @"404",
[Handler405 class], @"405",
[Handler409 class], @"409",
[Handler500 class], @"500",
[Handler505 class], @"505",
[Handler507 class], @"507",
[HandlerIndex class], @"index",
nil];
_lock = [[NSConditionLock alloc] initWithCondition: READY];
_threadToQuit = NO;
if(detached)
{
_serverThread = [[NSThread alloc] initWithTarget: self
selector: @selector(_startDetached:)
object: extra];
}
else
{
_serverThread = nil;
}
}
return self;
}
- (void)dealloc
{
DESTROY(_lock);
DESTROY(_address);
DESTROY(_port);
DESTROY(_extra);
DESTROY(_login);
DESTROY(_password);
_delegate = nil;
DESTROY(_traversalMap);
[super dealloc];
}
- (void)start:(id)extra
{
if([_server port] != nil)
{
if(YES == _debug)
{
NSWarnMLog(@"SimpleWebServer already started");
}
return;
}
if(nil != _serverThread)
{
if(![_serverThread isExecuting])
{
NSTimeInterval duration = 0.0;
[_serverThread start];
// wait for the SimpleWebServer is started
while(![_lock tryLockWhenCondition: STARTED] &&
duration < MAXDURATION)
{
[[NSRunLoop currentRunLoop]
runUntilDate: [NSDate dateWithTimeIntervalSinceNow: TIMING]];
duration += TIMING;
}
[_lock unlock];
if(duration >= MAXDURATION &&
nil != _delegate &&
[_delegate respondsToSelector: @selector(timeoutExceededByHandler:)])
{
[_delegate timeoutExceededByHandler: self];
[self stop];
}
}
else
{
NSWarnMLog(@"the detached thread is already started");
}
}
else
{
[self _startHTTPServer: extra];
}
}
- (void)stop
{
if([_server port] == nil)
{
if(YES == _debug)
{
NSWarnMLog(@"SimpleWebServer already stopped");
}
return;
}
if(nil != _serverThread)
{
if([_serverThread isExecuting])
{
NSTimeInterval duration = 0.0;
_threadToQuit = YES; // this makes the detached thread quiting from it's runloop
// wait for the SimpleWebServer is stopped
while(![_lock tryLockWhenCondition: STOPPED] &&
duration < MAXDURATION)
{
[[NSRunLoop currentRunLoop]
runUntilDate: [NSDate dateWithTimeIntervalSinceNow: TIMING]];
duration += TIMING;
}
[_lock unlockWithCondition: READY];
if(duration >= MAXDURATION &&
nil != _delegate &&
[_delegate respondsToSelector: @selector(timeoutExceededByHandler:)])
{
[_delegate timeoutExceededByHandler: self];
}
}
}
else
{
[self _stopHTTPServer];
}
}
- (BOOL) processRequest:(GSMimeDocument *)request
response:(GSMimeDocument *)response
for:(SimpleWebServer *)server
{
NSString *path;
NSString *component;
NSEnumerator *en;
BOOL ret = NO;
id handler;
// analyze the path to infer what the client wants
path = [[request headerNamed:@"x-http-path"] value];
// traverse over the path to search the right handler
en = [[path pathComponents] objectEnumerator];
while((component = [en nextObject]) != nil)
{
if((handler = [_traversalMap objectForKey: component]) != nil)
{
// the handler has been found
break; // while
}
}
if(nil == component)
{
// no component found... default 204
component = @"204";
handler = [_traversalMap objectForKey: component];
}
if(handler == [handler class])
{
// it is a Class not an instance... instantiate and substitute
// instead of the Class
Class cls = (Class)handler;
handler = [[cls alloc] init];
[_traversalMap setObject: handler forKey: component];
}
// set the handler
[handler setDelegate: _delegate];
[handler setLogin: _login];
[handler setPassword: _password];
if([handler respondsToSelector: @selector(setURLString:)])
{
if([handler isKindOfClass: [HandlerIndex class]])
{
NSString *urlString = [NSString stringWithFormat: @"%@://%@:%@/",
_isSecure ? @"https" : @"http",
_address,
_port];
[(HandlerIndex *)handler setURLString: urlString];
}
else if([handler isKindOfClass: [Handler301 class]])
{
// by default http://localhost:54322/
NSString *port = [NSString stringWithFormat: @"%u", [_port intValue] + 1]; // the TestWebServer's port + 1
NSString *urlString = [NSString stringWithFormat: @"%@://%@:%@/",
_isSecure ? @"https" : @"http",
_address,
port];
[(Handler301 *)handler setURLString: urlString];
}
}
// TODO: add session-related conditions here
[handler prehandleRequest: request
response: response
for: self];
// the main job
ret = [handler handleRequest: request
response: response
for: self];
// TODO: analyze 'ret'... if NO a default handler must be called
// TODO: add session-related conditions here
[handler posthandleRequest: request
response: response
for: self];
return ret;
}
/* end of SimpleWebServer delegate */
/* getters */
- (NSString *)address
{
return _address;
}
- (NSString *)port
{
return _port;
}
- (NSString *)login
{
return _login;
}
- (NSString *)password
{
return _password;
}
- (BOOL)isSecure
{
return _isSecure;
}
/* end of getters */
/* setters */
- (void)setDelegate:(id)delegate
{
_delegate = delegate;
}
- (void)setDebug:(BOOL)mode
{
_debug = mode;
}
/* end of setters */
@end /* TestWebServer */
@implementation TestWebServer (Private)
- (void)_startHTTPServer:(id)extra
{
NSString *certPath;
NSString *keyPath;
NSDictionary *secure = nil;
BOOL status;
_server = [SimpleWebServer new];
[_server setDebug: _debug];
[_server setDelegate: self];
if(_isSecure)
{
if([_address isEqualToString: @"127.0.0.1"] ||
[_address isEqualToString: @"localhost"])
{
certPath = [[NSBundle bundleForClass: [self class]]
pathForResource: @"testCert"
ofType: @"pem"];
keyPath = [[NSBundle bundleForClass: [self class]]
pathForResource: @"testKey"
ofType: @"pem"];
secure = [NSDictionary dictionaryWithObjectsAndKeys:
certPath, @"CertificateFile",
keyPath, @"KeyFile",
nil];
}
else
{
// NOTE: generate corresponding certificates for any address differing
// from 127.0.0.1
}
}
status = [_server setAddress: _address port: _port secure: secure]; // run the server
if(!status)
{
[NSException raise: NSInternalInconsistencyException
format: @"The server hasn't run. Perhaps the port %@ is invalid", DEFAULTPORT];
}
}
- (void)_stopHTTPServer
{
if(nil != _server && [_server port] != nil)
{
[_server stop]; // shut down the server
if(YES == _debug)
{
NSLog(@"%@: stopped SimpleWebServer %@", self, _server);
}
DESTROY(_server);
}
}
- (void)_startDetached:(id)extra
{
CREATE_AUTORELEASE_POOL(arp);
NSTimeInterval duration = 0.0;
[_lock lockWhenCondition: READY];
[self _startHTTPServer: extra];
[_lock unlockWithCondition: STARTED];
if(YES == _debug)
{
NSLog(@"%@: enter into runloop in detached thread %@", self, [NSThread currentThread]);
}
while(!_threadToQuit && duration < MAXDURATION)
{
[[NSRunLoop currentRunLoop]
runUntilDate: [NSDate dateWithTimeIntervalSinceNow: TIMING]];
duration += TIMING;
}
if(YES == _debug)
{
NSLog(@"%@: exit from runloop in detached thread %@", self, [NSThread currentThread]);
}
if(duration >= MAXDURATION &&
nil != _delegate &&
[_delegate respondsToSelector: @selector(timeoutExceededByHandler:)])
{
[_delegate timeoutExceededByHandler: self];
}
[_lock lockWhenCondition: STARTED];
[self _stopHTTPServer];
[_lock unlockWithCondition: STOPPED];
DESTROY(arp);
}
@end /* TestWebServer (Private) */