#Criollo
A powerful Cocoa web framework and HTTP server for macS, iOS and tvOS.
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 torequest.query
under thepid
key. - Regex patterns (ex.
/[0-9]{4}/[0-9]{1,2}/[a-zA-Z0-9-]+
). When the three patterns are matched, they are added torequest.query
, under the keys0
,1
and2
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:
<!-- Define some placeholders -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{title}}</title>
</head>
<body>
<h1>{{title}}</h1>
<p>{{content}}</p>
</body>
</html>
Source code:
// 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
- Read the “Getting Started” guide and move further with the “Doing More Stuff” guide
- Check out the Examples
- Check out the API Reference for a look at the APIs available
- Learn how to deploy your Criollo apps in the “Deployment” guide
- Check out the Criollo Blog for news, ideas and updates on Criollo.
Installing
The preferred way of installing Criollo is through CocoaPods. However, you can also embed the framework in your projects manually.
Installing with CocoaPods
- Create the
Podfile
if you don’t already have one. You can do so by runningpod init
in the folder of the project. - Add Criollo to your
Podfile
.pod 'Criollo', '~> 0.5'
- Run
pod install
Please note that Criollo will download CocoaAsyncSocket as a dependency.
Swift Package Manager
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.