From 054bdbdd1b56f30c8b335ef197cd346dfcf051ad Mon Sep 17 00:00:00 2001 From: Logan Wright Date: Wed, 3 Aug 2016 17:45:49 -0400 Subject: [PATCH] request and response --- couscous.yml | 5 +- http/request.md | 216 +++++++++++++++++++++++++++++++++++++++++++++++ http/response.md | 119 +++++++++++++++++++++++++- 3 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 http/request.md diff --git a/couscous.yml b/couscous.yml index e9123674..8a7133d1 100644 --- a/couscous.yml +++ b/couscous.yml @@ -71,6 +71,9 @@ menu: http: name: HTTP items: - guide-droplet: + guide-request: + text: Request + relativeUrl: http/request.html + guide-response: text: Response relativeUrl: http/response.html diff --git a/http/request.md b/http/request.md new file mode 100644 index 00000000..b8a5e01d --- /dev/null +++ b/http/request.md @@ -0,0 +1,216 @@ +--- +currentMenu: http-request +--- + +# Request + +The most common part of the `HTTP` library we'll be interacting with is the `Request` type. Here's a look at some of the most commonly used attributes in this type. + +```swift +public var method: Method +public var uri: URI +public var parameters: Node +public var headers: [HeaderKey: String] +public var body: Body +public var data: Content +``` + +#### Method + +The HTTP `Method` associated with the `Request`, ie: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`. + +#### URI + +The associated `URI` of the request. We will use this to access attributes about the `uri` the request was sent to. + +For example, given the following uri: `http://vapor.codes/example?query=hi#fragments-too` + +```swift +let scheme = request.uri.scheme // http +let host = request.uri.host // vapor.codes + +let path = request.uri.path // /example +let query = request.uri.query // query=hi +let fragment = request.uri.fragment // fragments-too +``` + +#### Parameters + +The url parameters associated with the request. For example, if we have a path registered as `hello/:name/age/:age`, we would be able to access those in our request, like so: + +```swift +let name = request.parameters["name"] // String? +let age = request.parameters["age"]?.int // Int? +``` + +Or, to automatically throw on `nil` or invalid variable, you can also `extract` + +```swift +let name = try request.parameters.extract("name") as String +let age = try request.parameters.extract("age") as Int +``` + +These extract functions can cast to any `NodeInitializable` type, including your own custom types. Make sure to check out [Node](https://github.com/vapor/node) for more info. + +> Note: Vapor also provides type safe routing in the routing section of our docs. + +#### Headers + +These are the headers associated with the request. If you are preparing an outgoing request, this can be used to add your own keys. + +```swift +let contentType = request.headers["Content-Type"] +``` + +Or for outgoing requests: + +```swift +let request = Request ... +request.headers["Content-Type"] = "application/json" +request.headers["Authorization"] = ... my auth token +``` + +##### Extending Headers + +We generally seek to improve code bases by removing stringly typed code where possible. We can add variables to the headers using generic extensions. + +```swift +extension KeyAccessible where Key == HeaderKey, Value == String { + var customKey: String? { + get { + return self["Custom-Key"] + } + set { + self["Custom-Key"] = newValue + } + } +} +``` + +With this pattern implemented, our string `"Custom-Key"` is contained in one section of our code. We can now access like this: + +```swift +let customKey = request.headers.customKey + +// or + +let request = ... +request.headers.customKey = "my custom value" +``` + +#### Body + +This is the body associated with the request and represents the general data payload. You can view more about body in the associated [docs](./body.md) + +For incoming requests, we'll often pull out the associated bytes like so: + +```swift +let rawBytes = request.body.bytes +``` + +## Content + +Generally when we're sending or receiving requests, we're using them as a way to transport content. For this, Vapor provides a convenient `data` variable associated with the request that prioritizes content in a consistent way. + +For example, say I receive a request to `http://vapor.codes?hello=world`. + +```swift +let world = request.data["hello"].string +``` + +This same code will work if I receive a JSON request, for example: + +``` +{ + "hello": "world" +} +``` + +Will still be accessible through data. + +```swift +let world = request.data["hello"].string +``` + +This also applies to multi-part requests and can even be extended to new types such as XML or YAML through middleware. + +If you'd prefer to access given types more explicitly, that's totally fine. The `data` variable is purely opt-in convenience for those who want it. + +## JSON + +To access JSON directly on a given request, use the following: + +```swift +let json = request.json["hello"] +``` + +## Query + +The same applies to query convenience: + +```swift +let query = request.query["hello"] +``` + +## Key Paths + +Key paths work on most Vapor types that can have nested key value objects. Here's a couple examples of how to access given the following json: + +```json +{ + "metadata": "some metadata", + "artists" : { + "href": "http://someurl.com", + "items": [ + { + "name": "Van Gogh", + }, + { + "name": "Mozart" + } + ] + } +} +``` + +We could access the data in the following ways: + +##### Metadata + +Access top level values + +```swift +let type = request.data["metadata"].string // "some metadata" +``` + +##### Items + +Access nested values + +```swift +let items = request.data["artists", "items"] // [["name": "Van Gogh"], ["name": "Mozart"]] +``` + +##### Mixing Arrays and Objects + +Get first artists + +```swift +let first = request.data["artists", "items", 0] // ["name": "Van Gogh"] +``` + +##### Array Item + +Get key from array item + +```swift +let firstName = request.data["artists", "items", 0, "name"] // "Van Gogh" +``` + +##### Array Comprehension + +We can also smartly map an array of keys, for example, to just get the names of all of the artists, we could use the following + +```swift +let names = request.data["artists", "items", "name"] // ["Van Gogh", "Mozart"] +``` diff --git a/http/response.md b/http/response.md index 96f1e489..758bb542 100644 --- a/http/response.md +++ b/http/response.md @@ -4,4 +4,121 @@ currentMenu: http-response # Response -Sometimes a custom `Respons`e object needs to be created instead of using something that is `ResponseRepresentable`. For instance, if you need to manually assign cookies. \ No newline at end of file +When building endpoints, we'll often be returning responses for requests. If we're making outgoing requests, we'll be receiving them. + +```swift +public let status: Status +public var headers: [HeaderKey: String] +public var body: Body +public var data: Content +``` + +#### Status + +The http status associated with the event, for example `.ok` == 200 ok. + +#### Headers + +These are the headers associated with the request. If you are preparing an outgoing response, this can be used to add your own keys. + +```swift +let contentType = response.headers["Content-Type"] +``` + +Or for outgoing response: + +```swift +let response = response ... +response.headers["Content-Type"] = "application/json" +response.headers["Authorization"] = ... my auth token +``` + +##### Extending Headers + +We generally seek to improve code bases by removing stringly typed code where possible. We can add variables to the headers using generic extensions. + +```swift +extension KeyAccessible where Key == HeaderKey, Value == String { + var customKey: String? { + get { + return self["Custom-Key"] + } + set { + self["Custom-Key"] = newValue + } + } +} +``` + +With this pattern implemented, our string `"Custom-Key"` is contained in one section of our code. We can now access like this: + +```swift +let customKey = response.headers.customKey + +// or + +let request = ... +response.headers.customKey = "my custom value" +``` + +#### Body + +This is the body associated with the response and represents the general data payload. You can view more about body in the associated [docs](./body.md) + +For responses, the body is most commonly set at initialization. With two main types. + +##### BodyRepresentable + +Things that can be converted to bytes, ie: + +```swift +let response = Response(status: .ok, body: "some string") +``` + +In the above example, the `String` will be automatically converted to a body. Your own types can do this as well. + +##### Bytes Directly + +If we already have our bytes array, we can pass it into the body like so: + +```swift +let response = Response(status: .ok, body: .data(myArrayOfBytes)) +``` + +##### Chunked + +To send an `HTTP.Response` in chunks, we can pass a closure that we'll use to send our response body in parts. + +```swift +let response = Response(status: .ok) { chunker in + for name in ["joe", "pam", "cheryl"] { + sleep(1) + try chunker.send(name) + } + + try chunker.close() +} +``` + +> Make sure to call `close()` before the chunker leaves scope. + +## Content + +We can access content the same we do in a [request](./request.md). This most commonly applies to outgoing requests. + +```swift +let pokemonResponse = try drop.client.get("http://pokeapi.co/api/v2/pokemon/") +let names = pokemonResponse.data["results", "name"]?.array +``` + +## JSON + +To access JSON directly on a given response, use the following: + +```swift +let json = request.response["hello"] +``` + +## Key Paths + +For more on KeyPaths, visit [here](./request.md#key-paths)