Skip to content

tomieq/swifterfork

 
 

Repository files navigation

Platform Swift Protocols CocoaPods Carthage Compatible

Why this fork?

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).

What is Swifter?

Tiny http server engine written in Swift programming language.

Branches

* 2.0.0 - latest release

How to start?

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)

How to keep the process running on Linux?

import Dispatch
dispatchMain()

How to share files?

let server = HttpServer()
server["/desktop/:path"] = shareFilesFromDirectory("/Users/me/Desktop")
server.start()

How to redirect?

let server = HttpServer()
server["/redirect"] = { request, _ in
  return .movedPermanently("http://www.google.com")
}
server.start()

How to HTML ?

let server = HttpServer()
server["/my_html"] = scopes { 
  html {
    body {
      h1 { inner = "hello" }
    }
  }
}
server.start()

How to WebSockets ?

let server = HttpServer()
server["/websocket-echo"] = websocket(text: { session, text in
  session.writeText(text)
}, binary: { session, binary in
  session.writeBinary(binary)
})
try server.start()

How to add metric tracking

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
})

How to add routing with enum

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"))
}

How to compose routing

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.

How to make object from uploaded form data

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)"))
}

How to make object from query params

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)"))
}

How to make object from body

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)"))
}

How to make object from headers

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)"))
}

How to make object from path params

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)"))
}

How to return response instantly

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"))
}

How to send custom Server header for every response

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)

CocoaPods? Yes.

use_frameworks!

pod 'Swifter', '~> 1.5.0'

Carthage? Also yes.

github "tomieq/swifter" ~> 1.5.0

Swift Package Manager.

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")
            ])
    ]

About

Tiny http server engine written in Swift programming language.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Swift 98.5%
  • Ruby 1.5%