WebServer documentation

Authors

Richard Frith-Macdonald (rfm@gnu.org)

Version: 28588

Date: 2009-09-01 10:57:11 +0100 (Tue, 01 Sep 2009)

Copyright: (C) 2004 Free Software Foundation, Inc.


Contents -

  1. The WebServer class
  2. What is the WebServer class?
  3. Performance and threading
  4. Software documentation for the WebServer class
  5. Software documentation for the WebServerBundles class
  6. Software documentation for the WebServerDelegate protocol

The WebServer class

What is the WebServer class?

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

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

Built-in facilities include -

Performance and threading

The WebServer class essentially works using asynchronous I/O in a single thread. The asynchronous I/O mechanism is capably of reading a request of up to the operating systems network buffer size in a single operation and similarly writing out a response of up to the operating system's network buffer size.
As long as requests and responses are within those limits, it can be assumed that low processing of a request in the [<WebServerDelegate>-processRequest:response:for:] method will have little impact on efficiency as the WebServer read request and write responses as rapidly as the delegates processing can handle them.
If however the I/O sizes are larger than the buffers, then writing a response will need to be multiple operations and each buffer full of data may need to wait for the next call to the processing method before it can be sent.
So, for large request/response sizes, or other cases where processing a single request at a time is a problem, the WebServer class provides a simple mechanism for supporting multithreaded use.

To use multiple threads, all you need to do is have the delegate implementation of [<WebServerDelegate>-processRequest:response:for:] pass processing to another thread and return NO. When processing is complete, the delegate calls -completedWithResponse: to pass the response back to the WebServer instance for delivery to the client.
NB. the -completedWithResponse: method is safe to call from any thread but all other methods of the class should be called only from the main thread. If a delegate needs to call methods of the WebServer instance in order to handle a request, it should do so in the [<WebServerDelegate>-processRequest:response:for:] method before handing control to another thread.

Software documentation for the WebServer class

WebServer : NSObject

Declared in:
WebServer.h

You create an instance of the WebServer class in order to handle incoming HTTP or HTTPS requests on a single port.

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

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

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

WebServerHosts
An array of host IP addresses to list the hosts permitted to send requests to the server. If defined, requests from other hosts will be rejected (with an HTTP 403 response). It may be better to use firewalling to control this sort of thing.
WebServerQuiet
An array of host IP addresses to refrain from logging ... this is useful if (for instance) you have a monitoring process which sends requests to the server to be sure it's alive, and don't want to log all the connections from this monitor.
Not only do we refrain from logging anything but exceptional events about these hosts, connections and requests by these hosts are not counted in statistics we generate.
ReverseHostLookup
A boolean (default NO) which specifies whether the server should lookup the host name for each incoming connection, and refuse connections where no host can be found. The downside of enabling this is that host lookups can be slow and cause performance problems.

To shut down the WebServer, you should call -setPort:secure: with nil arguments. This will stop the server listening for incoming connections and wait for any existing connections to be closed (or to time out), after which the timer used to check connection timeouts will be stopped, releasing the WebServer instance (which may cause it to be deallocated if you haven't retained it).

Method summary

decodeURLEncodedForm: into: 

+ (unsigned) decodeURLEncodedForm: (NSData*)data into: (NSMutableDictionary*)dict;
Same as the instance method of the same name.

encodeURLEncodedForm: into: 

+ (unsigned) encodeURLEncodedForm: (NSDictionary*)dict into: (NSMutableData*)data;
Same as the instance method of the same name.

parameter: at: from: 

+ (NSData*) parameter: (NSString*)name at: (unsigned)index from: (NSDictionary*)params;
Same as the instance method of the same name.

parameterString: at: from: charset: 

+ (NSString*) parameterString: (NSString*)name at: (unsigned)index from: (NSDictionary*)params charset: (NSString*)charset;
Same as the instance method of the same name.

accessRequest: response: 

- (BOOL) accessRequest: (GSMimeDocument*)request response: (GSMimeDocument*)response;
This method is called for each incoming request, and checks that the requested resource is accessible (basic user/password access control).
The method returns YES if access is granted, or returns NO and sets the appropriate response values if access is refused.
If access is refused by this method, the delegate is not informed of the request at all... so this forms an initial access control mechanism, but if it is passed, the delegate is still free to implement its own additional access control within the [<WebServerDelegate>-processRequest:response:for:] method.
The access control is managed by the WebServerAccess user default, which is a dictionary whose keys are paths, and whose values are dictionaries specifying the access control for those paths. Access control is done on the basis of the longest matching path.
Each access control dictionary contains an authentication realm string (keyed on Realm) and a dictionary containing username/password pairs (keyed on Users).
eg.
 WebServerAccess = {
   "" = {
     Realm = "general";
     Users = {
       Fred = 1942;
     };
   };
 };
 

completedWithResponse: 

- (void) completedWithResponse: (GSMimeDocument*)response;

This may only be called in the case where a call to the delegate's [<WebServerDelegate>-processRequest:response:for:] method to process a request returned NO, indicating that the delegate would handle the request in another thread and complete it later.

In such a case, the thread handling the request in the delegate must call this method upon completion (passing in the same request parameter that was passed to the delegate) to inform the WebServer instance that processing of the request has been completed and that it should now take over the job of sending the response to the client process.


decodeURLEncodedForm: into: 

- (unsigned) decodeURLEncodedForm: (NSData*)data into: (NSMutableDictionary*)dict;
Decode an application/x-www-form-urlencoded form and store its contents into the supplied dictionary.
The resulting dictionary keys are strings.
The resulting dictionary values are arrays of NSData objects.
You probably don't need to call this method yourself... more likely you will use the -parameters: method instead.
NB. For forms POST-ed using multipart/form-data you don't need to perform any explicit decoding as this will already have been done for you and the decoded form will be presented as the request GSMimeDocument. The fields of the form will be the component parts of the content of the request and can be accessed using the standard GSMimeDocument methods.
This method returns the number of fields actually decoded.

encodeURLEncodedForm: into: 

- (unsigned) encodeURLEncodedForm: (NSDictionary*)dict into: (NSMutableData*)data;
Encode an application/x-www-form-urlencoded form and store its representation in the supplied data object.
The dictionary contains the form, with keys as data objects or strings, and values as arrays of values to be placed in the data. Each value in the array may be a data object or a string.
As a special case, a value may be a data object or a string rather than an array... this is treated like an array of one value.
All non data keys and values are converted to data using utf-8 encoding.
This method returns the number of values actually encoded.

isSecure 

- (BOOL) isSecure;
Returns YES if the server is for HTTPS (encrypted connections), NO otherwise.

parameter: at: from: 

- (NSData*) parameter: (NSString*)name at: (unsigned)index from: (NSDictionary*)params;
Returns the index'th data parameter for the specified name.
Matching of names is case-insensitive
If there are no data items for the name, or if the index is too large for the number of items which exist, this returns nil.

parameter: from: 

- (NSData*) parameter: (NSString*)name from: (NSDictionary*)params;
Calls -parameter:at:from: with an index of zero.

parameterString: at: from: 

- (NSString*) parameterString: (NSString*)name at: (unsigned)index from: (NSDictionary*)params;
Calls -parameterString:at:from:charset: with a nil charset so that UTF-8 encoding is used for string conversion.

parameterString: at: from: charset: 

- (NSString*) parameterString: (NSString*)name at: (unsigned)index from: (NSDictionary*)params charset: (NSString*)charset;
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).

parameterString: from: 

- (NSString*) parameterString: (NSString*)name from: (NSDictionary*)params;
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).

parameterString: from: charset: 

- (NSString*) parameterString: (NSString*)name from: (NSDictionary*)params charset: (NSString*)charset;
Calls -parameterString:at:from:charset: with an index of zero.

parameters: 

- (NSMutableDictionary*) parameters: (GSMimeDocument*)request;
Extracts request parameters from the HTTP query string and from the request body (if it was application/x-www-form-urlencoded or multipart/form-data) and return the extracted parameters as a mutable dictionary whose keys are the parameter names and whose values are arrays containing the data for each parameter.
You should call this no more than once per request, storing the result and using it as an argument to the methods used to extract particular parameters.
Parameters from the request data are added to any found in the query string.
Values provided as multipart/form-data are also available in a more flexible format as the content of the request.

produceResponse: fromStaticPage: using: 

- (BOOL) produceResponse: (GSMimeDocument*)aResponse fromStaticPage: (NSString*)aPath using: (NSDictionary*)map;
Loads a template file from disk and places it in aResponse as content whose mime type is determined from the file extension using the provided mapping (or a simple built-in default mapping if map is nil).
If you have a dedicated web server for handling static pages (eg images) it is better to use that rather than vending static pages using this method. It's unlikely that this method can be as efficient as a dedicated server. However this mechanism is adequate for moderate throughputs.

produceResponse: fromTemplate: using: 

- (BOOL) produceResponse: (GSMimeDocument*)aResponse fromTemplate: (NSString*)aPath using: (NSDictionary*)map;
Loads a template file from disk and places it in aResponse as content of type 'text/html' with a charset of 'utf-8'.
The argument aPath is a path relative to the root path set using the -setRoot: method.
Substitutes values into the template from map using the -substituteFrom:using:into:depth: method.
Returns NO if them template could not be read or if any substitution failed. In this case no value is set in the response.
If the response is actually text of another type, or you want another characterset used, you can change the content type header in the request after you call this method.

setConnectionTimeout: 

- (void) setConnectionTimeout: (NSTimeInterval)aDelay;
Sets the time after which an idle connection should be shut down.
Default is 30.0

setDelegate: 

- (void) setDelegate: (id)anObject;
Sets the delegate object which processes requests for the receiver.

setDurationLogging: 

- (void) setDurationLogging: (BOOL)aFlag;
Sets a flag to determine whether logging of request and connection durations is to be performed.
If this is YES then the duration of requests and connections will be logged using the [<WebServerDelegate>-webLog:for:] method.
The request duration is calculated from the point where the first byte of data in the request is read to the point where the response has been completely written.
This is useful for debugging and where a full audit trail is required.

setMaxBodySize: 

- (void) setMaxBodySize: (unsigned)max;
Sets the maximum size of an uploaded request body.
The default is 4M bytes.
The HTTP failure response for too large a body is 413.

setMaxConnections: 

- (void) setMaxConnections: (unsigned)max;
Sets the maximum number of simultaneous connections with clients.
The default is 128.
A value of zero permits unlimited connections.
If this limit is reached, the behavior of the software depends upon the value set by the -setMaxConnectionsReject: method.

setMaxConnectionsPerHost: 

- (void) setMaxConnectionsPerHost: (unsigned)max;
Sets the maximum number of simultaneous connections with a particular remote host.
The default is 32.
A value of zero permits unlimited connections.
If this value is greater than that of -setMaxConnections: then it will have no effect as the maximum number of connections from one host cannot be reached.
The HTTP failure response for too many connections from a host is 503.

setMaxConnectionsReject: 

- (void) setMaxConnectionsReject: (BOOL)reject;

This setting (default value NO) determines the behavior of the software when the number of simultaneous incoming connections exceeds the value set by the -setMaxConnections: method.

If reject is NO, the software will simply not accept the incoming connections until some earlier connection is terminated, so the incoming connections will be queued by the operating system and may time-out if no connections become free quickly enough for them to be handled. In the case of a huge number of incoming connections the 'listen' queue of the operating system may fill up and connections may be lost altogether.

If reject is yes, then the service will set aside a slot for one extra connection and, when the number of permitted connections is exceeded, the server will accept the first additional connection, send back an HTTP 503 response, and drop the additional connection again. This means that clients should receive a 503 response rather than finding that their connection attempts block and possible time out.


setMaxRequestSize: 

- (void) setMaxRequestSize: (unsigned)max;
Sets the maximum size of an incoming request (including all headers, but not the body).
The default is 8K bytes.
The HTTP failure response for too large a request is 413.

setPort: secure: 

- (BOOL) setPort: (NSString*)aPort secure: (NSDictionary*)secure;
Sets the port and security information for the receiver... without this the receiver will not listen for incoming requests.
If secure is nil then the receiver listens on aPort for HTTP requests.
If secure is not nil, the receiver listens for HTTPS instead.
If secure is a dictionary containing CertificateFile, KeyFile and Password then the server will use the specified certificate and key files (which it will access using the password).
The secure dictionary may also contain other dictionaries keyed on IP addresses, and if the address that an incoming connection arrived on matches the key of a dictionary, that dictionary is used to provide the certificate information, with the top-level values being used as a fallback.
This method returns YES on success, NO on failure... if it returns NO then the receiver will not be capable of handling incoming web requests!
Typically a failure will be due to an invalid port being specified... a port may not already be in use and may not be in the range up to 1024 (unless running as the super-user).
Call this with a nil port argument to shut the server down as soon as all current connections are closed.

setRoot: 

- (void) setRoot: (NSString*)aPath;
Set root path for loading template files from.
Templates may only be loaded from within this directory.

setSubstitutionLimit: 

- (void) setSubstitutionLimit: (unsigned)depth;
Sets the maximum recursion depth allowed for substitutions into templates. This defaults to 4.

setVerbose: 

- (void) setVerbose: (BOOL)aFlag;
Sets a flag to determine whether verbose logging is to be performed.
If this is YES then all incoming requests and their responses will be logged using the [<WebServerDelegate>-webLog:for:] method.
Setting this to YES automatically sets duration logging to YES as well, though you can then call -setDurationLogging: to set it back to NO.
This is useful for debugging and where a full audit trail is required.

substituteFrom: using: into: depth: 

- (BOOL) substituteFrom: (NSString*)aTemplate using: (NSDictionary*)map into: (NSMutableString*)result depth: (unsigned)depth;
Perform substitutions replacing the markup in aTemplate with the values supplied by map and appending the results to the result.
Substitutions are recursive, and the depth argument is used to specify the current recursion depth (you should normally call this method with a depth of zero at the start of processing a template).
Any value inside SGML comment delimiters ('<!--' and '-->') is treated as a possible key in map and the entire comment is replaced by the corresponding map value (unless it is nil). Recursive substitution is done unless the mapped value starts with an SGML comment.
While the map is nominally a dictionary, in fact it may be any object which responds to the objectForKey: method by returning an NSString or nil.
The method returns YES on success, NO on failure (depth too great).
You don't normally need to use this method directly... call the -produceResponse:fromTemplate:using: method instead.

Software documentation for the WebServerBundles class

WebServerBundles : NSObject

Declared in:
WebServer.h
Conforms to:
WebServerDelegate
WebServerBundles is an example delegate for the WebServer class.
This is intended to act as a convenience for a scheme where the WebServer instance in a program is configured by values obtained from the user defaults system, and incoming requests may be handled by different delegate objects depending on the path information supplied in the request. The WebServerBundles instance is responsible for loading the bundles (based on information in the WebServerBundles dictionary in the user defaults system) and for forwarding requests to the appropriate bundles for processing.
If a request comes in which is not an exact match for the path of any handler, the request path is repeatedly shortened by chopping off the last path component until a matching handler is found.
The paths in the dictionary must not end with a slash... an empty string will match all requests which do not match a handler with a longer path.
 

Instance Variables

Method summary

defaultsUpdate: 

- (BOOL) defaultsUpdate: (NSNotification*)aNotification;
Handle a notification that the defaults have been updated... change WebServer configuration if necessary.
  • WebServerPort must be used to specify the port that the server listens on. See [WebServer -setPort:secure:] for details.
  • WebServerSecure may be supplied to make the server operate as an HTTPS server rather than an HTTP server. See [WebServer -setPort:secure:] for details.
  • WebServerBundles is a dictionary keyed on path strings, whose values are dictionaries, each containing per-handler configuration information and the name of the bundle containing the code to handle requests sent to the path. NB. the bundle name listed should omit the .bundle extension.
Returns YES on success, NO on failure (if the port of the WebServer cannot be set).

handlerForPath: info: 

- (id) handlerForPath: (NSString*)path info: (NSString**)info;
Returns the handler to be used for the specified path, or nil if there is no handler available.
If the info argument is non-null, it is used to return additional information, either the path actually matched, or an error string.

handlers 

- (NSMutableDictionary*) handlers;
Return dictionary of all handlers by name (path in request which maps to that handler instance).

http 

- (WebServer*) http;
Return the WebServer instance that the receiver is acting as a delegate for.

initAsDelegateOf: 

- (id) initAsDelegateOf: (WebServer*)http;
This is a designated initialiser for the class.
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.

processRequest: response: for: 

- (BOOL) processRequest: (GSMimeDocument*)request response: (GSMimeDocument*)response for: (WebServer*)http;

Handles an incoming request by forwarding it to another handler.
If a direct mapping is available from the path in the request to an existing handler, that handler is used to process the request . Otherwise, the WebServerBundles dictionary (obtained from the defaults system) is used to map the request path to configuration information listing the bundle containing the handler to be used.

The configuration information is a dictionary containing the name of the bundle (keyed on 'Name'), and this is used to locate the bundle in the applications resources.
Before a request is passed on to a handler, two extra headers are set in it... x-http-path-base and x-http-path-info being the actual path matched for the handler, and the remainder of the path after that base part.


registerHandler: forPath: 

- (void) registerHandler: (id)handler forPath: (NSString*)path;
Registers an object as the handler for a particular path.
Registering a nil handler destroys any existing handler for the path.

webAlert: for: 

- (void) webAlert: (NSString*)message for: (WebServer*)http;
Just write to stderr using NSLog.

webAudit: for: 

- (void) webAudit: (NSString*)message for: (WebServer*)http;
Log an audit record as UTF8 data on stderr.

webLog: for: 

- (void) webLog: (NSString*)message for: (WebServer*)http;
Just discard the message... please subclass or use a category to override this method if you wish to used the logged messages.



Instance Variables for WebServerBundles Class

_handlers

@protected NSMutableDictionary* _handlers;
Warning the underscore at the start of the name of this instance variable indicates that, even though it is not technically private, it is intended for internal use within the package, and you should not use the variable in other code.

_http

@protected WebServer* _http;
Warning the underscore at the start of the name of this instance variable indicates that, even though it is not technically private, it is intended for internal use within the package, and you should not use the variable in other code.




Software documentation for the WebServerDelegate protocol

WebServerDelegate

Declared in:
WebServer.h
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.
Method summary

processRequest: response: for: 

- (BOOL) processRequest: (GSMimeDocument*)request response: (GSMimeDocument*)response for: (WebServer*)http;
Process the HTTP request whose headers and data are provided in a GSMimeDocument.
Extra headers are created as follows -
x-http-method
The method from the HTTP request (eg. GET or POST)
x-http-path
The path from the HTTP request, or an empty string if there was no path.
x-http-query
The query string from the HTTP request or an empty string if there was no query.
x-http-version
The version from the HTTP request.
x-local-address
The IP address of the local host receiving the request.
x-local-port
The port of the local host receiving the request.
x-remote-address
The IP address of the host that the request came from.
x-remote-port
The port of the host that the request came from.
x-http-username
The username from the 'authorization' header if the request supplied HTTP basic authentication.
x-http-password
The password from the 'authorization' header if the request supplied HTTP basic authentication.
x-count-requests
The number of requests being precessed at the point when this request started (includes this request).
x-count-connections
The number of connections established to the WebServer at the point when this request started (including the connection this request arrived on).
x-count-connected-hosts
The number of connects hosts (IP addresses) at the point when this request started (including the host which sent this request).
x-count-host-connections
The number of connections to the web server from the host which sent this request at the point when this request started (includes the connection that this request arrived on).
On completion, the method must modify response to contain the data and headers to be sent out.
The 'content-length' header need not be set in the response as it will be overridden anyway.
The special 'HTTP' header will be used as the response/status line. If not supplied, 'HTTP/1.1 200 Success' or 'HTTP/1.1 204 No Content' will be used as the response line, depending on whether the data is empty or not.
If an exception is raised by this method, the response produced will be set to 'HTTP/1.0 500 Internal Server Error' and the connection will be closed.
If the method returns YES, the WebServer instance sends the response to the client process which made the request.
If the method returns NO, the WebServer instance assumes that the delegate is processing the request in another thread (perhaps it will take a long time to process) and takes no action until the delegate calls [WebServer -completedWithResponse:] to let it know that processing is complete and the response should at last be sent out.

webAlert: for: 

- (void) webAlert: (NSString*)message 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.

webAudit: for: 

- (void) webAudit: (NSString*)message for: (WebServer*)http;
Log an audit record... if the delegate does not implement this method, the message is logged to stderr.
The logged record is similar to the Apache common log format, though it differs after the timestamp:
ip address
the address of the client host making the request
ident
not currently implemented... '-' as placeholder
ident
not currently implemented... '-' as placeholder
user
the remote user name from the authorization header, or '-'
timestamp
the date/time enclosed in square brackets
command
The command sent in the request... as a quoted string
agent
The user-agent header from the request... as a quoted string
result
The initial response line... as a quoted string

webLog: for: 

- (void) webLog: (NSString*)message for: (WebServer*)http;
Log a debug... if the delegate does not implement this method, no logging is done.