🍬 Announcing mod_swift

I am pleased to announce the first demo release of mod_swift! A neat way to write Apache modules in Swift.

Server Side Swift the right way.

mod_swift is a technology demo which shows how to write native modules for the Apache Web Server in the Swift programming language. The demo includes a C module to load Swift modules, a basic demo module, the ApacheExpress framework which provides an Express like API for mod_swift, a demo for ApacheExpress, a Todo MVC backend, and a few supporting libraries (such as Freddy or Noze.io Mustache).

What is an Apache module?

Well, Apache is a highly modular and efficient server framework. The httpd daemon itself is quite tiny and pretty much all webserver functionality is actually implemented in the form of modules. Be it thread handling, access control, mime detection or content negotation - all of that is implemented as modules. And can be replaced by own modules!

The Apache core modules are written in portable C. Some modules are built right into the server, but most are loaded as dynamic libraries. Which ones is specified by the user in the configuration file, e.g.:

LoadModule authz_core_module /usr/libexec/apache2/mod_authz_core.so
LoadModule mime_module       /usr/libexec/apache2/mod_mime.so

Now with mod_swift you can write such modules using the Swift programming language. Enter:

LoadSwiftModule ApacheMain /usr/libexec/apache2/mods_demo.so

This is a little different to something like mod_php which enables Apache to directly interpret PHP scripts. mod_php itself is C software and a single module. Since Swift compiles down to regular executable binaries, and because Swift has excellent C integration, you can write arbitrary modules with mod_swift which behave just like the regular C modules.

This is boring, show us something!

OK, ok. So in short with mod_swift you can extend Apache in many many ways, or just write dynamic HTTP endpoints, be it dynamically generated web pages or web services. There is no interpreter or JVM or proxy, the compiled Swift code runs directly as part of Apache - a.k.a. superfast. Here is your screenshot:

and here is some code used to generate that page (shortened):

let sampleDict  : [ String : Any ] = [
  "name"        : "Chris",
  "value"       : 10000,
  "taxed_value" : Int(10000 - (10000 * 0.4))
]

func MustacheHandler(p: UnsafeMutablePointer<request_rec>?) -> Int32 {
  var req = ZzApacheRequest(raw: p!) // make it nicer to use
  guard req.handler == "de.zeezide.mustache" else { return DECLINED }
  
  req.contentType = "text/html; charset=ascii"
  guard let fn = req.filename else { return HTTP_NOT_FOUND }
  
  guard let template = try? String(contentsOfFile: fn) else {
    return HTTP_INTERNAL_SERVER_ERROR
  }
  req.puts(MustacheParser().parse(string: template).render(object: sampleDict))
  return OK
}

What this does is it loads a Mustache template located in the Apache documents directory It then resolves the template from some Swift dictionary and returns the result to the browser. Note that the file lookup and all that is managed by other Apache modules, this handler is just invoked for Mustache templates (as configured in our apache.conf).

Remember that this is just a proof of concept. Quite likely you’d want some wrapper library making the Apache API a little ‘Swiftier’. Also remember that you can use this not only to deliver dynamic content, but you can also use it to add new authentication modules to Apache, or write new filter modules (say one which converts XML to JSON on demand).

Know what? This looks awkwardly difficult …

Fair enough. So we integrated a tiny subset of Noze.io to allow you to do just that. This is what it looks like:

func expressMain() {
  apache.onRequest { req, res in
    res.writeHead(200, [ "Content-Type": "text/html" ])
    try res.end("<h1>Hello World</h1>")
  }
}

And is configured like this in the Apache conf:

<LocationMatch /express/*>
  SetHandler de.zeezide.ApacheExpress
</LocationMatch>

Now you are saying, this is all nice and pretty. But what about Connect? I want to write and reuse middleware! Here you go:

func expressMain() {
  let app = apache.connect()

  app.use { req, res, next in
    console.info("Request is passing Connect middleware ...")
    res.setHeader("Content-Type", "text/html; charset=utf-8")
    // Note: we do not close the request, we continue with the next middleware
    try next()
  }
 
  app.use("/express/connect") { req, res, next in
    try res.write("<p>This is a random cow:</p><pre>")
    try res.write(vaca())
    try res.write("</pre>")
    res.end()
  }
}

And Express? Sure, the Apache Express is about to leave:

let app = apache.express(cookieParser(), session())

app.get("/express/cookies") { req, res, _ in
  // returns all cookies as JSON
  try res.json(req.cookies)
}

app.get("/express/") { req, res, _ in
  let tagline = arc4random_uniform(taglines.count)

  let values : [ String : Any ] = [
    "tagline"     : taglines[tagline],
    "viewCount"   : req.session["viewCount"] ?? 0,
    "cowOfTheDay" : cows.vaca()
  ]
  try res.render("index", values)
}

Yes. All that is running within Apache. The working example can be found here: ExpressMain.swift. The TodoMVC is pretty neat too.

This is wicked! How can I try it?

Easy! Just clone this repository, make and run it:

git clone https://github.com/AlwaysRightInstitute/mod_swift.git
cd mod_swift
make run

Then open http://localhost:8042/ and voilà, you should see a webpage delivered by Apache.

Well, you need to have macOS and Xcode 8, preferably 10.12 (though 10.11 also works w/ -X issues). Note: This would also work on Linux, but I didn’t bother to port it, let me know if there is some actual interest.

To explore and hack the code, just open the UseMe.xcworkspace. You can run everything directly from within Xcode.

Don’t be afraid, the Apache invoked here doesn’t interfere with your system Apache at all (but uses it, Apache is part of all macOS installs). It uses the apache.conf included in this repo, runs against the DocRoot included in this repo and loads the libraries from the Xcode build dir (hardcoded to SRCROOT/.libs).


So, what do you think? Like it? Hate it? Don’t care since you are still happy with WebObjects GETobjects? There is a Slack team and a Mailing list you can join for discussion.

Have fun! hh

Written on January 25, 2017