Getting started

WebAppKit is a Mac OS X framework for building web applications. The easiest way to get started is to duplicate Example Web App and customize it as needed.

The action happens in your WAApplication subclass. Override -init to create routes and initialize things. Don't forget to call super.

WAApplicationMain() instantiates your application subclass (from Info.plist’s NSPrincipalClass) and starts an HTTP server running on the port specified in Info.plist’s WAHTTPServerPort. The server normally only listens on the loopback interface, unless you explicitly set WAHTTPServerExternalAccess to YES.

Info.plist keys

Key Type Description
NSPrincipalClass String The class name of the default application class.
WAHTTPServerPort Integer The TCP port number for the default server.
WAHTTPServerExternalAccess Boolean If enabled, the default server listens on all network interfaces. The default is NO, which causes the server to listen only on the loopback interface. Enable this only if you need to expose the web app server directly to the Internet. The loopback interface is enough for local debugging and reverse proxying.

Routes

A route lets you map certain requests, based on a path and an HTTP method, to an Objective-C method.

[self addRouteSelector:@selector(index) HTTPMethod:@"GET" path:@"/"];

WAApplication has properties for the request and response. Use these two objects to find out more about the request and to construct the response.

- (void)index {
    [self.response appendString:@"Hello World"];
}

Route paths can also contain wildcard components that are sent as string arguments to the method.

[self addRouteSelector:@selector(showName:) HTTPMethod:@"GET" path:@"/show/*"];

- (void)showName:(NSString*)name {
    [self.response appendFormat:@"Showing %@", [name HTMLEscapedString]];
    //...
}

Response output can be constructed by using the append... methods in WAResponse, but you can also return objects from route methods. Currently, WebAppKit supports NSString, NSData and WATemplate objects for return values.

- (id)index {
    return @"Hello World";
}

You can override the preprocess and postprocess methods for code that needs to run before or after every route.

Templates

WebAppKit has one built-in general-purpose template system called WATemplate.

<h1>Welcome</h1>
Welcome to my web site, <%print name>!<br/>
The current date and time is <%print [NSDate date]>.

WATemplate uses an Objective-C-like expression syntax. Read more

Templates can contain <%keyword> sequences:

<%print expression> Replaces the keyword sequence with the value of expression.
<%for variable in expression>
   body
<%end>
Iterates through expression using fast enumeration, setting variable to each object and repeating body once for every iteration.
<%if booleanExpression>
   body
<%elseif booleanExpression>
   body
<%else>
   body
<%end>
Includes body only if booleanExpression evaluates to true. Else and elseif are optional, and there can be any number of elseifs.
<%set variable = expression>
Sets variable in the current scope to the result of expression.

Templates normally use the file extension .wat, and you can instantiate a template from your Resources directory with templateNamed:. Use setValue:forKey: to fill in variables that you can access from within the template.

WATemplate *template = [WATemplate templateNamed:@"welcome"];
[template setValue:@"Joe" forKey:@"name"];
return template;

If you're using LLVM Compiler 4.0 or later (Xcode 4.5), you can use keyed subscripting to set variables:

template[@"name"] = @"Joe";

A template can have a 'parent'; another template that is used to wrap the contents of the inner template. This is useful for things like layouts, where you can put the basic HTML structure in one template and each page of content in its own. The child template is inserted by using the <%content> keyword in the parent. Variables from the child is available to the parent during evaluation.

<!DOCTYPE html>
<html>
<head>
    <title>My Site - <%print title.HTML></title>
</head>
<body>

    <h1>My Site</h1>
    <div id="content">
        <%content>
    </div>

</body>
</html>

Code:

WATemplate *layout = [WATemplate templateNamed:@"layout"];
WATemplate *example = [WATemplate templateNamed:@"example"];
example.parent = layout;
[example setValue:@"Example page" forKey:@"title"];
return example;

Models

Core Data

Just like regular Cocoa apps, WAK apps can use Core Data. It's a good option for easy management and persistence of data and relationships. WebAppKit adds a bunch of category methods that make Core Data easier to use. managedObjectContextFromModelNamed:storeName:type: handles locating files and initialization of a managed object model, a persistent store coordinator and a managed object context. This one line of code uses Model.mom from Resources and loads an SQLite store file named Data in your Application Support folder:

- (id)init {
    if((self = [super init])) {
        self.managedObjectContext = [NSManagedObjectContext managedObjectContextFromModelNamed:@"Model" 
                                                                                     storeName:@"Data" 
                                                                                          type:NSSQLiteStoreType];
    }
    return self;
}

There are also methods for easily fetching objects if you've got an NSManagedObject subclass. WACoreDataExtras looks up the corresponding entity, creates a fetch request and runs it. It can even sort and filter the result for you.

- (void)peopleSearch {
    NSString *query = [self.request valueForQueryParameter:@"q"];
    NSArray *people = [Person objectsInManagedObjectContext:moc
                                                   sortedBy:@"name"
                                                  ascending:YES
                                    matchingPredicateFormat:@"name CONTAINS[c] %@",
                                                            query];
    //...
}

Take a look at WACoreDataExtras.h for more.

SQLite

WebAppKit bundles FMDatabase, a nice wrapper for libsqlite3.

Sessions

Sessions can store any object that supports NSCoding. WebAppKit stores session data in an SQLite database. Begin by creating a session generator once, and keep it around. When you want to use a session, call sessionForRequest:response with the current request and response, and use valueForKey: and setValue:forKey (or keyed subscripting) to get and set values. WAApplication provides a predeclared sessionGenerator property where you can store a session generator. You can then later access the session for the current request and response through the session property.

- (id)init {
    ...
    self.sessionGenerator = [[WASessionGenerator alloc] initWithName:@"mySession"];
    ...
}

- (id)showUserPage {
    NSNumber *userID = self.session[@"userID"];
    //...
}

CSRF

You can use sessions for protecting against cross-site request forgery. For local POST forms, set the session property of your template to the WASession instance and create a hidden form input with WAKSessionToken as its name and <%token> as the value. Then use -[WASession validateRequestToken] in your route method.

<form action="/dosomething" method="post">
    <input type="text" name="name" />
    <input type="submit" />

    <input type="hidden" name="WAKSessionToken" value="<%token>" />
</form>

 

- (id)doSomething {
    if(![self.session validateRequestToken]) return nil;
    // no CSRF - do stuff!
}

HTTP Authentication

WebAppKit has support for Basic and Digest authentication.

if(![self.request hasValidAuthenticationForUsername:@"foo" password:@"bar"])
    [self.response requestAuthenticationForRealm:@"Test" scheme:WABasicAuthenticationScheme];

For Basic authentication, you can get the supplied user and password using -getAuthenticationUser:password:. If you want to avoid storing credentials as cleartext, you can precompute a credential hash with +credentialHashForUsername:password:realm: and later check against it using -hasValidAuthenticationForCredentialHash:.

Static files

WebAppKit serves files from the directory public in Resources, and its subdirectories. You can use this directory for things like images and scripts.

Deployment

You can expose the WebAppKit server directly to the Internet, but it's often a good idea to instead run a reverse proxy to let your app share port 80 with other services.

HAProxy

HAProxy can be used as a standalone reverse proxy, and supports web sockets.

frontend http-in *:80
  acl host_myfancyapp hdr(Host) -i myfancyapp.com
  use_backend myfancyapp if host_myfancyapp

backend myfancyapp
  server myfancyapp 127.0.0.1:9090

Apache

<VirtualHost *:80>
    ServerName myfancyapp.com
    ProxyPass / http://127.0.0.1:9090/
    ProxyPassReverse / http://127.0.0.1:9090/

    <Location />
        Order allow,deny
        Allow from all
    </Location>
</VirtualHost>

lighttpd

$HTTP["host"] == "myfancyapp.com" {
    proxy.server =  ("/" => (
        ("host" => "127.0.0.1", "port" => 9090)
    ))
}

nginx

server {
    listen       80;
    server_name  myfancyapp.com;

    location / {
        proxy_pass http://localhost:9090;
    }
}