Super lightweight async HTTP server library in pure Swift runs in iOS / MacOS / Linux

Last update: Jun 8, 2022

Embassy

Build Status Carthage compatible SwiftPM compatible CocoaPods Swift Version Plaform GitHub license

Super lightweight async HTTP server in pure Swift.

Please read: Embedded web server for iOS UI testing.

See also: Our lightweight web framework Ambassador based on Embassy

Features

  • Swift 4 & 5
  • iOS / tvOS / MacOS / Linux
  • Super lightweight, only 1.5 K of lines
  • Zero third-party dependency
  • Async event loop based HTTP server, makes long-polling, delay and bandwidth throttling all possible
  • HTTP Application based on SWSGI, super flexible
  • IPV6 ready, also supports IPV4 (dual stack)
  • Automatic testing covered

Example

Here's a simple example shows how Embassy works.

let loop = try! SelectorEventLoop(selector: try! KqueueSelector())
let server = DefaultHTTPServer(eventLoop: loop, port: 8080) {
    (
        environ: [String: Any],
        startResponse: ((String, [(String, String)]) -> Void),
        sendBody: ((Data) -> Void)
    ) in
    // Start HTTP response
    startResponse("200 OK", [])
    let pathInfo = environ["PATH_INFO"]! as! String
    sendBody(Data("the path you're visiting is \(pathInfo.debugDescription)".utf8))
    // send EOF
    sendBody(Data())
}

// Start HTTP server to listen on the port
try! server.start()

// Run event loop
loop.runForever()

Then you can visit http://[::1]:8080/foo-bar in the browser and see

the path you're visiting is "/foo-bar"

By default, the server will be bound only to the localhost interface (::1) for security reasons. If you want to access your server over an external network, you'll need to change the bind interface to all addresses:

let server = DefaultHTTPServer(eventLoop: loop, interface: "::", port: 8080)

Async Event Loop

To use the async event loop, you can get it via key embassy.event_loop in environ dictionary and cast it to EventLoop. For example, you can create an SWSGI app which delays sendBody call like this

let app = { (
    environ: [String: Any],
    startResponse: ((String, [(String, String)]) -> Void),
    sendBody: @escaping ((Data) -> Void)
) in
    startResponse("200 OK", [])

    let loop = environ["embassy.event_loop"] as! EventLoop

    loop.call(withDelay: 1) {
        sendBody(Data("hello ".utf8))
    }
    loop.call(withDelay: 2) {
        sendBody(Data("baby ".utf8))
    }
    loop.call(withDelay: 3) {
        sendBody(Data("fin".utf8))
        sendBody(Data())
    }
}

Please notice that functions passed into SWSGI should be only called within the same thread for running the EventLoop, they are all not threadsafe, therefore, you should not use GCD for delaying any call. Instead, there are some methods from EventLoop you can use, and they are all threadsafe

call(callback: (Void) -> Void)

Call given callback as soon as possible in the event loop

call(withDelay: TimeInterval, callback: (Void) -> Void)

Schedule given callback to withDelay seconds then call it in the event loop.

call(atTime: Date, callback: (Void) -> Void)

Schedule given callback to be called at atTime in the event loop. If the given time is in the past or zero, this methods works exactly like call with only callback parameter.

What's SWSGI (Swift Web Server Gateway Interface)?

SWSGI is a hat tip to Python's WSGI (Web Server Gateway Interface). It's a gateway interface enables web applications to talk to HTTP clients without knowing HTTP server implementation details.

It's defined as

public typealias SWSGI = (
    [String: Any],
    @escaping ((String, [(String, String)]) -> Void),
    @escaping ((Data) -> Void)
) -> Void

environ

It's a dictionary contains all necessary information about the request. It basically follows WSGI standard, except wsgi.* keys will be swsgi.* instead. For example:

[
  "SERVER_NAME": "[::1]",
  "SERVER_PROTOCOL" : "HTTP/1.1",
  "SERVER_PORT" : "53479",
  "REQUEST_METHOD": "GET",
  "SCRIPT_NAME" : "",
  "PATH_INFO" : "/",
  "HTTP_HOST": "[::1]:8889",
  "HTTP_USER_AGENT" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
  "HTTP_ACCEPT_LANGUAGE" : "en-US,en;q=0.8,zh-TW;q=0.6,zh;q=0.4,zh-CN;q=0.2",
  "HTTP_CONNECTION" : "keep-alive",
  "HTTP_ACCEPT" : "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
  "HTTP_ACCEPT_ENCODING" : "gzip, deflate, sdch",
  "swsgi.version" : "0.1",
  "swsgi.input" : (Function),
  "swsgi.error" : "",
  "swsgi.multiprocess" : false,
  "swsgi.multithread" : false,
  "swsgi.url_scheme" : "http",
  "swsgi.run_once" : false
]

To read request from body, you can use swsgi.input, for example

let input = environ["swsgi.input"] as! SWSGIInput
input { data in
    // handle the body data here
}

An empty Data will be passed into the input data handler when EOF reached. Also please notice that, request body won't be read if swsgi.input is not set or set to nil. You can use swsgi.input as bandwidth control, set it to nil when you don't want to receive any data from client.

Some extra Embassy server specific keys are

  • embassy.connection - HTTPConnection object for the request
  • embassy.event_loop - EventLoop object
  • embassy.version - Version of embassy as a String, e.g. 3.0.0

startResponse

Function for starting to send HTTP response header to client, the first argument is status code with message, e.g. "200 OK". The second argument is headers, as a list of key value tuple.

To response HTTP header, you can do

startResponse("200 OK", [("Set-Cookie", "foo=bar")])

startResponse can only be called once per request, extra call will be simply ignored.

sendBody

Function for sending body data to client. You need to call startResponse first in order to call sendBody. If you don't call startResponse first, all calls to sendBody will be ignored. To send data, here you can do

sendBody(Data("hello".utf8))

To end the response data stream simply call sendBody with an empty Data.

sendBody(Data())

Install

CocoaPods

To install with CocoaPod, add Embassy to your Podfile:

pod 'Embassy', '~> 4.1'

Carthage

To install with Carthage, add Embassy to your Cartfile:

github "envoy/Embassy" ~> 4.1

Package Manager

Add it this Embassy repo in Package.swift, like this

import PackageDescription

let package = Package(
    name: "EmbassyExample",
    dependencies: [
        .package(url: "https://github.com/envoy/Embassy.git",
                 from: "4.1.1"),
    ]
)

You can read this example project here.

GitHub

https://github.com/envoy/Embassy
Comments
  • 1. Tests fail to compile when running under Swift 3.1.1

    I'm using the precompiled binaries, under Linux. I made a test function using the command env to make sure my libraries were representative of the real setups.

    The package passes all the tests under Swift 3.0.2, but actually fails to compile them under Swift 3.1.1.

    These are the errors I get:

    Compile Swift Module 'EmbassyTests' (10 sources)
    /home/felix/Documents/SKD/TestingDependencies/Embassy/Tests/EmbassyTests/HTTPServerTests.swift:120:39: error: 'Error?' is not convertible to 'NSError?'; did you mean to use 'as!' to force downcast?
                    receivedError = error as NSError?
                                    ~~~~~~^~~~~~~~~~~
                                          as!
    /home/felix/Documents/SKD/TestingDependencies/Embassy/Tests/EmbassyTests/HTTPServerTests.swift:161:39: error: 'Error?' is not convertible to 'NSError?'; did you mean to use 'as!' to force downcast?
                    receivedError = error as NSError?
                                    ~~~~~~^~~~~~~~~~~
                                          as!
    /home/felix/Documents/SKD/TestingDependencies/Embassy/Tests/EmbassyTests/HTTPServerTests.swift:209:39: error: 'Error?' is not convertible to 'NSError?'; did you mean to use 'as!' to force downcast?
                    receivedError = error as NSError?
                                    ~~~~~~^~~~~~~~~~~
                                          as!
    
    Reviewed by felix91gr at 2017-05-20 15:53
  • 2. [Bugfix] Crashes when reading the body in Release configurations

    Fixes #96

    Explanation

    When the _value is set to a new value, the closure's parameter value is freed from memory. When this freed value was returned to the SelectorEventLoop, the app crashed.

    To avoid this, I suggest to keep a strong reference to the value object, that way it won't be deleted.

    Reviewed by ArnaudWurmel at 2021-04-23 12:46
  • 3. Swift 4.1 compiler warning fix

    On swift 4.1 there's a compiler warning:

    Overlapping accesses to 'address', but modification requires exclusive access; consider copying to a local variable
    

    This fixes the compiler warning.

    Reviewed by ryanrhee at 2018-04-14 02:06
  • 4. Embassy doesn't build with Xcode 9 Beta + Swift 3.2

    If I try to build Embassy with the latest Xcode 9 Beta 4, I get the following error message:

    KqueueSelector.swift:75:34: Cannot convert value of type 'Int32' to expected argument type 'UInt'

    It looks like the Darwin.kevent() method changed.

    Reviewed by patricks at 2017-07-31 05:43
  • 5. SPM: package 'Embassy' contains no targets

    This project cannot be used as a dependency with the current version of Swift Package Manager.

    I believe this has been addressed partially in #74 but the latest release 4.1.0 does not include the fix. Consequently, SPM fails when this package is specified as a dependency.

    As suggested in #80 bumping the release number of this project (adding a tag) will most likely solve this issue.

    Steps to reproduce:

    $ swift --version
    Apple Swift version 5.1.2 (swiftlang-1100.0.278 clang-1100.0.33.9)
    Target: x86_64-apple-darwin19.0.0
    $ swift package init
    Creating library package: test
    Creating Package.swift
    Creating README.md
    Creating .gitignore
    Creating Sources/
    Creating Sources/test/test.swift
    Creating Tests/
    Creating Tests/LinuxMain.swift
    Creating Tests/testTests/
    Creating Tests/testTests/testTests.swift
    Creating Tests/testTests/XCTestManifests.swift
    $ 
    $ vim Package.swift   # Embassy was added manually as a dependency in Package.swift
    $ 
    $ cat Package.swift
    // swift-tools-version:5.1
    // The swift-tools-version declares the minimum version of Swift required to build this package.
    
    import PackageDescription
    
    let package = Package(
        name: "test",
        products: [
            // Products define the executables and libraries produced by a package, and make them visible to other packages.
            .library(
                name: "test",
                targets: ["test"]),
        ],
        dependencies: [
            // Dependencies declare other packages that this package depends on.
            // .package(url: /* package url */, from: "1.0.0"),
            .package(url: "https://github.com/envoy/Embassy.git", from: "4.1.0"),
        ],
        targets: [
            // Targets are the basic building blocks of a package. A target can define a module or a test suite.
            // Targets can depend on other targets in this package, and on products in packages which this package depends on.
            .target(
                name: "test",
                dependencies: ["Embassy"]),
            .testTarget(
                name: "testTests",
                dependencies: ["test"]),
        ]
    )
    $ swift build
    'Embassy' /Users/tlk/test/.build/checkouts/Embassy: error: package 'Embassy' contains no targets
    warning: dependency 'Embassy' is not used by any target
    $
    
    Reviewed by tlk at 2019-12-08 22:40
  • 6. Fix warning in Swift 4.1.50

    Since implicitly unwrapped optionals got a bit of a makeover, this IUO caused a warning with Swift 4.1.50 (Xcode 10). This change is backwards compatible with Swift 4.1.2 (Xcode 9.4)

    Reviewed by keith at 2018-06-20 08:05
  • 7. [Bugfix] Fix SIGABRT in KqueueSelector

    Fixes issue #78 , which persisted even with #83 fix. Our team tried this fix over the #83 , so you may want to merge both of them. We had a SIGABRT and an EXC_BAD_ACCESS before we made this fix. And after restarting simulater and this fix, the tests run smoothly (we use Embassy for UI tests).

    Explanation

    The array count was incorrectly used. The documentation says:

    • Warning: Do not rely on anything about the array that is the target of this method during execution of the body closure; it might not appear to have its correct value.

    Meaning we should not used kevents array in the closure. But then it continues:

    Instead, use only the UnsafeMutableBufferPointer argument to body.

    So we should use the pointer provided to body, not the count calculated before the function is called.

    Reviewed by danielaRiesgo at 2020-05-12 17:10
  • 8. Adding tvOS support for Embassy.

    • Created a new target for tvOS.
    • Added a new scheme for tvOS support.
    • Added tvOS support for both Carthage and Cocoapods.
    • Update the Podspec to support tvOS.
    Reviewed by thisisjak at 2018-09-12 03:27
  • 9. Leaking memory problem

    We capture images from the iPad camera and want to send these data back to the client. The image >5MB large. In Xcode and also Instruments we see that the memory used by the app is increasing by roughly 5MB whenever we trigger a request on the web server. Instruments shows us that there are some memory leaks in the http request. Maybe the objects are not released. The code looks similar to your demo template. Only the body data is larger and instead of an image we use a simple Data object with the same size like an camera image.

    let loop = try! SelectorEventLoop(selector: try! SelectSelector())
    let server = DefaultHTTPServer(eventLoop: loop, interface: "127.0.0.1", port: self.port) {
     (
       environ: [String: Any],
       startResponse: ((String, [(String, String)]) -> Void),
       sendBody: ((Data) -> Void)
     ) in
     startResponse("200 OK", [])
     sendBody(Data(String(repeating: "A", count: 5427471).utf8))
     sendBody(Data())   // send EOF
    }
    try! server.start() 
    loop.runForever()
    
    Reviewed by ghost at 2018-05-16 17:08
  • 10. Can't access HTTP server from another device within network

    Hi, I am trying to figure out how to send requests to the server from other devices within the network. When I embed the example in my iOS App, open up Safari and type in my iPhone's IP, I get a correct response from the server. But how can I access the HTTP server from another device? Do I have to get any permissions to access the device remotely? I also tried a similar functionality with IBM's Kitura, where this functionality is working. But as I prefer this framework, I would like to find a solution with Embassy.

    Reviewed by DennisHirschgaenger at 2017-05-10 14:10
  • 11. Static resources?

    Hey guys, love this project, been playing with it for a couple of days from the simplest scenario adding little things like templates (Stencil) and databases (Postgres) and it's looking damn good.

    This is what I got so far trying to simplify the entry point of an Ambassador app tweaking a little bit of code in a Server.swift file:

    // MAIN
    
    let router = Router()
    
    //router["/"]     = App.index()  // here
    router["/index"]  = App.index()
    router["/users"]  = App.users()
    router["/delay"]  = App.delay()
    router["/test"]   = App.test()
    router["/static"] = App.staticResource() // here
    router["/404"]    = App.notFound()
    router.notFound   = App.notFound()
    
    let server = Server()
    server.run(router)
    

    Ok, two things:

    1. How to serve static resources? I couldn't find any info in the very scarce docs. Is there any manual/doc anywhere else?

    2. As you can see, the root is commented since uncommenting it would cause all paths to be redirected to the root, perhaps due to the regex evaluation? So, how do you route the root like localhost:8080/ as "/"?

    Thanks for all the cool stuff, this project rocks!

    Reviewed by kuyawa at 2017-05-07 15:50
  • 12. SystemLibrary: Fatal Error: Not enough bits to represent the passed value

    I don't have enough Swift and iOS/macOS knowledge to even properly formulate what's the issue, except for it randomly, often enough, crashes my app on macOS (both 13.+ & 14.+). Any hints are appreciated!

    SystemLibrary
    Reviewed by shliama at 2020-10-15 19:09
  • 13. Crash is happening while unwrapping optional value in HTTPHeaderParser while using Embassy through Swift Package Manager

    Crash is happening while unwrapping optional value in HTTPHeaderParser

    We are using the Swift Package ( and this happened only after we started using the swift Package) Happened in this:

    @objc private func runEventLoop() {
            eventLoop.runForever()
            eventLoopThreadCondition.lock()
            eventLoopThreadCondition.signal()
            eventLoopThreadCondition.unlock()
        }
    

    Date/Time: 2020-04-26 04:00:00.058 +0200 OS Version: Mac OS X 10.15.4 (19E287) Application Specific Information: CoreSimulator 704.12.1 - Device: iPhone 8 (EA3A1275-294C-4D4C-B9DB-D0568281514A) - Runtime: iOS 13.3 (17C45) - DeviceType: iPhone 8

    Fatal error: Unexpectedly found nil while unwrapping an Optional value: file /SourcePackages/checkouts/Embassy/Sources/HTTPHeaderParser.swift, line 62

    Reviewed by AyaAkl25 at 2020-04-27 10:10
  • 14. Is it possible to host a small reactjs website inside iOS with this library?

    Hi, I'm looking for options to host a small reactjs site inside an iOS app, I managed to start the small server and consume an internal file, but a small site will have a lot of files, I'm trying to load each file one by one, but I'm not sure that will work or if there's an easy way to do this or a smarter way.

    Please advise.

    Reviewed by diegoramoncito at 2020-03-20 17:48
  • 15. can't restart the DefaultHTTPServer

    issue: step 1: start the server step 2: stop the server step 3: start the same server. it will fail.

    it seems like the stop func does not set acceptScoket to nil so it will failed to start again.

    public func start() throws { guard acceptSocket == nil else { logger.error("Server already started") return }

    Reviewed by robertpub at 2019-10-09 03:37
Super lightweight web framework in Swift based on SWSGI

Ambassador Super lightweight web framework in Swift based on SWSGI Features Super lightweight Easy to use, designed for UI automatic testing API mocki

Mar 18, 2022
Swift HTTP server using the pre-fork worker model

Curassow Curassow is a Swift Nest HTTP Server. It uses the pre-fork worker model and it's similar to Python's Gunicorn and Ruby's Unicorn. It exposes

Mar 20, 2022
A Swift web framework and HTTP server.
A Swift web framework and HTTP server.

A Swift Web Framework and HTTP Server Summary Kitura is a web framework and web server that is created for web services written in Swift. For more inf

Jun 24, 2022
Tiny http server engine written in Swift programming language.

What is Swifter? Tiny http server engine written in Swift programming language. Branches * stable - lands on CocoaPods and others. Supports the latest

Jun 21, 2022
Server-side Swift. The Perfect core toolset and framework for Swift Developers. (For mobile back-end development, website and API development, and more…)
Server-side Swift. The Perfect core toolset and framework for Swift Developers. (For mobile back-end development, website and API development, and more…)

Perfect: Server-Side Swift 简体中文 Perfect: Server-Side Swift Perfect is a complete and powerful toolbox, framework, and application server for Linux, iO

Jun 22, 2022
💧 A server-side Swift web framework.
💧 A server-side Swift web framework.

Vapor is a web framework for Swift. It provides a beautifully expressive and easy to use foundation for your next website, API, or cloud project. Take

Jun 23, 2022
Meet Corvus, the first strongly declarative server-side framework.
Meet Corvus, the first strongly declarative server-side framework.

Corvus Corvus is the first truly declarative server-side framework for Swift. It provides a declarative, composable syntax which makes it easy to get

Jan 29, 2022
A Swift Multiplatform Single-threaded Non-blocking Web and Networking Framework
A Swift Multiplatform Single-threaded Non-blocking Web and Networking Framework

Serverside non-blocking IO in Swift Ask questions in our Slack channel! Lightning (formerly Edge) Node Lightning is an HTTP Server and TCP Client/Serv

Jan 29, 2022
Evented I/O streams for Swift
Evented I/O streams for Swift

Noze.io "Das Haus das Verrückte macht." Noze.io is an attempt to carry over the Node.js ideas into pure Swift. It uses libdispatch for event-driven, n

Apr 18, 2022
Super lightweight async HTTP server library in pure Swift runs in iOS / MacOS / Linux

Embassy Super lightweight async HTTP server in pure Swift. Please read: Embedded web server for iOS UI testing. See also: Our lightweight web framewor

Jun 8, 2022
Super lightweight async HTTP server library in pure Swift runs in iOS / MacOS / Linux

Embassy Super lightweight async HTTP server in pure Swift. Please read: Embedded web server for iOS UI testing. See also: Our lightweight web framewor

Jun 8, 2022
A replacement for as which runs in constant time instead of O(n) when the conformance is not satisfiedA replacement for as which runs in constant time instead of O(n) when the conformance is not satisfied

ZConform A replacement for as? which runs in constant time instead of O(n) when the conformance is not satisfied. How it works ZConform does a one-tim

Jun 14, 2022
Lightweight library for web server applications in Swift on macOS and Linux powered by coroutines.
Lightweight library for web server applications in Swift on macOS and Linux powered by coroutines.

Why Zewo? • Support • Community • Contributing Zewo Zewo is a lightweight library for web applications in Swift. What sets Zewo apart? Zewo is not a w

May 30, 2022
Lightweight library for web server applications in Swift on macOS and Linux powered by coroutines.
Lightweight library for web server applications in Swift on macOS and Linux powered by coroutines.

Why Zewo? • Support • Community • Contributing Zewo Zewo is a lightweight library for web applications in Swift. What sets Zewo apart? Zewo is not a w

Jun 23, 2022
Lightweight library for web server applications in Swift on macOS and Linux powered by coroutines.
Lightweight library for web server applications in Swift on macOS and Linux powered by coroutines.

Why Zewo? • Support • Community • Contributing Zewo Zewo is a lightweight library for web applications in Swift. What sets Zewo apart? Zewo is not a w

Jun 23, 2022
A Ruby on Rails inspired Web Framework for Swift that runs on Linux and OS X

IMPORTANT! We don't see any way how to make web development as great as Ruby on Rails or Django with a very static nature of current Swift. We hope th

Jun 12, 2022
A Ruby on Rails inspired Web Framework for Swift that runs on Linux and OS X

IMPORTANT! We don't see any way how to make web development as great as Ruby on Rails or Django with a very static nature of current Swift. We hope th

Jun 22, 2022
Swift backend / server framework (Pure Swift, Supports Linux)

NetworkObjects NetworkObjects is a #PureSwift backend. This framework compiles for OS X, iOS and Linux and serves as the foundation for building power

May 30, 2022
Swift backend / server framework (Pure Swift, Supports Linux)

NetworkObjects NetworkObjects is a #PureSwift backend. This framework compiles for OS X, iOS and Linux and serves as the foundation for building power

May 30, 2022
GCDWebServer is a modern and lightweight GCD based HTTP 1.1 server designed to be embedded in iOS, macOS & tvOS apps.

GCDWebServer is a modern and lightweight GCD based HTTP 1.1 server designed to be embedded in iOS, macOS & tvOS apps. It was written from scr

Jun 20, 2022