GCDWebServer/README.md

197 lines
10 KiB
Markdown
Raw Normal View History

2012-12-30 02:22:35 +08:00
GCDWebServer
============
2012-12-30 15:27:20 +08:00
GCDWebServer is a lightweight GCD based HTTP 1.1 server for Mac & iOS apps written from scratch with the following goals in mind:
* Entirely built with an event-driven design using [Grand Central Dispatch](http://en.wikipedia.org/wiki/Grand_Central_Dispatch) for maximum performance and concurrency
2012-12-30 16:49:43 +08:00
* Well designed API for easy integration
* Handle URL requests with simple GCD blocks instead of subclasses or delegates
2012-12-30 14:43:08 +08:00
* Support for streaming large HTTP bodies for requests and responses to minimize memory usage
* Built-in parser for web forms submitted using "application/x-www-form-urlencoded" or "multipart/form-data"
2012-12-30 16:50:22 +08:00
* Minimal number of source files and no dependencies on third-party source code
2012-12-30 17:23:25 +08:00
* Available under a friendly [New BSD License](GCDWebServer/blob/master/LICENSE)
2012-12-30 14:43:08 +08:00
2012-12-30 16:54:01 +08:00
What's not available out of the box but can be implemented on top of the API:
* Authentication like Basic Authentication
* Web forms submitted using "multipart/mixed"
What's not supported (but not really required from an embedded HTTP server):
2012-12-30 14:43:08 +08:00
* Keep-alive connections
* HTTPS
Requirements:
* OS X 10.7 or later
* iOS 5.0 or later
2012-12-30 15:12:22 +08:00
Hello World
===========
2012-12-30 14:43:08 +08:00
A simple HTTP server that runs on port 8080 and returns a "Hello World" HTML page to any request:
```objectivec
2012-12-30 15:12:22 +08:00
#import "GCDWebServer.h"
int main(int argc, const char* argv[]) {
@autoreleasepool {
// Create server and add default handler
GCDWebServer* webServer = [[GCDWebServer alloc] init];
[webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
}];
// Run server on port 8080 until SIGINT received
[webServer runWithPort:8080];
// Destroy server
[webServer release];
}
return 0;
}
```
Serving a Static Website
========================
GCDWebServer includes a built-in handler that can recursively serve a directory (it also lets you control how the "Cache-Control" header should be set):
```objectivec
#import "GCDWebServer.h"
int main(int argc, const char* argv[]) {
@autoreleasepool {
GCDWebServer* webServer = [[GCDWebServer alloc] init];
[webServer addHandlerForBasePath:@"/" localPath:NSHomeDirectory() indexFilename:nil cacheAge:3600];
[webServer runWithPort:8080];
[webServer release];
}
return 0;
}
```
2012-12-30 15:27:20 +08:00
Using GCDWebServer
==================
2012-12-30 15:12:22 +08:00
2012-12-30 15:27:20 +08:00
You start by creating an instance of the 'GCDWebServer' class. Note that you can have multiple web servers running in the same app as long as they listen on different ports.
Then you add one or more "handlers" to the server: each handler gets a chance to handle an incoming web request and provide a response. Handlers are called in a LIFO queue, so the latest added handler overrides any previously added ones.
2012-12-30 15:12:22 +08:00
2012-12-30 15:27:20 +08:00
Finally you start the server on a given port. Note that even if built on GCD, GCDWebServer still requires a runloop to be around (by default the main thread runloop is used). This is because there is no CGD API at this point to handle listening sockets, so it must be done using CFSocket which requires a runloop. However, the runloop is only used to accept the connection: immediately afterwards, the connection handling is dispatched to GCD queues.
2012-12-30 15:51:37 +08:00
Implementing Handlers
=====================
2012-12-30 15:27:20 +08:00
2012-12-30 15:51:37 +08:00
GCDWebServer relies on "handlers" to process incoming web requests and generating responses. Handlers are implemented with GCD blocks which makes it very easy to provide your owns. However, they are executed on arbitrary threads within GCD so special attention must be paid to thread-safety.
Handlers require 2 GCD blocks:
2012-12-30 17:23:25 +08:00
* The 'GCDWebServerMatchBlock' is called on every handler added to the 'GCDWebServer' instance whenever a web request has started (i.e. HTTP headers have been received). It is passed the basic info for the web request (HTTP method, URL, headers...) and must decide if it wants to handle it or not. If yes, it must return a 'GCDWebServerRequest' instance which will be used to read (and optionally parse) the web request HTTP body. Otherwise, it simply returns nil. GCDWebServer provides several subclasses of 'GCDWebServerRequest' to handle common cases like storing the body in memory or to a file on disk. See [GCDWebServerRequest.h](GCDWebServer/blob/master/CGDWebServer/GCDWebServerRequest.h) for the full list.
* The 'GCDWebServerProcessBlock' is called after the web request has been fully received and is passed the 'GCDWebServerRequest' instance created at the previous step. It must return a 'GCDWebServerResponse' instance which will be used to send the reponse HTTP headers and body. GCDWebServer provides several subclasses of 'GCDWebServerResponse' to handle common cases like HTML text in memory or streaming a file from disk. See [GCDWebServerResponse.h](GCDWebServer/blob/master/CGDWebServer/GCDWebServerResponse.h) for the full list.
2012-12-30 15:12:22 +08:00
2012-12-30 17:28:41 +08:00
Note that most methods on 'GCDWebServer' to add handlers only take a 'GCDWebServerProcessBlock' as they already provide a built-in 'GCDWebServerMatchBlock' e.g. to match a URL path with a Regex.
2012-12-30 15:12:22 +08:00
Advanced Example 1: Implementing HTTP Redirects
===============================================
Here's an example handler that redirects "/" to "/index.html" using the convenience method on 'GCDWebServerResponse' (it sets the HTTP status code and 'Location' header automatically):
```objectivec
[self addHandlerForMethod:@"GET"
path:@"/"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
2012-12-30 17:00:33 +08:00
return [GCDWebServerResponse responseWithRedirect:[NSURL URLWithString:@"index.html" relativeToURL:request.URL]
permanent:NO];
2012-12-30 15:12:22 +08:00
}];
```
Advanced Example 2: Implementing Forms
======================================
To implement an HTTP form, you need a pair of handlers:
* The GET handler does not expect any body in the HTTP request and therefore uses the 'GCDWebServerRequest' class. The handler generates a response containing a simple HTML form.
* The POST handler expects the form values to be in the body of the HTTP request and percent-encoded. Fortunately, GCDWebServer provides the request class 'GCDWebServerURLEncodedFormRequest' which can automatically parse such bodies. The handler simply echoes back the value from the user submitted form.
```objectivec
[webServer addHandlerForMethod:@"GET"
path:@"/"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
2012-12-30 14:43:08 +08:00
2012-12-30 15:12:22 +08:00
NSString* html = @" \
<html><body> \
<form name=\"input\" action=\"/\" method=\"post\" enctype=\"application/x-www-form-urlencoded\"> \
Value: <input type=\"text\" name=\"value\"> \
<input type=\"submit\" value=\"Submit\"> \
</form> \
</body></html> \
";
return [GCDWebServerDataResponse responseWithHTML:html];
2012-12-30 14:43:08 +08:00
}];
2012-12-30 15:12:22 +08:00
[webServer addHandlerForMethod:@"POST"
path:@"/"
requestClass:[GCDWebServerURLEncodedFormRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
NSString* value = [[(GCDWebServerURLEncodedFormRequest*)request arguments] objectForKey:@"value"];
NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p></body></html>", value];
return [GCDWebServerDataResponse responseWithHTML:html];
}];
2012-12-30 14:43:08 +08:00
```
2012-12-30 16:10:27 +08:00
2012-12-30 17:20:42 +08:00
Advanced Example 3: Serving a Dynamic Website
=============================================
2012-12-30 16:10:27 +08:00
2012-12-30 17:32:05 +08:00
GCDWebServer provides an extension to the 'GCDWebServerDataResponse' class that can return HTML content generated from a template and a set of variables (using the format '%variable%'). It is a very basic template system and is really intended as a starting point to building more advanced template systems by subclassing 'GCDWebServerResponse'.
2012-12-30 17:20:42 +08:00
2012-12-30 17:28:41 +08:00
Assuming you have a website directory in your app containing HTML template files along with the corresponding CSS, scripts and images, it's pretty easy to turn it into a dynamic website:
2012-12-30 17:20:42 +08:00
```objectivec
// Get the path to the website directory
NSString* websitePath = [[NSBundle mainBundle] pathForResource:@"Website" ofType:nil];
// Add a default handler to serve static files (i.e. anything other than HTML files)
[self addHandlerForBasePath:@"/" localPath:websitePath indexFilename:nil cacheAge:3600];
// Add an override handler for all requests to "*.html" URLs to do the special HTML templatization
[self addHandlerForMethod:@"GET"
pathRegex:@"/.*\.html"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
NSDictionary* variables = [NSDictionary dictionaryWithObjectsAndKeys:@"value", @"variable", nil];
return [GCDWebServerDataResponse responseWithHTMLTemplate:[websitePath stringByAppendingPathComponent:request.path]
variables:variables];
}];
// Add an override handler to redirect "/" URL to "/index.html"
[self addHandlerForMethod:@"GET"
path:@"/"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [GCDWebServerResponse responseWithRedirect:[NSURL URLWithString:@"index.html" relativeToURL:request.URL]
permanent:NO];
];
```
Final Example: File Downloads and Uploads From iOS App
======================================================
GCDWebServer was originally written for the [ComicFlow](http://itunes.apple.com/us/app/comicflow/id409290355?mt=8) comic reader app for iPad. It uses it to provide a web server for people to upload and download comic files directly over WiFi to and from the app.
2012-12-30 16:10:27 +08:00
2012-12-30 16:19:08 +08:00
ComicFlow is [entirely open-source](https://code.google.com/p/comicflow/) and you can see how it uses GCDWebServer in the [AppDelegate.m](https://code.google.com/p/comicflow/source/browse/Classes/AppDelegate.m) file.