Objective-C in the Cloud

Request based applications in Detail

The "documentation about request based applications in general" only gives a high-level overview about what is possible. This chapter is intended to be read by developers who like to know more about request based applications.

Most important facts

Here is a list of the most important facts about request based applications:

  • Request based applications form a very thin wrapper around OCFWeb.
  • You learn a lot about request based applications by simply reading the documentation of OCFWeb.
  • The wrapper around OCFWeb leaks two classes: OCFRequest and OCFResponse which can be used directly from your application.
  • You can write almost any web application you want by developing cloud apps that respond to requests.

Routing and Parameters

Your should already know how to add routes and handlers to your cloud application. If you don't know how to do that please read the documentation about request based applications.

When adding a request handler you have to specify a HTTP method and a path like this:

 1 #import "CloudApp.h"
 2 #import <OCFoundation/OCFoundation.h>
 3 
 4 @implementation CloudApp
 5 
 6 + (void)finishLaunching {
 7   NSBundle *mainBundle = NSBundle.mainBundle;
 8   // Add the route and handler
 9   [self handleRequestsWithMethod:@"GET"
10                     matchingPath:@"/"
11                        withBlock:^(OCFRequest *request) {
12                          // respond with "Hello World"
13                          [request respondWith:@"Hello World"];
14                        }];
15 }
16 
17 @end

In the examples above we used GET as the HTTP method and / as the path. You can do more sophisticated things though.

The HTTP method you specify can be a regular expression. For example: This allows you to route all requests - no matter the request method - to a single handler (by using @"^.+$") as the method. It should be mentioned that doing this is considered bad practice. If you have the exact same handler for different HTTP methods you should rethink your architecture.

The path can be a pattern with placeholders. A placeholder begins with a :. This is useful if you have hierarchical URLs/paths like this:

  • GET /countries/: Lists all countries.
  • GET /countries/Germany/: Lists only the country called Germany.
  • GET /countries/Germany/states/Berlin/: Lists only the german state Berlin.

Let's assume you want to add and implement a handler that displays a specific state. You would do that by using the path pattern /countries/:country/states/:state/.

This assumes that the path component of your cloud application is set to countries.

Example: A Route with Placeholders

The following example shows you how to register a handler that is only executed if the request path is matching a specific pattern.

 1 #import "CloudApp.h"
 2 #import <OCFoundation/OCFoundation.h>
 3 
 4 @implementation CloudApp
 5 
 6 + (void)finishLaunching {
 7   [self handleRequestsWithMethod:@"GET"
 8                     matchingPath:@"/countries/:country/states/:state/"
 9                        withBlock:^(OCFRequest *request) {
10                          // respond with "Hello World"
11                          [request respondWith:[request.parameters description]];
12                        }];
13 }
14 
15 @end

The pattern used (/countries/:country/states/:state/) has two placeholders:

  1. country and
  2. state

If a request with a matching path comes in (GET /countries/Germany/states/Berlin) then the specified handler is executed. The parameters property of the request passed to the handler is a dictionary which contains two key/value-pairs:

  1. country = Germany
  2. state = Berlin

The request handler simply returns a description of the dictionary in this case. So you don't have to parse the path of the request yourself.

Finding a handler

There is no limit regarding the number of handlers you add. If a request comes in then one out of many handlers has to be picked and executed. OCFWebApplication finds a handler for an incoming request in two steps:

  1. Within the first step OCFWebApplication is only using the HTTP method of the request to find handlers which can handle requests of that type: If a GET request comes is all handlers which handle GET requests are determined. Let's call those handlers handler candidates. The remaining handlers are ignored from now on.
  2. For each handler candidate the path pattern is evaluated against the path of the HTTP request. The first handler whose path matches the pattern will be executed.

Multipart Requests

Multipart requests are often used to allow users to upload files by using an HTML form like this:

1 <form method="POST" action="/upload" id="files" enctype="multipart/form-data">
2   <input name="file-a" type="file" size="50" maxlength="1048576" accept="*"/>
3   <input name="file-b" type="file" size="50" maxlength="1048576" accept="*"/>
4   <input type="submit" value="Upload">
5 </form>

This form allows someone to upload two files (file-a and file-b). When a multipart request reaches one of your handlers you can get the uploaded files by using the request object. This is where the parameters of a request come in again. The parts of a multipart request are simply entries in the parameters dictionary. With regards to the example above you would access the multipart information like this:

 1 [self handleRequestsWithMethod:@"POST"
 2                   matchingPath:@"/upload"
 3                      withBlock:^(OCFRequest *request) {
 4                        NSDictionary *parameters = request.parameters;
 5                        
 6                        // file-a
 7                        NSDictionary *fileA = parameters[@"file-a"];
 8                        NSString *temporaryPathA = fileA[@"temporaryPath"];
 9                        NSString *contentTypeA = fileA[@"contentType"];
10                        NSString *filenameA = fileA[@"filename"];
11                        [self doSomethingWithFile:temporaryPathA];
12                        
13                        // file-b
14                        NSDictionary *fileB = parameters[@"file-b"];
15                        NSString *temporaryPathB = fileB[@"temporaryPath"];
16                        NSString *contentTypeB = fileB[@"contentType"];
17                        NSString *filenameB = fileB[@"filename"];
18                        [self doSomethingWithFile:temporaryPathB];
19                        
20                        // Send something back to the client
21                        [request respondWith:@"Got it!";
22                        
23                        // Do not access temporaryPathA and temporaryPathB
24                        // after you called -respondWith:.
25 }];

Each part is represented by a dictionary with three key-value-pairs:

  1. temporaryPath (mandatory): An NSString pointing to a temporary file which contains the raw data of the part. You can access the file by loading it in an NSData object or some other means. The file at temporaryPath will automatically be deleted once you call -respondWith:.
  2. contentType (optional): An NSString which contains the content type of the file. You can use the UTI API from Apple to convert the content type to an actual UTI if you need to.
  3. filename (optional): An NSString which contains the name of the uploaded file.

Your code should not crash if one or all of the optional key-value-paris are missing.