Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

https://github.com/envoy/Embassy

Super lightweight async HTTP server library in pure Swift runs in iOS / MacOS / Linux
https://github.com/envoy/Embassy

asynchronous ios lightweight linux swift webserver

Last synced: about 1 month ago
JSON representation

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

Lists

README

        

# Embassy

[![Build Status](https://travis-ci.org/envoy/Embassy.svg?branch=master)](https://travis-ci.org/envoy/Embassy)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-brightgreen.svg)](https://github.com/Carthage/Carthage)
[![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager)
[![CocoaPods](https://img.shields.io/cocoapods/v/Embassy.svg)]()
![Swift Version](https://img.shields.io/badge/Swift-5.0-orange.svg)
![Plaform](https://img.shields.io/badge/Platform-macOS|iOS|Linux-lightgrey.svg)
[![GitHub license](https://img.shields.io/github/license/envoy/Embassy.svg)](https://github.com/envoy/Embassy/blob/master/LICENSE)

Super lightweight async HTTP server in pure Swift.

**Please read**: [Embedded web server for iOS UI testing](https://envoy.engineering/embedded-web-server-for-ios-ui-testing-8ff3cef513df#.c2i5tx380).

**See also**: Our lightweight web framework [Ambassador](https://github.com/envoy/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](#whats-swsgi-swift-web-server-gateway-interface), super flexible
- IPV6 ready, also supports IPV4 (dual stack)
- Automatic testing covered

## Example

Here's a simple example shows how Embassy works.

```Swift
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:

```Swift
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

```Swift
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](https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/) 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)](https://www.python.org/dev/peps/pep-3333/). It's a gateway interface enables web applications to talk to HTTP clients without knowing HTTP server implementation details.

It's defined as

```Swift
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:

```Swift
[
"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

```Swift
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

```Swift
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

```Swift
sendBody(Data("hello".utf8))
```

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

```Swift
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

```swift
import PackageDescription

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

You can read this [example project](https://github.com/envoy/example-embassy) here.