Disclaimer

What you are going to read is absolutely experimental and might not represent the best way to tackle a certain problem, so, in case you have a better idea/comment/critique, share it keeping in mind what I just said. :)

Disclaimer 2

If you are thinking “Why do that?” (using Swift for server side), I am not going to answer to this question now… :)

Introduction

I have worked and keep working on server side code when possible, in the past I worked with Java (sigh), more precisely with frameworks like Spring, Hibernate, Jena, etc… Ruby on Rails and Scala, mostly with Play. I also wrote some toy projects in Node.js and Golang, just for fun and spent some time understanding the code behind Hacker News, written in ARC, a special dialect of Lisp.

This was necessary to understand some of the things I am going to talk about and contextualize some of the analogies I am going to use.

Act 1 - Writing a custom web framework

I started working with Swift on server side during Christmas last year, I created a small framework called Wing focusing on modularity and trying to take advantage of its protocol-oriented design. In a first instance, I started working with file descriptors and sockets, in a rudimentary and naive way, with absolutely no luck in the final result. The implementation was really unstable and was generating a lot of bad instructions on Mac and Linux, crashing badly on runtime.

I always been a kind of fan of libuv, so I decided to give it a try and wrap it. The result was extremely good, I was able to asynchronously reply to requests and I have to say I was very happy with the result (that part has been later removed because GCD was not working on Linux). I was also very happy to check the HTTP stack and learn it in the hard way, coding a parser and playing with it. Considering I have no time to maintain such a big library and that I don’t like to publicize something I can’t maintain, I kept it private to later share it as “reference”.

I really recommend this experience, it doesn’t take more than 2-3 days to write a basic web framework working in a decent way and the quantity of things learned are worthy.

Act 2 - Writing a production application

A language is production ready once it’s on production and to do that it’s required to write something to actually put in production and prove that the technology can be used and is good enough. Pioneering is something I kind of love, so I decided to create a real Hacker News clone based on Antirez’s Lamernews, a Ruby implementation based on Redis. Considering how powerful and easy is Redis, I decided to adopt it in my version too.

Picking the right framework

This has been a source of major problem, I dropped the development of Wing, because I did not want to be responsible for a web framework, so I decided to look around and search for a valid alternative to use.

I started checking out Perfect, that seems to be very popular, but some of the things haven’t really convinced me, so after a quick try I decided to look at something skinnier and lightweight where I can eventually help in developing new features and get my hands dirty.

I later stepped into Zewo that is more a collection of libraries to create server side code, rather than a real batteries included framework. I gave it a shot and I was happily impressed of the quality of the code. But on that time there’re a lot of problems with the snapshots and I ended up spending hours and hours fighting against the compiler because some dependencies were still for an old snapshot, so I moved forward trying something else.

At this point, I was looking to an Express/Spray.io inspired framework and I found a some of them:

Kitura is definitely impressive, but after some hours of usage and some annoying crashes on runtime due to bad instructions I decided to drop it, even considering this is the most promising one, by far, taking in consideration that IBM is a big player and that is investing a lot of resources in Swift, I will keep an eye on it.

Swift Express seems very promising and after a first try I liked it a lot, but I wasn’t in my usual lazy-state and for this reason I decided to move on and try the other ones, later discovering it wasn’t anymore building with the end of February snapshot.

SwiftOn demonstrated to be very interesting as alternative, but the lack of cookies was a major issue on my goal, considering they are used to keep the user’s session alive. So I had to drop it quite immediately.

At this point, Vapor was the last one remaining and the first feeling was of happiness. After a couple of hours a I was able to show and submit articles and links using REST calls. Conclusion: this is the framework I am going to use… but I still need a Redis library.

What about Redis?

So, a Redis client. If you don’t know what Redis, here’s the description:

Redis is an open source (BSD licensed), in-memory data structure store, used as database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs and geospatial indexes with radius queries. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.

There are wrappers and mappings for Hiredis, the official C client, around but I was looking to something more swifty, so I quickly searched and, excluding the one provided with Kitura due to the unfair number of dependencies that are included, I stepped into Redbird that was the only real choice here.

Writing the code

Time to write Swift!

Porting from another language is not a trivial task, it might seem easy in a first sight, considering in most cases we can skip to think too much about the logic, but the truth is that, sometimes a language adaptation is necessary for the following reason:

  • different type system
  • missing library
  • the original code can be improved

Lamernews is a simple, but extremely qualitative piece of software, so the option number 3 is an unexpected case. Number 1 and 2 are, instead, everywhere. Ruby is a massive ecosystem, like Express (Play is big, but not big enough to complete against the first 2) and the amount of libraries available is just ridiculously huge.

Translating from a dynamic language like Ruby is a really challenging task, because things that can be done with one line of code, in Swift we have to be safe and a guard or an if becomes a necessary extra check.

For example, Redis is an extremely basic database in terms of commands and returned data, so parsing it and return anything that is not a String can be quite annoying, for this reason I decided to use only strings and avoid any validation in a first version, wrapping Redbird like this:

func get(key: String) throws -> String {
    return try command("GET", params: [key]).toString()
}

Feeling ashamed of myself, I later convinced myself this was for a good reason (time) and continued my journey in writing unsafe and bad Swift code, without any regret. Considering I love Scala and how its type system works, I have to translate the good habit of Scala while writing Swift code for server side, but on the next refactored version.

User Registration

User’s management is a very simple task to code and I decided to avoid using a single file for the whole application, like lamernews does, and split it into Controllers, which seems a very nice abstraction provided by Vapor. The code at this point is well organized and every single function has its own responsability like:

private func getUserById(id: String) throws -> [String:String] {
    return try redis.hgetall("user:\(id)")
}

private func getUserByUsername(username: String) throws -> [String:String]? {
    guard try redis.exists("username.to.id:\(username.lowercaseString)") else {
        return nil
    }

    let id = try redis.get("username.to.id:\(username.lowercaseString)")
    return try getUserById(id)
}

private func checkUserCredentials(username: String, password: String) throws -> (String, String)? {
    guard let user = try getUserByUsername(username) else {
        return nil
    }
    let hashed = hashPassword(password, salt: user["salt"]!)
    if user["password"]! == hashed {
        return (user["auth"]!, user["apisecret"]!)
    }
    return nil
}

Article Submission

Article submission is similar to user’s registration and management, few functions are enough to construct all the necessary actions, like vote a news, submit a new one or edit an existing one. Here some utils methods were necessary so I created functions like:

private func hostnameFromURL(url: String) -> String? {
    guard url.hasPrefix("https://") || url.hasPrefix("http://") else {
        return nil
    }
    
    let http = try! Regex(pattern: "http://")
    let https = try! Regex(pattern: "https://")
    let www = try! Regex(pattern: "www.")
    var striped = http.replace(url, withTemplate: "")
    striped = https.replace(striped, withTemplate: "")
    striped = www.replace(striped, withTemplate: "")

    return striped.characters.split(separator: "/", maxSplits:1024, omittingEmptySubsequences: true).map { String($0) }[0]
}

private func humanReadablePostTime(time: Int) -> String {
    let newsDate = NSDate(timeIntervalSince1970: (Double(time) ?? 0) ?? 0.0)
    let delta = abs(Int(newsDate.timeIntervalSinceNow))

    if delta < 3 {
        return "just now"
    } else if delta < 60 {
        return "\(delta) seconds ago"
    } else if delta < 120 {
        return "1 minute ago"
    } else if delta < 3600 {
        return "\(delta / 60) minutes ago"
    } else if delta < 7200 {
        return "1 hour ago"
    } else if delta < 86400 {
        return "\(delta / 3600) hours ago"
    } else if delta < 172800 {
        return "1 day ago"
    }

    return "\((delta / 86400) + 1) days ago"
}

Note: I am aware that the previous code can be written in a better way, but it’s not the purpose of this project, yet.

Comments

Commenting is the core part of an Hacker News clone, discussions is were all the fur parts are… :) I am still working on this and I will later do a dedicated article explaining how comments can be rendered, considering that an article can have N comments, with M subcomments and infinite nesting. It might take some time to tackle this.

Where’s the code?

At this point, I think you want to see the result which is on Github and it is called SwiftyNews, feel free to help opening issues and doing PRs.

Act 3 - The Future and Conclusion

I am quite happy about Swift on server side and it seems a very appealing and powerful solution with a big potential for the next years. The type system is amazing and the fact that the result is a compiled and linked binary, it makes Swift a great candidate for super fast micro-services, once stabilized and frameworks are finally mature.

Do you want to create your own framework? Please go ahead and just do it! It might sound a stupid thing, considering some are already there, but the ones I found are in the early stages, doing mistakes and changing a lot on every release, even breaking completely the app in development, but in this way we understand what is good and what is bad in server side code written in Swift.

I created my own web framework for educational purposes and I am going to try to help some of these projects in improving their code based on my personal experience and my mistakes, also taking advantage of my experience with Java, Scala and other programming languages used for server side code.