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.
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 -
-
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
-
HTTP Basic authentication
-
Limit access by IP address
-
Limit total number of simultaneous connections
-
Limit number of simultaneous connections from one
address
-
Limit idle time permitted on a connection
-
Limit size of request headers permitted
-
Limit size of request body permitted
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.
- 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
+ (unsigned)
decodeURLEncodedForm: (NSData*)data
into: (NSMutableDictionary*)dict;
Same as the instance method of the same name.
+ (unsigned)
encodeURLEncodedForm: (NSDictionary*)dict
into: (NSMutableData*)data;
Same as the instance method of the same name.
+ (NSData*)
parameter: (NSString*)name
at: (unsigned)index
from: (NSDictionary*)params;
Same as the instance method of the same
name.
+ (NSString*)
parameterString: (NSString*)name
at: (unsigned)index
from: (NSDictionary*)params
charset: (NSString*)charset;
Same as the instance method of the same
name.
- (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;
};
};
};
- (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.
- (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.
- (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.
- (BOOL)
isSecure;
Returns YES
if the server is for HTTPS
(encrypted connections), NO
otherwise.
- (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
.
- (NSData*)
parameter: (NSString*)name
from: (NSDictionary*)params;
- (NSString*)
parameterString: (NSString*)name
at: (unsigned)index
from: (NSDictionary*)params;
- (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).
- (NSString*)
parameterString: (NSString*)name
from: (NSDictionary*)params;
- (NSString*)
parameterString: (NSString*)name
from: (NSDictionary*)params
charset: (NSString*)charset;
- (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.
- (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.
- (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.
- (void)
setConnectionTimeout: (NSTimeInterval)aDelay;
Sets the time after which an idle connection should be
shut down.
Default is 30.0
- (void)
setDelegate: (id)anObject;
Sets the delegate object which processes requests for
the receiver.
- (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.
- (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.
- (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.
- (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.
- (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.
- (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.
- (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.
- (void)
setRoot: (NSString*)aPath;
Set root path for loading template files from.
Templates may only be loaded from within this
directory.
- (void)
setSubstitutionLimit: (unsigned)depth;
Sets the maximum recursion depth allowed
for substitutions into templates. This defaults to 4.
- (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.
- (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.
- 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
- (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).
- (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.
- (NSMutableDictionary*)
handlers;
Return dictionary of all handlers by name (path in
request which maps to that handler instance).
- (
WebServer*)
http;
Return the WebServer instance that the receiver is
acting as a delegate for.
- (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.
- (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.
- (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.
- (void)
webAlert: (NSString*)message
for: (
WebServer*)http;
Just write to stderr using NSLog.
- (void)
webAudit: (NSString*)message
for: (
WebServer*)http;
Log an audit record as UTF8 data on stderr.
- (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
@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.
@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.
- 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
- (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.
- (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.
- (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
- (void)
webLog: (NSString*)message
for: (
WebServer*)http;
Log a debug... if the delegate does not implement this
method, no logging is done.