{"id":20038045,"url":"https://github.com/perfectlysoft/perfect-nio","last_synced_at":"2025-05-05T06:32:06.879Z","repository":{"id":39657042,"uuid":"159360383","full_name":"PerfectlySoft/Perfect-NIO","owner":"PerfectlySoft","description":"Perfect 4 NIO","archived":false,"fork":false,"pushed_at":"2022-05-29T00:49:03.000Z","size":169,"stargazers_count":20,"open_issues_count":2,"forks_count":2,"subscribers_count":5,"default_branch":"master","last_synced_at":"2023-06-15T03:45:43.094Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PerfectlySoft.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}},"created_at":"2018-11-27T15:50:27.000Z","updated_at":"2022-05-31T03:39:47.000Z","dependencies_parsed_at":"2022-09-20T07:12:43.434Z","dependency_job_id":null,"html_url":"https://github.com/PerfectlySoft/Perfect-NIO","commit_stats":null,"previous_names":[],"tags_count":null,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PerfectlySoft%2FPerfect-NIO","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PerfectlySoft%2FPerfect-NIO/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PerfectlySoft%2FPerfect-NIO/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PerfectlySoft%2FPerfect-NIO/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PerfectlySoft","download_url":"https://codeload.github.com/PerfectlySoft/Perfect-NIO/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224429337,"owners_count":17309703,"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":[],"created_at":"2024-11-13T10:24:59.827Z","updated_at":"2024-11-13T10:25:00.608Z","avatar_url":"https://github.com/PerfectlySoft.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://developer.apple.com/swift/\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/Swift-5.0-orange.svg?style=flat\" alt=\"Swift 5.0\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://developer.apple.com/swift/\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/Platforms-macOS%20%7C%20Linux%20-lightgray.svg?style=flat\" alt=\"Platforms macOS | Linux\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"http://perfect.org/licensing.html\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/License-Apache-lightgrey.svg?style=flat\" alt=\"License Apache\"\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n# Perfect 4 NIO\n\nThis project is a work in progress and should be considered **alpha quality** until this sentence is removed.\n\n\u003ca href=\"#usage\"\u003ePackage.swift Usage\u003c/a\u003e\n\n### Intro\n\nPerfect 4 NIO is a Swift based API server. It provides the ability to serve HTTP/S endpoints by creating one or more URI based routes and binding them to a port. Each route is built as a series of operations, each accepting and returning some sort of value. A route finally terminates and outputs to the client by returning an `HTTPOutput` object.\n\n### Simple Routing\n\n```swift\nroot { \"Hello, world!\" }.text()\n```\n\nThis simple route would be applied to the root `/` of the server. It accepts nothing, Void, but returns a String. That string would be returned to the client with the text/plain content type.\n\nHowever, that bit of code produces an unused value. To serve a route you must first bind it to a port, ask it to listen for requests, then (optionally) wait until the process is terminated.\n\n```swift\ntry root { \"Hello, world!\" }.text().bind(port: 8080).listen().wait()\n```\n\nThis will create a route and bind it to port 8080. It will then serve HTTP clients on that port until the process exits.\n\nEach of these steps can be broken up as nessesary.\n\n```swift\nlet route = root { \"Hello, world!\" }\nlet textOutput = route.text()\nlet boundServer = try textOutput.bind(port: 8080)\nlet listeningServer = try boundServer.listen()\ntry listeningServer.wait()\t\n```\n\n### Root\n\nThe `root` function is used to create a route beginning with `/`. The root is, by default, a function accepting an \u003ca href=\"#httprequest\"\u003e`HTTPRequest`\u003c/a\u003e and returning an `HTTPRequest`; an identity function. There are a few other variants of the `root` func. These are listed here: \u003ca href=\"#rooot\"\u003eroot\u003c/a\u003e.\n\nThe type of object returned by `root` is a `Routes` object. Routes is defined simply as:\n\n```swift\npublic struct Routes\u003cInType, OutType\u003e {}\n```\n\nA `Routes` object encompasses one or more paths with their associated functions. For a particular route, all enclosed functions accept `InType` and return `OutType`.\n\n### Paths\n\nA route can have additional path components added to it by using Swift 4.2 dynamic member lookup.\n\n```swift\nlet route = root().hello { \"Hello, world!\" }\n```\n\nNow the route serves itself on `/hello`.\n\n```swift\nlet route = root().hello.world { \"Hello, world!\" }\n```\n\nNow the route serves itself on `/hello/world`.\n\nEquivalently, you may use the `path` func to achieve the same thing.\n\n```swift\nlet route = root().path(\"hello\").path(\"world\") { \"Hello, world!\" }\n```\nor\n\n```swift\nlet route = root().path(\"hello/world\") { \"Hello, world!\" }\n```\n\nThis may be required in cases where your desired path component string conflicts with a built-in func (*\\*list these somewhere simply*) or contains characters which are invalid for Swift identifiers. You may also simply prefer it stylistically, or may be using variable path names. These are all good reasons why one might want to use the `path` func over dynamic member lookup.\n\nAll further examples in this document use dynamic member lookup.\n\nNote that paths which begin with a number, or consist wholly of numbers, are valid when using dynamic member lookup, even though they would normally not be when used as a property or func. This is a bit of a digression, but, for example:\n\n```swift\nlet route = root().1234 { \"This is cool\" }.text()\n\nstruct MyTotallyUnrelatedStruct {\n\tfunc 1234() -\u003e String { ... } // compilation error\n}\n```\n\n### Combining Routes\n\nMost servers will want to service more than one URI. Routes can be combined in various ways. Combined routes behave as though they were one route. Combined routes can be bound and can listen for connections the same as an individual route can.\n\nRoutes are combined using the `dir` func. Dir will append the given routes to the receiver and return a new route object containing all of the routes.\n\n```swift\nlet helloRoute = root().hello { \"Hello, world!\" }\nlet byeRoute = root().bye { \"Bye, world!\" }\n\nlet combinedRoutes = try root().v1.dir(helloRoute, byeRoute).text()\n\ntry combinedRoutes.bind(port: 8080).listen().wait()\n```\n\nThe above creates two routes which can be accessed at the URIs `/v1/hello` and `/v1/bye`. These two routes are combined and then the `text()` func is applied to them so that they return a text/plain content type.\n\nDir will ensure that you are not adding any duplicate routes and will throw an Error if you are.\n\nDir can be called and passed either a variadic number of routes, an array of routes, or a closure which accepts a stand-in route and returns an array of routes to append. Let's look closer at this last case.\n\n```swift\nlet foos = try root().v1.dir{[\n\t$0.foo1 { \"OK1\" },\n\t$0.foo2 { \"OK2\" },\n\t$0.foo3 { \"OK3\" },\n]}.text()\n```\n\nThis produces the following routes: `/v1/foo1`, `/v1/foo2`, and `/v1/foo3`, which each have the `text()` func applied to them.\n\nIt's important to note that because routes are strongly typed, all routes that are passed to `dir` must accept whatever type of value the preceeding function returns. Any misuse will be caught at compilation time.\n\nThe following case passes the current request's `path` URI to two different routes which each modify the value in some way and then pass it down the line.\n\n```swift\nlet route = try root { $0.path }.v1.dir {[\n\t$0.upper { $0.uppercased() },\n\t$0.lower { $0.lowercased() }\n]}.text()\t\t\t\n```\n\nThe above produces the following routes: `/v1/upper` and `/v1/lower`.\n\n### HTTP Method\n\nUnless otherwise indicated, a route will serve for any HTTP method (GET, POST, etc.). Calling one of the method properties on a route will force it to serve only with the method indicated. \n\n```swift\nlet route = try root().dir {[\n\t$0.GET.foo1 { \"GET OK\" },\n\t$0.POST.foo2 { \"POST OK\" },\n]}.text()\n```\n\nAbove, two routes are added, both with a URI of `/`. However, one accepts only GET and the other only POST.\n\nIf you wish a route to serve more than one HTTP method, the `method` func will facilitate this.\n\n```swift\nlet route = root().method(.GET, .POST).foo { \"GET or POST OK\" }.text()\n```\n\nThis will creat a route `/foo` which will answer to either GET or POST.\n\nApplying a method like this to routes which have already had methods applied to them will remove the old method and apply the new.\n\n### Route Operations\n\nA variety of operations can be applied to a route. These operations include:\n\n#### map\nTransform an output in some way producing a new output or a sequence of output values.\n\nDefinitions:\n\n```swift\npublic extension Routes {\n\t/// Add a function mapping the input to the output.\n\tfunc map\u003cNewOut\u003e(_ call: @escaping (OutType) throws -\u003e NewOut) -\u003e Routes\u003cInType, NewOut\u003e\n\t/// Add a function mapping the input to the output.\n\tfunc map\u003cNewOut\u003e(_ call: @escaping () throws -\u003e NewOut) -\u003e Routes\u003cInType, NewOut\u003e\n\t/// Map the values of a Collection to a new Array.\n\tfunc map\u003cNewOut\u003e(_ call: @escaping (OutType.Element) throws -\u003e NewOut) -\u003e Routes\u003cInType, Array\u003cNewOut\u003e\u003e where OutType: Collection \n}\n```\n\nExample:\n\n```swift\nlet route = try root().dir {[\n\t$0.a { 1 }.map { \"\\($0)\" }.text(),\n\t$0.b { [1,2,3] }.map { (i: Int) -\u003e String in \"\\(i)\" }.json()\n]}\n```\n\n#### ext\nApply a file extension to the routes.\n\nDefinitions:\n\n```swift\npublic extension Routes {\n\t/// Adds the indicated file extension to the route set.\n\tfunc ext(_ ext: String) -\u003e Routes\n\t/// Adds the indicated file extension to the route set.\n\t/// Optionally set the response's content type.\n\t/// The given function accepts the input value and returns a new value.\n\tfunc ext\u003cNewOut\u003e(_ ext: String,\n\t\t\t\t\t  contentType: String? = nil,\n\t\t\t\t\t  _ call: @escaping (OutType) throws -\u003e NewOut) -\u003e Routes\u003cInType, NewOut\u003e\n}\n```\n\nExample:\n\nThe following returns a `Foo` object to the client and makes the object available as either `json` or `text` by adding an appropriate file extension to the route URI.\n\n```swift\nstruct Foo: Codable, CustomStringConvertible {\n\tvar description: String {\n\t\treturn \"foo-data \\(id)/\\(date)\"\n\t}\n\tlet id: UUID\n\tlet date: Date\n}\nlet fooRoute = root().foo { Foo(id: UUID(), date: Date()) }\nlet route = try root().dir(\n\t\t\tfooRoute.ext(\"json\").json(),\n\t\t\tfooRoute.ext(\"txt\").text())\n```\n\nThis will produce the routes `/foo.json` and `/foo.txt`.\n\n#### wild\nApply a wildcard path segment.\n\nDefinitions:\n\n```swift\npublic extension Routes {\n\t/// Adds a wildcard path component to the route set.\n\t/// The given function accepts the input value and the value for that wildcard path component, as given by the HTTP client,\n\t/// and returns a new value.\n\tfunc wild\u003cNewOut\u003e(_ call: @escaping (OutType, String) throws -\u003e NewOut) -\u003e Routes\u003cInType, NewOut\u003e\n\t/// Adds a wildcard path component to the route set.\n\t/// Gives the wildcard path component a variable name and the path component value is added as a request urlVariable.\n\tfunc wild(name: String) -\u003e Routes\n}\n```\n\nExample:\n\n```swift\nlet route = root().wild { $1 }.foo.text()\n```\n\nAbove, the route `/*/foo` is created. The \"*\" can be any string, which is then echoed back to the client.\n\nWIldcard path components can also be given a name. This will make the component available through the `HTTPRequest.uriVariables` property, or for use during Decodable `decode` operations.\n\nExample:\n\n```swift\nstruct Req: Codable {\n\tlet id: UUID\n\tlet action: String\n}\nlet route = root().v1\n\t.wild(name: \"id\")\n\t.wild(name: \"action\")\n\t.decode(Req.self) { return \"\\($1.id) - \\($1.action)\" }\n\t.text()\n```\n\nThe above creates the route `/v1/*/*`. The \"id\" and \"action\" wildcards are saved and used during decoding of the `Req` object. The `Req` properties are then echoed back to the client.\n\n#### trailing\nApply a trailing wildcard path segment\n\nDefinitions:\n\n```swift\npublic extension Routes {\n\t/// Adds a trailing-wildcard to the route set.\n\t/// The given function accepts the input value and the value for the remaining path components, as given by the HTTP client,\n\t/// and returns a new value.\n\tfunc trailing\u003cNewOut\u003e(_ call: @escaping (OutType, String) throws -\u003e NewOut) -\u003e Routes\u003cInType, NewOut\u003e\n}\n```\n\nExample:\n\n```swift\nlet route = root().foo.trailing { $1 }.text()\n```\n\nThe route `/foo/**` is created, with \"**\" matching any subsequent path components. The remaining path components String is available as the second argument (the first argument is still the current `HTTPRequest` object), and is then echoed back to the client. If a client accessed the URI `/foo/OK/OK`, the String \"OK/OK\" would be made available as the trailing wildcard value.\n\n#### request\nAccess the HTTPRequest object. \n\nWhile all routes start off by receiving the current HTTPRequest object, it can often be more convenient to begin passing other values down your route pipeline but then go back to the request object for some cause.\n\nDefinitions:\n\n```swift\npublic extension Routes {\n\t/// Adds the current HTTPRequest as a parameter to the function.\n\tfunc request\u003cNewOut\u003e(_ call: @escaping (OutType, HTTPRequest) throws -\u003e NewOut) -\u003e Routes\u003cInType, NewOut\u003e \n}\n```\n\nExample:\n\n```swift\nlet route = root().foo { \"OK\" }.request { $1.path }.text()\n```\n\nThis route URI is `/foo`. It echos back the request path by first discarding the HTTPRequest but then grabbing it again using the `request` func. The request is always provided as the second argument.\n\n#### readBody\nRead the client body data and deliver it to the provided callback.\n\nDefinitions:\n\n```swift\npublic extension Routes {\n\tfunc readBody\u003cNewOut\u003e(_ call: @escaping (OutType, HTTPRequestContentType) throws -\u003e NewOut) -\u003e Routes\u003cInType, NewOut\u003e\n}\n```\n\nExample:\n\n```swift\nlet route = root().POST.readBody {\n\t(req: HTTPRequest, content: HTTPRequestContentType) -\u003e String in\n\tswitch content {\n\tcase .urlForm: return \"url-encoded\"\n\tcase .multiPartForm: return \"multi-part\"\n\tcase .other: return \"other\"\n\tcase .none:\treturn \"none\"\n\t}\n}.text()\n```\n\nThis example accepts a POST request at `/`. It reads the submitted body and returns a String describing what type the data was.\n\n#### statusCheck\nAssert some condition by returning either 'OK' (200..\u003c300 status code) or failing.\n\nDefinitions:\n\n```swift\npublic extension Routes {\n\t/// The caller can inspect the given input value and choose to return an HTTP error code.\n\t/// If any code outside of 200..\u003c300 is return the request is aborted.\n\tfunc statusCheck(_ handler: @escaping (OutType) throws -\u003e HTTPResponseStatus) -\u003e Routes\u003cInType, OutType\u003e\n\t/// The caller can choose to return an HTTP error code.\n\t/// If any code outside of 200..\u003c300 is return the request is aborted.\n\tfunc statusCheck(_ handler: @escaping () throws -\u003e HTTPResponseStatus) -\u003e Routes\u003cInType, OutType\u003e\n}\n```\n\nExample:\n\n```swift\nlet route = try root().dir {[\n\t$0.a,\n\t$0.b\n]}.statusCheck {\n\treq in\n\tguard req.path != \"/b\" else {\n\t\treturn .internalServerError\n\t}\n\treturn .ok\n}.map { req in \"OK\" }.text()\n```\n\nThis route will serve the URIs `/a` and `/b`. However, the request will be deliberately failed with `.internalServerError` if the `/b` URI is accessed. After the function given to `statusCheck` is called, the route continues with the previous value. You can see this in the call to `map` where its first parameter reverts back to the current request after the status check.\n\n#### decode\nRead and decode the client body as a Decodable object.\n\nDecode offers a few variants to fit different use cases.\n\nDefinitions:\n\n```swift\npublic extension Routes {\n\t/// Read the client content body and then attempt to decode it as the indicated `Decodable` type.\n\t/// Both the original input value and the newly decoded object are delivered to the provided function.\n\tfunc decode\u003cType: Decodable, NewOut\u003e(_ type: Type.Type,\n\t\t\t\t\t     _ handler: @escaping (OutType, Type) throws -\u003e NewOut) -\u003e Routes\u003cInType, NewOut\u003e\n\t/// Read the client content body and then attempt to decode it as the indicated `Decodable` type.\n\t/// The newly decoded object is delivered to the provided function.\n\tfunc decode\u003cType: Decodable, NewOut\u003e(_ type: Type.Type,\n\t\t\t\t\t     _ handler: @escaping (Type) throws -\u003e NewOut) -\u003e Routes\u003cInType, NewOut\u003e\n\t/// Read the client content body and then attempt to decode it as the indicated `Decodable` type.\n\t/// The newly decoded object becomes the route set's new output value.\n\tfunc decode\u003cType: Decodable\u003e(_ type: Type.Type) -\u003e Routes\u003cInType, Type\u003e\n\t/// Decode the request body into the desired type, or throw an error.\n\t/// This function would be used after the content body has already been read.\n\tfunc decode\u003cA: Decodable\u003e(_ type: A.Type, content: HTTPRequestContentType) throws -\u003e A\n}\n```\n\nExample:\n\n```swift\nstruct Foo: Codable {\n\tlet id: UUID\n\tlet date: Date\n}\nlet route = try root().POST.dir{[\n\t$0.1.decode(Foo.self),\n\t$0.2.decode(Foo.self) { $1 },\n\t$0.3.decode(Foo.self) { $0 },\n]}.json()\n```\n\nThis example will serve the URIs `/1`, `/2`, and `/3`. It decodes the POST body in three different ways. #1 decodes the body and returns it as the new value (discarding the HTTPRequest). #2 decodes the body and calls the closure with the previous value, the HTTPRequest, and with the newly decoded Foo object. This case simply returns the Foo as the new value. #3 decodes the body and accepts it in a closure which accepts only one argument. This also is simply returned as the new value. Finally, regardless of which URI was hit, the value is converted to json and returns to the client.\n\n#### unwrap\nUnwrap an Optional value, or fail the request if the value is nil.\n\nDefinitions:\n\n```swift\npublic extension Routes {\n\t/// If the output type is an `Optional`, this function permits it to be safely unwraped.\n\t/// If it can not be unwrapped the request is terminated.\n\t/// The provided function is called with the unwrapped value.\n\tfunc unwrap\u003cU, NewOut\u003e(_ call: @escaping (U) throws -\u003e NewOut) -\u003e Routes\u003cInType, NewOut\u003e where OutType == Optional\u003cU\u003e\n}\n```\n\nExample:\n\n```swift\nlet route = try root().dir {[\n\t$0.a { nil },\n\t$0.b { \"OK\" }\n]}.unwrap { $0 }.text()\n```\n\nThe above creates `/a` and `/b`. `/a` returns a nil `String?` while `/b` returns \"OK\". Either route's value will go through the `unwrap` func. If the value is nil, the request will be failed with an `.internalServerError`. (KRJ: address this. needs to be more flexible wrt response status code.)\n\n#### async\nExecute a task asynchronously, out of the NIO event loop.\n\nWhen performing lengthy or blocking operations, such as external URL requests or database operations, it is vital that the operation be moved out of the NIO event loop. This `async` func lets you do just that. The activity moves into a new thread out of the NIO event loop within which you have free reign. When your activity has completed, signal the provided EventLoopPromise with your return value by calling either `success` or `fail`.\n\nDefinitions:\n\n```swift\npublic extension Routes {\n\t/// Run the call asynchronously on a non-event loop thread.\n\t/// Caller must succeed or fail the given promise to continue the request.\n\tfunc async\u003cNewOut\u003e(_ call: @escaping (OutType, EventLoopPromise\u003cNewOut\u003e) -\u003e ()) -\u003e Routes\u003cInType, NewOut\u003e\n}\n```\n\nExample:\n\n```swift\nlet route = root().async {\n\t(req: HTTPRequest, p: EventLoopPromise\u003cString\u003e) in\n\tsleep(1)\n\tp.succeed(result: \"OK\")\n}.text()\n```\n\nThe above spins off an asynchronous activity (in this case, sleeping for 1 second) and then signals that it is complete. The value that it submits to the promise, \"OK\", is sent to the client.\n\nIt's important to note that subsequent activities for the route will occur on the NIO event loop.\n\n#### text\nUse a `CustomStringConvertible` as the output with a text/plain content type.\n\nDefinitions:\n\n```swift\npublic extension Routes where OutType: CustomStringConvertible {\n\tfunc text() -\u003e Routes\u003cInType, HTTPOutput\u003e\n}\n```\n\nExample:\n\n```swift\nlet route = root { 1 }.text()\n```\n\nAbove, the route `/` is created which serves the stringified number 1.\n\n#### json\nUse an `Encodable` as the output with the application/json content type.\n\nDefinitions:\n\n```swift\npublic extension Routes where OutType: Encodable {\n\tfunc json() -\u003e Routes\u003cInType, HTTPOutput\u003e\n}\n```\n\nExample:\n\n```swift\nstruct Foo: Codable {\n\tlet id: UUID\n\tlet date: Date\n}\nlet route = root().foo { Foo(id: UUID(), date: Date()) }.json()\n```\n\nThis example create a route `/foo` which returns a Foo object. The Foo is converted to JSON and sent to the client.\n\n#### compressed\n\nOutgoing client content can be compressed using either gzip or deflate algorithms by calling the `compressed()` function on any route returning HTTPOutput.\n\n```swift\n/// Compresses eligible output\npublic extension Routes where OutType: HTTPOutput {\n\tfunc compressed() -\u003e Routes\u003cInType, HTTPOutput\u003e\n}\n```\n\nCompressed content takes HTTPOutput and then selectively compresses and sends the content to the client. If the source HTTPOutput object specifies a response Content-Length and that content length is less than 14k, the response will not be compressed. If the source HTTPoutput specifies a content-type and that type begins with \"image/\", \"video/\", or \"audio/\", the response will not be compressed.\n\nExample:\n\n```swift\nclass StreamOutput: HTTPOutput {\n\tvar counter = 0\n\toverride init() {\n\t\tsuper.init()\n\t\tkind = .stream\n\t}\n\toverride func body(promise: EventLoopPromise\u003cIOData?\u003e, allocator: ByteBufferAllocator) {\n\t\tif counter \u003e 15 {\n\t\t\tpromise.succeed(result: nil)\n\t\t} else {\n\t\t\tlet toSend = String(repeating: \"\\(counter % 10)\", count: 1024)\n\t\t\tcounter += 1\n\t\t\tlet ary = Array(toSend.utf8)\n\t\t\tvar buf = allocator.buffer(capacity: ary.count)\n\t\t\tbuf.write(bytes: ary)\n\t\t\tpromise.succeed(result: .byteBuffer(buf))\n\t\t}\n\t}\n}\nlet route = root() { return StreamOutput() as HTTPOutput }.compressed()\n```\n\nThis example streams text content to the client. The usage of `.compressed()` at the end of the route will turn on content compression.\n\n### HTTPOutput\n\nConsidering a complete set of routes as a function, it would look like:\n\n`(HTTPRequest) -\u003e HTTPOutput`\n\n\u003ca href=\"httpoutput\"\u003e`HTTPOutput`\u003c/a\u003e is a base class which can optionally set the HTTP response status, headers and body data. Several concrete HTTPOutput implementations are provided for you, but you can add your own custom output by sub-classing and returning your object.\n\nBuilt-in HTTPOutput types include `HTTPOutputError`, which can be thrown, JSONOutput, TextOutput, CompressedOutput, FileOutput, MustacheOutput, and BytesOutput.\n\n```swift\n/// The response output for the client\nopen class HTTPOutput {\n\t/// Indicates how the `body` func data, and possibly content-length, should be handled\n\tvar kind: HTTPOutputResponseHint\n\t/// Optional HTTP head\n\topen func head(request: HTTPRequestHead) -\u003e HTTPHead?\n\t/// Produce body data\n\t/// Set nil on last chunk\n\t/// Call promise.fail upon failure\n\topen func body(promise: EventLoopPromise\u003cIOData?\u003e, allocator: ByteBufferAllocator)\n\t/// Called when the request has completed either successfully or with a failure.\n\t/// Sub-classes can override to take special actions or perform cleanup operations.\n\t/// Inherited implimenation does nothing.\n\topen func closed()\n}\n```\n\n#### FileOutput\n\nFile content can be returned from a route by using the `FileOutput` type.\n\n```swift\npublic class FileOutput: HTTPOutput {\n\tpublic init(localPath: String) throws\n}\n```\n\nExample:\n\n```swift\nlet route = root().test {\n\ttry FileOutput(localPath: \"/tmp/test.txt\") as HTTPOutput\n}.ext(\"txt\")\n```\n\nThis example serves the route /test.txt and returns the content of a local file. If the file does not exist or is not readable then an Error will be thrown.\n\n#### MustacheOutput\n\nContent from mustache templates can be returned from a route by using the `MustacheOutput` type. \n\n```swift\npublic class MustacheOutput: HTTPOutput {\n\tpublic init(templatePath: String,\n\t\t    inputs: [String:Any],\n\t\t    contentType: String) throws \n}\n```\n\nExample:\n\n```swift\nlet route = root().test {\n\ttry MustacheOutput(templatePath: tmpFilePath,\n\t\t\t   inputs: [\"key1\":\"value1\", \"key2\":\"value2\"],\n\t\t\t   contentType: \"text/html\") as HTTPOutput\n}.ext(\"html\")\n```\n\nThis example processes and serves a mustache template file as text/html.\n\n### Caveats\n\nmake notes on:\nusing diseparate types in `dir`\nordering of `wild` and `decode` wrt path variables\ndoing blocking activities in a non-async func\n\n*TBD:*\n\n* Logging - use the new sss logging stuff\n\n### Reference\n\n\u003ca name=\"rooot\"\u003e\u003c/a\u003e\n#### root()\n\n```swift\n/// Create a root route accepting/returning the HTTPRequest.\npublic func root() -\u003e Routes\u003cHTTPRequest, HTTPRequest\u003e\n/// Create a root route accepting the HTTPRequest and returning some new value.\npublic func root\u003cNewOut\u003e(_ call: @escaping (HTTPRequest) throws -\u003e NewOut) -\u003e Routes\u003cHTTPRequest, NewOut\u003e\n/// Create a root route returning some new value.\npublic func root\u003cNewOut\u003e(_ call: @escaping () throws -\u003e NewOut) -\u003e Routes\u003cHTTPRequest, NewOut\u003e\n/// Create a root route accepting and returning some new value.\npublic func root\u003cNewOut\u003e(path: String = \"/\", _ type: NewOut.Type) -\u003e Routes\u003cNewOut, NewOut\u003e\n```\n\n\u003ca name=\"routes\"\u003e\u003c/a\u003e\n#### Routes\\\u003cInType, OutType\\\u003e\n\n```swift\n/// Main routes object.\n/// Created by calling `root()` or by chaining a function from an existing route.\npublic struct Routes\u003cInType, OutType\u003e {\n\t// Routes can not be directly instantiated.\n\t// All functionality is provided through extensions.\n}\n```\n\n\u003ca name=\"httprequest\"\u003e\u003c/a\u003e\n#### HTTPRequest\n\n```swift\npublic protocol HTTPRequest {\n\tvar method: HTTPMethod { get }\n\tvar uri: String { get }\n\tvar headers: HTTPHeaders { get }\n\tvar uriVariables: [String:String] { get set }\n\tvar path: String { get }\n\tvar searchArgs: QueryDecoder? { get }\n\tvar contentType: String? { get }\n\tvar contentLength: Int { get }\n\tvar contentRead: Int { get }\n\tvar contentConsumed: Int { get }\n\tvar localAddress: SocketAddress? { get }\n\tvar remoteAddress: SocketAddress? { get }\n\t/// Returns all the cookie name/value pairs parsed from the request.\n\tvar cookies: [String:String]\n\tfunc readSomeContent() -\u003e EventLoopFuture\u003c[ByteBuffer]\u003e\n\tfunc readContent() -\u003e EventLoopFuture\u003cHTTPRequestContentType\u003e\n}\n```\n\n\u003ca name=\"querydecoder\"\u003e\u003c/a\u003e\n#### QueryDecoder\n\n```swift\npublic struct QueryDecoder {\n\tpublic init(_ c: [UInt8])\n\tpublic subscript(_ key: String) -\u003e [String]\n\tpublic func map\u003cT\u003e(_ call: ((String,String)) throws -\u003e T) rethrows -\u003e [T]\n\tpublic func mapBytes\u003cT\u003e(_ call: ((String,ArraySlice\u003cUInt8\u003e)) throws -\u003e T) rethrows -\u003e [T]\n\tpublic func get(_ key: String) -\u003e [ArraySlice\u003cUInt8\u003e]\n}\n```\n\n\u003ca name=\"dir\"\u003e\u003c/a\u003e\n#### dir\n\n```swift\n/// These extensions append new route sets to an existing set.\npublic extension Routes {\n\t/// Append new routes to the set given a new output type and a function which receives a route object and returns an array of new routes.\n\t/// This permits a sort of shorthand for adding new routes.\n\t/// At times, Swift's type inference can fail to discern what the programmer intends when calling functions like this.\n\t/// Calling the second version of this method, the one accepting a `type: NewOut.Type` as the first parameter,\n\t/// can often clarify your intentions to the compiler. If you experience a compilation error with this function, try the other.\n\tfunc dir\u003cNewOut\u003e(_ call: (Routes\u003cOutType, OutType\u003e) throws -\u003e [Routes\u003cOutType, NewOut\u003e]) throws -\u003e Routes\u003cInType, NewOut\u003e\n\t/// Append new routes to the set given a new output type and a function which receives a route object and returns an array of new routes.\n\t/// This permits a sort of shorthand for adding new routes.\n\t/// The first `type` argument to this function serves to help type inference.\n\tfunc dir\u003cNewOut\u003e(type: NewOut.Type, _ call: (Routes\u003cOutType, OutType\u003e) -\u003e [Routes\u003cOutType, NewOut\u003e]) throws -\u003e Routes\u003cInType, NewOut\u003e\n\t/// Append new routes to this set given an array.\n\tfunc dir\u003cNewOut\u003e(_ registries: [Routes\u003cOutType, NewOut\u003e]) throws -\u003e Routes\u003cInType, NewOut\u003e\n\t/// Append a new route set to this set.\n\tfunc dir\u003cNewOut\u003e(_ registry: Routes\u003cOutType, NewOut\u003e, _ registries: Routes\u003cOutType, NewOut\u003e...) throws -\u003e Routes\u003cInType, NewOut\u003e\n}\n```\n\n\u003ca name=\"routeerror\"\u003e\u003c/a\u003e\n#### RouteError\n\n```swift\n/// An error occurring during process of building a set of routes.\npublic enum RouteError: Error, CustomStringConvertible {\n\tcase duplicatedRoutes([String])\n\tpublic var description: String\n}\n```\n\n\u003ca name=\"httpoutput\"\u003e\u003c/a\u003e\n#### HTTPOutput\n\n```swift\n/// The response output for the client\nopen class HTTPOutput {\n\t/// Indicates how the `body` func data, and possibly content-length, should be handled\n\tvar kind: HTTPOutputResponseKind\n\t/// Optional HTTP head\n\topen func head(request: HTTPRequestHead) -\u003e HTTPHead?\n\t/// Produce body data\n\t/// Set nil on last chunk\n\t/// Call promise.fail upon failure\n\topen func body(promise: EventLoopPromise\u003cIOData?\u003e, allocator: ByteBufferAllocator)\n\t/// Called when the request has completed either successfully or with a failure.\n\t/// Sub-classes can override to take special actions or perform cleanup operations.\n\t/// Inherited implimenation does nothing.\n\topen func closed()\n}\n```\n\n\u003ca name=\"httprequestcontenttype\"\u003e\u003c/a\u003e\n#### HTTPRequestContentType\n\n```swift\n/// Client content which has been read and parsed (if needed).\npublic enum HTTPRequestContentType {\n\t/// There was no content provided by the client.\n\tcase none\n\t/// A multi-part form/file upload.\n\tcase multiPartForm(MimeReader)\n\t/// A url-encoded form.\n\tcase urlForm(QueryDecoder)\n\t/// Some other sort of content.\n\tcase other([UInt8])\n}\n```\n\n\u003ca name=\"listeningroutes\"\u003e\u003c/a\u003e\n#### ListeningRoutes\n\n```swift\n/// Routes which have been bound to a port and have started listening for connections.\npublic protocol ListeningRoutes {\n\t/// Stop listening for requests\n\t@discardableResult\n\tfunc stop() -\u003e ListeningRoutes\n\t/// Wait, perhaps forever, until the routes have stopped listening for requests.\n\tfunc wait() throws\n}\n```\n\n\u003ca name=\"boundroutes\"\u003e\u003c/a\u003e\n#### BoundRoutes\n\n```swift\n/// Routes which have been bound to an address but are not yet listening for requests.\npublic protocol BoundRoutes {\n\t/// The address the server is bound to.\n\tvar address: SocketAddress { get }\n\t/// Start listening\n\tfunc listen() throws -\u003e ListeningRoutes\n}\n```\n\n\u003ca name=\"methods\"\u003e\u003c/a\u003e\n#### HTTP Methods\n\n```swift\npublic extension Routes {\n\tvar GET: Routes\u003cInType, OutType\u003e\n\tvar POST: Routes\n\tvar PUT: Routes\n\tvar DELETE: Routes\n\tvar OPTIONS: Routes\n\tfunc method(_ method: HTTPMethod, _ methods: HTTPMethod...) -\u003e Routes\n}\n```\n\n\u003ca name=\"usage\"\u003e\u003c/a\u003e\n### Package.swift Usage\n\nIn your Package.swift:\n\n```swift\n.package(url: \"https://github.com/PerfectlySoft/Perfect-NIO.git\", .branch(\"master\"))\n```\n\nYour code may need to `import PerfectNIO`, `import NIO`, `import NIOHTTP1`, or `import NIOSSL`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fperfectlysoft%2Fperfect-nio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fperfectlysoft%2Fperfect-nio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fperfectlysoft%2Fperfect-nio/lists"}