Criollo

by thecatalinstan

thecatalinstan / Criollo

A powerful Cocoa web framework and HTTP server for macS, iOS and tvOS.

219 Stars 30 Forks Last release: 4 months ago (1.0.1) MIT License 987 Commits 44 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

Criollo

A powerful Cocoa web framework and HTTP server for macS, iOS and tvOS.

Build Status Version Status Platform Carthage compatible MIT License Twitter Gitter

Criollo helps create fast standalone or embedded web apps that deliver content directly over HTTP or FastCGI. You can write code in Swift or Objective-C and you can use the Cocoa technologies you already know.

It's as easy as this:

let server = CRHTTPServer()
server.get("/") { (req, res, next) in
  res.send("Hello world!")
}
server.startListening()

... and in Objective-C:

CRServer* server = [[CRHTTPServer alloc] init];
[server get:@"/" block:^(CRRequest *req, CRResponse *res, CRRouteCompletionBlock next) {
  [res send:@"Hello world!"];
}];
[server startListening];

Key Features

Criollo is designed with speed, security and flexibility in mind, that's why it comes with a few very useful features out of the box, thus allowing you to focus on the actual job your project needs to do, without having to jump through hoops in order to make it happen.

HTTPS

Criollo fully supports HTTPS on all platforms. You can pass the credentials as a PKCS#12 identity and password, or an X509 certificate and private key pair, either PEM or DER encoded.

server.isSecure = true

// Credentials: PKCS#12 Identity and password server.identityPath = Bundle.main.path(forResource: "identity", ofType: "p12") server.password = "123456"

// Credentials: PEM-encoded certificate and public key server.certificatePath = Bundle.main.path(forResource: "certificate", ofType: "pem") server.privateKeyPath = Bundle.main.path(forResource: "key", ofType: "pem")

// Credentials: DER-encoded certificate and public key server.certificatePath = Bundle.main.path(forResource: "certificate", ofType: "der") server.privateKeyPath = Bundle.main.path(forResource: "key", ofType: "der")

... and in Objective-C:

server.isSecure = YES;

// Credentials: PKCS#12 Identity and password server.identityPath = [NSBundle.mainBundle pathForResource:@"identity" ofType:@"p12"]; server.password = @"password";

// Credentials: PEM-encoded certificate and public key server.certificatePath = [NSBundle.mainBundle pathForResource:@"certificate" ofType:@"pem"]; server.privateKeyPath = [NSBundle.mainBundle pathForResource:@"key" ofType:@"pem"];

// Credentials: DER-encoded certificate and public key server.certificatePath = [NSBundle.mainBundle pathForResource:@"certificate" ofType:@"der"]; server.privateKeyPath = [NSBundle.mainBundle pathForResource:@"key" ofType:@"der"];

Routing

When defining routes, paths can be specified in three ways:

  • Fixed string (ex.
    /api
    ). This will match the string exactly.
  • Placeholders (ex.
    /posts/:pid
    ). The next path component after
    /posts
    , will be matched and added to
    request.query
    under the
    pid
    key.
  • Regex patterns (ex.
    /[0-9]{4}/[0-9]{1,2}/[a-zA-Z0-9-]+
    ). When the three patterns are matched, they are added to
    request.query
    , under the keys
    0
    ,
    1
    and
    2
    respectively.
server.add("/api") { (req, res, next) in
  // /api/?pid=12345
  res.send(req.query)
}

server.add("/posts/:pid") { (req, res, next) in // /posts/12345 res.send(req.query) }

server.add("/[0-9]{4}/[0-9]{1,2}/[a-zA-Z0-9-]+") { (req, res, next) in // /2017/10/my-first-criollo-app res.send(req.query) }

... and in Objective-C:

[server add:@"/api" block:^(CRRequest *req, CRResponse *res, CRRouteCompletionBlock next) {
  // /api/?pid=12345
  [res send:req];
}];

[server add:@"/posts/:pid" block:^(CRRequest *req, CRResponse *res, CRRouteCompletionBlock next) { // /posts/12345 [res send:req]; }];

[server add:@"/[0-9]{4}/[0-9]{1,2}/[a-zA-Z0-9-]+" block:^(CRRequest *req, CRResponse *res, CRRouteCompletionBlock next) { // /2017/10/my-first-criollo-app [res send:req]; }];

Controllers

Controllers provide a very simple way of grouping functionality into one semantical unit. They function as routers and allow you to define routes based on paths relative to the path they are themselves attached to.

// The controller class
class APIController : CRRouteController {
  override init(prefix: String) {
    super.init(prefix: prefix)

self.add("/status") { (req, res, next) in
  res.send(["status": true])
}

} }

// Add the controller to the server server.add("/api", controller:APIController.self)

... and in Objective-C:

// The controller class
@interface APIController : CRRouteController
@end

@implementation APIController

  • (instancetype)initWithPrefix:(NSString *)prefix { self = [super initWithPrefix:prefix]; if ( self != nil ) {

    [self add:@"/status" block:^(CRRequest *req, CRResponse *res, CRRouteCompletionBlock next) {

    [res send:@{@"status": @YES}];

    }];

    } }

@end

// Add the controller to the server [server add:@"/api" controller:APIController.class];

Views and View Controllers

View controllers render view objects, constructed from HTML resource files by calling the view's

render
method. This is achieved by the
CRViewConroller
,
CRView
and
CRNib
APIs respectively.

View controllers are powerful objects that allow you to easily standardise an app's appearance and group functionality together into a coherent unit.

HTML template file:

html




<title>{{title}}</title>


<h1>{{title}}</h1>
<p>{{content}}</p>

Source code: ```swift // The view controller class class HelloWorldViewController: CRViewController {

override func present(with request: CRRequest, response: CRResponse) -> String { self.vars["title"] = String(describing: type(of: self)) self.vars["content"] = "Hello from the view controller."

return super.present(with: request, response: response)

}

}

// Add the view controller to server server.add("/controller", viewController: HelloWorldViewController.self, withNibName: "HelloWorldViewController", bundle: nil) ```

... and in Objective-C:

// The view controller class
@interface HelloWorldViewController : CRViewController
@end

@implementation HelloWorldViewController

  • (NSString *)presentViewControllerWithRequest:(CRRequest *)request response:(CRResponse *)response { self.vars[@"title"] = NSStringFromClass(self.class); self.vars[@"content"] = @"Hello from the view controller.";

    return [super presentViewControllerWithRequest:request response:response]; }

@end

// Add the view controller to server [server add:@"/controller" viewController:HelloWorldViewController.class withNibName:@"HelloWorldViewController" bundle:nil];

Static File/Directory Serving

Criollo comes with built-in support for exposing both directories and individual over HTTP. The

CRStaticFileManager
and
CRStaticDirectoryManager
APIs enable you to do this.
// Expose the home directory (with auto-indexing)
server.mount("/pub", directoryAtPath: "~", options: [.autoIndex])

// Serve a single static file at a path server.mount("/info.plist", fileAtPath: Bundle.main.bundlePath.appending("/Info.plist"))

... and in Objective-C

// Expose the home directory (with auto-indexing)
[server mount:@"/pub" directoryAtPath:@"~" options:CRStaticDirectoryServingOptionsAutoIndex];

// Serve a single static file at a path [server mount:@"/info.plist" fileAtPath:[NSBundle.mainBundle.bundlePath stringByAppendingPathComponent:@"/Contents/Info.plist"]];

Multipart File Uploads

Criollo comes with builtin support for handling

multipart/form-data
POST requests, so that you can handle HTML file uploads out of the box. Uploaded files are provided in
request.files
, as an array of
CRUploadedFile
objects.
// Serve the first uploaded file back to the client
self.server.post("/image") { (req, res, next) in
  do {
    let data = try Data.init(contentsOf: (req.files!["0"]?.temporaryFileURL)!)
    res.setValue(req.env["HTTP_CONTENT_TYPE"]!, forHTTPHeaderField: "Content-type")
    res.setValue("\(data.count)", forHTTPHeaderField: "Content-Length")
    res.send(data)
  } catch {
    res.setValue("text/plain", forHTTPHeaderField: "Content-type")
    res.setValue("\(error.localizedDescription.count)", forHTTPHeaderField: "Content-length")
    res.send(error.localizedDescription)
  }
}

... and in Objective-C

// Serve the first uploaded file back to the client
[server post:@"/image" block:^(CRRequest *req, CRResponse *res, CRRouteCompletionBlock next) {
  NSError *error;
  NSData *data = [NSData dataWithContentsOfURL:req[0].temporaryFileURL options:0 error:&error];
  if ( error ) {
    [res setValue:@"text/plain" forHTTPHeaderField:@"Content-type"];
    [res setValue:@(error.description.length).stringValue forHTTPHeaderField:@"Content-length"];
    [res sendString:error.description];
  } else {
    [res setValue:request.env[@"HTTP_CONTENT_TYPE"] forHTTPHeaderField:@"Content-type"];
    [res setValue:@(data.length).stringValue forHTTPHeaderField:@"Content-length"];
    [res sendData:data];
  }
}];

Why?

Criollo was created in order to take advantage of the truly awesome tools and APIs that the Apple stack provides and serve content produced with them over the web.

It incorporates an HTTP web server and a FastCGI application server that are used to deliver content. The server is built on Grand Central Dispatch and designed for speed.

How to Use

Criollo can easily be embedded as a web-server inside your macOS, iOS or tvOS app, should you be in need of such a feature, however it was designed to create standalone, long-lived daemon style apps. It is fully

launchd
compatible and replicates the lifecycle and behavior of

NSApplication
, so that the learning curve should be as smooth as possible.

For a more real-world example, check out the criollo.io website, made using Criollo and available for your cloning pleasure at https://github.com/thecatalinstan/Criollo-Web.

See the Hello World Multi Target example for a demo of the two usage patterns.

Getting Started

Installing

The preferred way of installing Criollo is through CocoaPods. However, you can also embed the framework in your projects manually.

Installing with CocoaPods

  1. Create the
    Podfile
    if you don’t already have one. You can do so by running
    pod init
    in the folder of the project.
  2. Add Criollo to your
    Podfile
    .
    pod 'Criollo', '~> 0.5'
  3. Run
    pod install

Please note that Criollo will download CocoaAsyncSocket as a dependency.

Cloning the repo

Criollo uses CocoaAsyncSocket which is included as a git submodule

git clone --recursive https://github.com/thecatalinstan/Criollo.git

Get in Touch

If you have any questions regarding the project or how to do anything with it, please feel free to get in touch either on Twitter @criolloio or by plain old email [email protected].

I really encourage you to submit an issue, as your input is really and truly appreciated.


Check out the Criollo Blog for news, ideas and updates on Criollo.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.