I forked this repo to adjust the library to my needs. I refactored a little, removed linux incompatible classes and added some features I found useful (like cookie handling & setting).
Tiny http server engine written in Swift programming language.
* 2.0.0 - latest release
let server = HttpServer()
server["hello"] = { request, responseHeaders in
.ok(.html("html goes here"))
}
server["code.js"] = { request, responseHeaders in
.ok(.js("javascript goes here"))
}
server["api"] = { request, responseHeaders in
.ok(.json(<Encodeble object>))
}
try server.start(8080)import Dispatch
dispatchMain()let server = HttpServer()
server["/desktop/:path"] = shareFilesFromDirectory("/Users/me/Desktop")
server.start()let server = HttpServer()
server["/redirect"] = { request, _ in
return .movedPermanently("http://www.google.com")
}
server.start()let server = HttpServer()
server["/my_html"] = scopes {
html {
body {
h1 { inner = "hello" }
}
}
}
server.start()let server = HttpServer()
server["/websocket-echo"] = websocket(text: { session, text in
session.writeText(text)
}, binary: { session, binary in
session.writeBinary(binary)
})
try server.start()HttpRequest has onFinished closure that will be executed after request is finished
server.middleware.append( { request, _ in
// init tracking with request.id
request.onFinished = { id, responseCode in
// finish tracking;
// id is unique UUID for this request
// responseCode is the http code that was returned to client
}
return nil
})enum RestApi: String, WebPath {
case home = ""
case contact
}
server.get[RestApi.home] = { _, _ in
.ok(.text("welcome!"))
}
server.get[RestApi.contact] = { _, _ in
.ok(.text("contact page"))
}You can build routing with groups so they have common path prefixes:
// create a new routing groups with prefix `users`
let users = server.grouped("users")
// this route will be at URL: `/users`
users.get.handler = { request, _ in
.ok(.html("user list"))
}
users.post.handler = { request, _ in
.ok(.html("added user list"))
}
// this route will be at URL: `/users/avatars`
users.get["avatars"] = { request, _ in
.ok(.html("user avatars"))
}
// this route will be for specific user, e.g. `/users/876`
users.get[":id"] = { request, _ in
.ok(.html("user with id \(request.pathParams.get("id"))"))
}You can also use this approach:
server.group("libraries") { libraries in
// route from GET /libraries
libraries.get.handler = { request, _ in
.ok(.html("Welcome to libraries"))
}
libraries.post.handler = { request, _ in
.ok(.html("Added new library"))
}
libraries.group("europe") { europe in
// route from GET /libraries/europe/poland
europe.get["poland"] = { request, _ in
.ok(.html("Library in Poland"))
}
// route from e.g GET /libraries/europe/cities/warsaw
europe.get["cities/:city"] = { request, _ in
.ok(.html("Library in \(request.pathParams.get("city"))"))
}
}
}In both approaches you can nest routes as deep as you like.
server.post["uploadForm"] = { request, _ in
struct User: Codable {
let name: String
let pin: Int
}
guard let user: User = try? request.formData.decode() else {
return .badRequest(.text("Missing fields!"))
}
return .ok(.text("Uploaded for \(user.name)"))
}GET /search?start=10limit=50&query=SELECT
server.get["search"] = { request, _ in
struct Search: Codable {
let limit: Int
let start: Int
let query: String
}
guard let search: Search = try? request.queryParams.decode() else {
return .badRequest(.text("Missing query params!"))
}
return .ok(.text("Search results for \(search.query)"))
}server.post["create"] = { request, _ in
struct Car: Codable {
let weight: Int?
let length: Int?
let make: String
}
guard let car: Car = try? request.body.decode() else {
return .badRequest(.text("Invalid body"))
}
return .ok(.text("Car created \(car.make)"))
}Header field names are capitalized with first letter lowercased. That means that Content-Type becomes conentType:
server.get["resource"] = { request, _ in
struct Header: Codable {
let host: String
let authorization: String
let contentType: String
}
guard let headers: Header = try? request.headers.decode() else {
return .badRequest(.text("Missing header fields!"))
}
return .ok(.text("Showing web page for \(headers.host)"))
}Header field names are capitalized with first letter lowercased. That means that Content-Type becomes conentType:
// /GET book/98/poker-face
server.get["book/:id/:title"] = { request, _ in
struct Book: Codable {
let id: Int
let title: String
}
guard let book: Book = try? request.pathParams.decode() else {
return .badRequest(.text("Invalid url"))
}
return .ok(.text("Title: \(book.title)"))
}You can implement some validation or authorization classes using throw HttpInstantResponse
server.post["restricted/user/changepassword"] = { request, _ in
guard AccessValidator.canProcess(request) else {
throw HttpInstantResponse(response: .unauthorized())
}
return .ok(.text("Password changed"))
}var server = HttpServer()
server.name = "Apache"You can even set global headers that are send with every response until specific RequestHandler overrides it.
server.globalHeaders.addHeader("X-Docker-Instance", UUID().uuidString)use_frameworks!
pod 'Swifter', '~> 1.5.0'github "tomieq/swifter" ~> 1.5.0
import PackageDescription
let package = Package(
name: "MyServer",
dependencies: [
.package(url: "https://github.com/tomieq/swifter.git", .upToNextMajor(from: "1.5.0"))
]
)in the target:
targets: [
.executableTarget(
name: "AppName",
dependencies: [
.product(name: "Swifter", package: "Swifter")
])
]