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

Overview

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.

Comments
  • Fatal error with Xcode 14 and iOS 16

    Fatal error with Xcode 14 and iOS 16

    Hello, When running my project with Xcode 14 RC and iOS 16 RC(on an iPhone X) I get a fatal error:

    Swift/arm64-apple-ios.swiftinterface:23320: Fatal error
    In file TCPSocket line 117
    

    Could anyone help? Thanks

    opened by horpot 15
  • Tests fail to compile when running under Swift 3.1.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!
    
    opened by felix91gr 10
  • Fix crashes related to breaking Swift language changes to withMemoryRebound in Xcode 14

    Fix crashes related to breaking Swift language changes to withMemoryRebound in Xcode 14

    This PR fixes crashes presumably related to S3-0333 which adjusted the behavior of withMemoryRebound. These crashes prevent us from using this framework in Xcode 14 beta likely due to SE-0333 being accepted and included in its release.

    Explanation

    According to the docs of withMemoryRebound the parameter count refers to "the number of instances of Pointee to bind to type." This means that it does not actually refer to the size of the rebound region, but instead to the number of instances of T being mapped within it. The previous implementation passed the size of the MemoryLayout rather than simply passing 1, which is the number of socketaddr instances we're attempting to map/bind to.

    The proposal linked previously asserted that these changes would not break source compatibility, but I have found this assumption to be flawed. Even though TCPSocket's previous implementation appeared to misunderstand the intent of the capacity parameter, it functioned nonetheless. I am willing to file an issue to the Swift team if there's anyone that can help me distill this issue down.

    My fix has seemed to resolved the crashes on our end, but I would gladly appreciate others to verify my understanding. I am not too well-accustomed to C land, so it is entirely possible my understanding is flawed as well.

    In any case, if these changes can be verified and merged as soon as possible, we would be much appreciative, as we would love to continue using this library in Xcode 14 onward.

    Thank you!

    opened by JRR-OSU 9
  • [Bugfix] Crashes when reading the body in Release configurations

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

    opened by ArnaudWurmel 7
  • Swift 4.1 compiler warning fix

    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.

    opened by ryanrhee 6
  • Embassy doesn't build with Xcode 9 Beta +  Swift 3.2

    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.

    opened by patricks 6
  • SPM: package 'Embassy' contains no targets

    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
    $
    
    opened by tlk 4
  • Fix warning in Swift 4.1.50

    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)

    opened by keith 4
  • [Bugfix] Fix SIGABRT in KqueueSelector

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

    opened by danielaRiesgo 3
  • Adding tvOS support for Embassy.

    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.
    opened by thisisjak 3
  • Leaking memory problem

    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()
    
    opened by ghost 2
  • Bad File Descriptor crash SelectorEventLoop Xcode 14

    Bad File Descriptor crash SelectorEventLoop Xcode 14

    Hi, v4.1.4 resolved the memoryRebound fatal error on Xcode 14 / iOS 16 that we were seeing but there is also another issue in SelectorEventLoop.

    Embassy/SelectorEventLoop.swift:88: Fatal error: 'try!' expression unexpectedly raised an error: Embassy.OSError.ioError(number: 9, message: "Bad file descriptor")
    2022-10-28 01:08:39.731236+1100 XYZUITests-Runner[81238:5120563] Embassy/SelectorEventLoop.swift:88: Fatal error: 'try!' expression unexpectedly raised an error: Embassy.OSError.ioError(number: 9, message: "Bad file descriptor")
    
    Screen Shot 2022-10-29 at 1 22 40 am

    This looks like it was also raised in https://github.com/envoy/Ambassador/issues/67 but I thought I'd add it here for more visibility.

    We've tried cleaning up the event loop vars between tests, sharing a single instance of the server, and various other tweaks but no luck. Our use of Embassy is an almost identical setup to described here: https://envoy.engineering/embedded-web-server-for-ios-ui-testing-8ff3cef513df#.c2i5tx380

    Any help or ideas would be appreciated.

    Xcode 4.0.1 Embassy 4.1.4 macOS 12.6 Simulator iOS 16.0

    opened by danielbowden 2
  • Port to Android

    Port to Android

    Also gets the tests running on linux again.

    I used an earlier version of this pull when packaging CookCLI for Android last weekend, termux/termux-packages#11081. I tested this pull by running swift test on Ubuntu 20.04 x86_64 and Android 12 AArch64 without a problem.

    opened by buttaface 4
  • SystemLibrary: Fatal Error: Not enough bits to represent the passed value

    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
    opened by shliama 0
  • 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 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

    opened by AyaAkl25 0
  • Is it possible to host a small reactjs website inside iOS with this library?

    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.

    opened by diegoramoncito 0
Releases(v4.1.4)
Owner
Envoy
Transforming modern workplaces and challenging the status quo with innovations that make office life and work more meaningful
Envoy
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

Kyle Fuller Archive 397 Oct 30, 2022
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

Kitura 7.6k Dec 27, 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

null 3.6k Jan 3, 2023
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

Envoy 170 Nov 15, 2022
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

PerfectlySoft Inc. 13.9k Dec 29, 2022
💧 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

Vapor 22.4k Jan 7, 2023
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

null 42 Jun 29, 2022
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

SkyLab 316 Oct 6, 2022
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

The Noze Consortium 305 Oct 14, 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

Envoy 540 Dec 15, 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

Envoy 540 Dec 15, 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

Emerge Tools 20 Aug 4, 2022
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

Zewo 1.9k Dec 22, 2022
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

Zewo 1.9k Dec 22, 2022
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

Zewo 1.9k Dec 22, 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

Saulius Grigaitis 2k Dec 5, 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

Saulius Grigaitis 2k Dec 5, 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

Alsey Coleman Miller 258 Oct 6, 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

Alsey Coleman Miller 258 Oct 6, 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

Pierre-Olivier Latour 6.3k Dec 27, 2022