{"id":19778661,"url":"https://github.com/envoy/embassy","last_synced_at":"2025-05-16T11:05:42.406Z","repository":{"id":44164074,"uuid":"59251906","full_name":"envoy/Embassy","owner":"envoy","description":"Super lightweight async HTTP server library in pure Swift runs in iOS / MacOS / Linux","archived":false,"fork":false,"pushed_at":"2024-02-19T20:26:05.000Z","size":334,"stargazers_count":606,"open_issues_count":28,"forks_count":74,"subscribers_count":20,"default_branch":"master","last_synced_at":"2025-05-16T11:05:35.422Z","etag":null,"topics":["asynchronous","ios","lightweight","linux","swift","webserver"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/envoy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-05-20T00:14:53.000Z","updated_at":"2025-04-29T01:03:20.000Z","dependencies_parsed_at":"2023-02-16T11:00:57.666Z","dependency_job_id":"70ecf0ab-2391-43f3-b6a8-3a600a1e1a6a","html_url":"https://github.com/envoy/Embassy","commit_stats":{"total_commits":278,"total_committers":28,"mean_commits":9.928571428571429,"dds":"0.21942446043165464","last_synced_commit":"8469f2c1b334a7c1c3566e2cb2f97826c7cca898"},"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/envoy%2FEmbassy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/envoy%2FEmbassy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/envoy%2FEmbassy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/envoy%2FEmbassy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/envoy","download_url":"https://codeload.github.com/envoy/Embassy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254518383,"owners_count":22084374,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asynchronous","ios","lightweight","linux","swift","webserver"],"created_at":"2024-11-12T05:30:25.854Z","updated_at":"2025-05-16T11:05:37.387Z","avatar_url":"https://github.com/envoy.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Embassy\n\n[![Build Status](https://travis-ci.org/envoy/Embassy.svg?branch=master)](https://travis-ci.org/envoy/Embassy)\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-brightgreen.svg)](https://github.com/Carthage/Carthage)\n[![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager)\n[![CocoaPods](https://img.shields.io/cocoapods/v/Embassy.svg)]()\n![Swift Version](https://img.shields.io/badge/Swift-5.0-orange.svg)\n![Plaform](https://img.shields.io/badge/Platform-macOS|iOS|Linux-lightgrey.svg)\n[![GitHub license](https://img.shields.io/github/license/envoy/Embassy.svg)](https://github.com/envoy/Embassy/blob/master/LICENSE)\n\nSuper lightweight async HTTP server in pure Swift.\n\n**Please read**: [Embedded web server for iOS UI testing](https://envoy.engineering/embedded-web-server-for-ios-ui-testing-8ff3cef513df#.c2i5tx380).\n\n**See also**: Our lightweight web framework [Ambassador](https://github.com/envoy/Ambassador) based on Embassy\n\n## Features\n\n - Swift 4 \u0026 5\n - iOS / tvOS / MacOS / Linux\n - Super lightweight, only 1.5 K of lines\n - Zero third-party dependency\n - Async event loop based HTTP server, makes long-polling, delay and bandwidth throttling all possible\n - HTTP Application based on [SWSGI](#whats-swsgi-swift-web-server-gateway-interface), super flexible\n - IPV6 ready, also supports IPV4 (dual stack)\n - Automatic testing covered\n\n## Example\n\nHere's a simple example shows how Embassy works.\n\n```Swift\nlet loop = try! SelectorEventLoop(selector: try! KqueueSelector())\nlet server = DefaultHTTPServer(eventLoop: loop, port: 8080) {\n    (\n        environ: [String: Any],\n        startResponse: ((String, [(String, String)]) -\u003e Void),\n        sendBody: ((Data) -\u003e Void)\n    ) in\n    // Start HTTP response\n    startResponse(\"200 OK\", [])\n    let pathInfo = environ[\"PATH_INFO\"]! as! String\n    sendBody(Data(\"the path you're visiting is \\(pathInfo.debugDescription)\".utf8))\n    // send EOF\n    sendBody(Data())\n}\n\n// Start HTTP server to listen on the port\ntry! server.start()\n\n// Run event loop\nloop.runForever()\n```\n\nThen you can visit `http://[::1]:8080/foo-bar` in the browser and see\n\n```\nthe path you're visiting is \"/foo-bar\"\n```\n\nBy 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:\n\n```Swift\nlet server = DefaultHTTPServer(eventLoop: loop, interface: \"::\", port: 8080)\n```\n\n## Async Event Loop\n\nTo 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\n\n```Swift\nlet app = { (\n    environ: [String: Any],\n    startResponse: ((String, [(String, String)]) -\u003e Void),\n    sendBody: @escaping ((Data) -\u003e Void)\n) in\n    startResponse(\"200 OK\", [])\n\n    let loop = environ[\"embassy.event_loop\"] as! EventLoop\n\n    loop.call(withDelay: 1) {\n        sendBody(Data(\"hello \".utf8))\n    }\n    loop.call(withDelay: 2) {\n        sendBody(Data(\"baby \".utf8))\n    }\n    loop.call(withDelay: 3) {\n        sendBody(Data(\"fin\".utf8))\n        sendBody(Data())\n    }\n}\n```\n\nPlease 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\n\n### call(callback: (Void) -\u003e Void)\n\nCall given callback as soon as possible in the event loop\n\n### call(withDelay: TimeInterval, callback: (Void) -\u003e Void)\n\nSchedule given callback to `withDelay` seconds then call it in the event loop.\n\n### call(atTime: Date, callback: (Void) -\u003e Void)\n\nSchedule 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.\n\n## What's SWSGI (Swift Web Server Gateway Interface)?\n\nSWSGI 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.\n\nIt's defined as\n\n```Swift\npublic typealias SWSGI = (\n    [String: Any],\n    @escaping ((String, [(String, String)]) -\u003e Void),\n    @escaping ((Data) -\u003e Void)\n) -\u003e Void\n```\n\n### `environ`\n\nIt's a dictionary contains all necessary information about the request. It basically follows WSGI standard, except `wsgi.*` keys will be `swsgi.*` instead. For example:\n\n```Swift\n[\n  \"SERVER_NAME\": \"[::1]\",\n  \"SERVER_PROTOCOL\" : \"HTTP/1.1\",\n  \"SERVER_PORT\" : \"53479\",\n  \"REQUEST_METHOD\": \"GET\",\n  \"SCRIPT_NAME\" : \"\",\n  \"PATH_INFO\" : \"/\",\n  \"HTTP_HOST\": \"[::1]:8889\",\n  \"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\",\n  \"HTTP_ACCEPT_LANGUAGE\" : \"en-US,en;q=0.8,zh-TW;q=0.6,zh;q=0.4,zh-CN;q=0.2\",\n  \"HTTP_CONNECTION\" : \"keep-alive\",\n  \"HTTP_ACCEPT\" : \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\",\n  \"HTTP_ACCEPT_ENCODING\" : \"gzip, deflate, sdch\",\n  \"swsgi.version\" : \"0.1\",\n  \"swsgi.input\" : (Function),\n  \"swsgi.error\" : \"\",\n  \"swsgi.multiprocess\" : false,\n  \"swsgi.multithread\" : false,\n  \"swsgi.url_scheme\" : \"http\",\n  \"swsgi.run_once\" : false\n]\n```\n\nTo read request from body, you can use `swsgi.input`, for example\n\n```Swift\nlet input = environ[\"swsgi.input\"] as! SWSGIInput\ninput { data in\n    // handle the body data here\n}\n```\n\nAn empty Data will be passed into the input data handler when EOF\nreached. Also please notice that, request body won't be read if `swsgi.input`\nis not set or set to nil. You can use `swsgi.input` as bandwidth control, set\nit to nil when you don't want to receive any data from client.\n\nSome extra Embassy server specific keys are\n\n - `embassy.connection` - `HTTPConnection` object for the request\n - `embassy.event_loop` - `EventLoop` object\n - `embassy.version` - Version of embassy as a String, e.g. `3.0.0`\n\n### `startResponse`\n\nFunction 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.\n\nTo response HTTP header, you can do\n\n```Swift\nstartResponse(\"200 OK\", [(\"Set-Cookie\", \"foo=bar\")])\n```\n\n`startResponse` can only be called once per request, extra call will be simply ignored.\n\n### `sendBody`\n\nFunction 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\n\n```Swift\nsendBody(Data(\"hello\".utf8))\n```\n\nTo end the response data stream simply call `sendBody` with an empty Data.\n\n```Swift\nsendBody(Data())\n```\n\n## Install\n\n### CocoaPods\n\nTo install with CocoaPod, add Embassy to your Podfile:\n\n```\npod 'Embassy', '~\u003e 4.1'\n```\n\n### Carthage\n\nTo install with Carthage, add Embassy to your Cartfile:\n\n```\ngithub \"envoy/Embassy\" ~\u003e 4.1\n```\n\n### Package Manager\n\nAdd it this Embassy repo in `Package.swift`, like this\n\n```swift\nimport PackageDescription\n\nlet package = Package(\n    name: \"EmbassyExample\",\n    dependencies: [\n        .package(url: \"https://github.com/envoy/Embassy.git\",\n                 from: \"4.1.4\"),\n    ]\n)\n```\n\nYou can read this [example project](https://github.com/envoy/example-embassy) here.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fenvoy%2Fembassy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fenvoy%2Fembassy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fenvoy%2Fembassy/lists"}