mirror of https://github.com/vapor/docs.git
add content, client, websocket sections
This commit is contained in:
parent
5d1565e9be
commit
7c8d45905e
|
|
@ -0,0 +1,45 @@
|
|||
# Using Client
|
||||
|
||||
[`Client`](#fixme) is a convenience wrapper around the lower level [HTTP → Client](../http/client.md). It automatically parses things like hostname and port from URIs and helps you encode and decode [Content](content.md).
|
||||
|
||||
```swift
|
||||
let res = try req.client().get("http://vapor.codes")
|
||||
print(res) // Future<Response>
|
||||
```
|
||||
|
||||
## Container
|
||||
|
||||
The first thing you will need is a service [Container](../getting-started/services.md#container) to create your client.
|
||||
|
||||
If you are making this external API request as the result of an incoming request to your server, you should use the `Request` container to create a client. This is most often the case.
|
||||
|
||||
If you need a client during boot, use the `Application` or if you are in a `Command` use the command context's container.
|
||||
|
||||
Once you have a `Container`, use the [`client()`](#fixme) method to create a `Client`.
|
||||
|
||||
```swift
|
||||
// Creates a generic Client
|
||||
let client = try container.client()
|
||||
```
|
||||
|
||||
## Send
|
||||
|
||||
Once you have a `Client`, you can use the [`send(...)`](#fixme) method to send a `Request`. Note that the request URI must include a scheme and hostname.
|
||||
|
||||
```swift
|
||||
let req: Request ...
|
||||
let res = try client.send(req)
|
||||
print(res) // Future<Response>
|
||||
```
|
||||
|
||||
You can also use the convenience methods like [`get(...)`](#fixme), [`post(...)`](#fixme), etc.
|
||||
|
||||
```swift
|
||||
let user: User ...
|
||||
let res = try client.post("http://api.vapor.codes/users") { post in
|
||||
try post.content.encode(user)
|
||||
}
|
||||
print(res) // Future<Response>
|
||||
```
|
||||
|
||||
See [Content](./content.md) for more information on encoding and decoding content to messages.
|
||||
|
|
@ -2,11 +2,15 @@
|
|||
|
||||
In Vapor 3, all content types (JSON, protobuf, [URLEncodedForm](../url-encoded-form/getting-started.md), [Multipart](../multipart/getting-started.md), etc) are treated the same. All you need to parse and serialize content is a `Codable` class or struct.
|
||||
|
||||
For this introduction, we will use JSON as an example. But keep in mind the API is the same for any supported content type.
|
||||
For this introduction, we will use mostly JSON as an example. But keep in mind the API is the same for any supported content type.
|
||||
|
||||
## Request
|
||||
## Server
|
||||
|
||||
Let's take a look at how you would parse the following HTTP request.
|
||||
This first section will go over decoding and encoding messages sent between your server and connected clients. See the [client](#client) section for encoding and decoding content in messages sent to external APIs.
|
||||
|
||||
### Request
|
||||
|
||||
Let's take a look at how you would parse the following HTTP request sent to your server.
|
||||
|
||||
```http
|
||||
POST /login HTTP/1.1
|
||||
|
|
@ -18,12 +22,9 @@ Content-Type: application/json
|
|||
}
|
||||
```
|
||||
|
||||
### Decode Request
|
||||
|
||||
First, create a struct or class that represents the data you expect.
|
||||
First, create a struct or class that represents the data you expect.
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
import Vapor
|
||||
|
||||
struct LoginRequest: Content {
|
||||
|
|
@ -32,24 +33,42 @@ struct LoginRequest: Content {
|
|||
}
|
||||
```
|
||||
|
||||
Then simply conform this struct or class to `Content`.
|
||||
Now we are ready to decode that HTTP request.
|
||||
Notice the key names exactly match the keys in the request data. The expected data types also match. Next conform this struct or class to `Content`.
|
||||
|
||||
#### Decode
|
||||
|
||||
Now we are ready to decode that HTTP request. Every [`Request`](#fixme) has a [`ContentContainer`](#fixme) that we can use to decode content from the message's body.
|
||||
|
||||
```swift
|
||||
router.post("login") { req -> Future in
|
||||
return req.content.decode(LoginRequest.self).map(to: HTTPStatus.self) { loginRequest in
|
||||
router.post("login") { req -> Future<HTTPStatus> in
|
||||
return req.content.decode(LoginRequest.self).map { loginRequest in
|
||||
print(loginRequest.email) // user@vapor.codes
|
||||
print(loginRequest.password) // don't look!
|
||||
return .ok
|
||||
return HTTPStatus.ok
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We use `.map(to:)` here since `req.content.decode(_:)` returns a [future](../async/getting-started.md).
|
||||
We use `.map(to:)` here since `decode(...)` returns a [future](../async/getting-started.md).
|
||||
|
||||
### Other Request Types
|
||||
!!! note
|
||||
Decoding content from requests is asynchronous because HTTP allows bodies to be split into multiple parts using chunked transfer encoding.
|
||||
|
||||
Since the request in the previous example declared JSON as its content type, Vapor knows to use a JSON decoder automatically. This same method would work just as well for the following request.
|
||||
#### Router
|
||||
|
||||
To help make decoding content from incoming requests easier, Vapor offers a few extensions on [`Router`](#fixme) to do this automatically.
|
||||
|
||||
```swift
|
||||
router.post(LoginRequest.self, at: "login") { req, loginRequest in
|
||||
print(loginRequest.email) // user@vapor.codes
|
||||
print(loginRequest.password) // don't look!
|
||||
return HTTPStatus.ok
|
||||
}
|
||||
```
|
||||
|
||||
#### Detect Type
|
||||
|
||||
Since the HTTP request in this example declared JSON as its content type, Vapor knows to use a JSON decoder automatically. This same method would work just as well for the following request.
|
||||
|
||||
```http
|
||||
POST /login HTTP/1.1
|
||||
|
|
@ -58,12 +77,23 @@ Content-Type: application/x-www-form-urlencoded
|
|||
email=user@vapor.codes&don't+look!
|
||||
```
|
||||
|
||||
All HTTP requests must include a content type to be valid. Because of this, Vapor will automatically choose an appropriate decoder or error if it encounters an unknown media type.
|
||||
|
||||
!!! tip
|
||||
You can configure which encoders/decoders Vapor uses. Read on to learn more.
|
||||
You can [configure](#configure) the default encoders and decoders Vapor uses.
|
||||
|
||||
#### Custom
|
||||
|
||||
## Response
|
||||
You can always override Vapor's default decoder and pass in a custom one if you want.
|
||||
|
||||
Let's take a look at how you would create the following HTTP response.
|
||||
```swift
|
||||
let user = try req.content.decode(User.self, using: JSONDecoder())
|
||||
print(user) // Future<User>
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
Let's take a look at how you would create the following HTTP response from your server.
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
|
|
@ -75,12 +105,9 @@ Content-Type: application/json
|
|||
}
|
||||
```
|
||||
|
||||
### Encode Response
|
||||
|
||||
Just like decoding, first create a struct or class that represents the data that you are expecting.
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
import Vapor
|
||||
|
||||
struct User: Content {
|
||||
|
|
@ -89,40 +116,196 @@ struct User: Content {
|
|||
}
|
||||
```
|
||||
|
||||
Then just conform this struct or class to `Content`. Now we are ready to encode that HTTP response.
|
||||
Then just conform this struct or class to `Content`.
|
||||
|
||||
#### Encode
|
||||
|
||||
Now we are ready to encode that HTTP response.
|
||||
|
||||
```swift
|
||||
router.get("user") { req -> User in
|
||||
return User(
|
||||
name: "Vapor User",
|
||||
email: "user@vapor.codes"
|
||||
)
|
||||
return User(name: "Vapor User", email: "user@vapor.codes")
|
||||
}
|
||||
```
|
||||
|
||||
### Other Response Types
|
||||
This will create a default `Response` with `200 OK` status code and minimal headers. You can customize the response using a convenience [`encode(...)`](#fixme) method.
|
||||
|
||||
```swift
|
||||
router.get("user") { req -> Future<Response> in
|
||||
return User(name: "Vapor User", email: "user@vapor.codes")
|
||||
.encode(status: .created)
|
||||
}
|
||||
```
|
||||
|
||||
#### Override Type
|
||||
|
||||
Content will automatically encode as JSON by default. You can always override which content type is used
|
||||
using the `as:` parameter.
|
||||
|
||||
```swift
|
||||
try res.content.encode(user, as: .formURLEncoded)
|
||||
try res.content.encode(user, as: .urlEncodedForm)
|
||||
```
|
||||
|
||||
You can also change the default media type for any class or struct.
|
||||
|
||||
```swift
|
||||
struct User: Content {
|
||||
/// See Content.defaultMediaType
|
||||
static let defaultMediaType: MediaType = .formURLEncoded
|
||||
/// See `Content`.
|
||||
static let defaultContentType: MediaType = .urlEncodedForm
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Configuring Content
|
||||
## Client
|
||||
|
||||
Use `ContentConfig` to register custom encoder/decoders for your application. These custom coders will be used anywhere you do `content.encode`/`content.decode`.
|
||||
Encoding content to HTTP requests sent by [`Client`](#fixme)s is similar to encoding HTTP responses returned by your server.
|
||||
|
||||
### Request
|
||||
|
||||
Let's take a look at how we can encode the following request.
|
||||
|
||||
```http
|
||||
POST /login HTTP/1.1
|
||||
Host: api.vapor.codes
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"email": "user@vapor.codes",
|
||||
"password": "don't look!"
|
||||
}
|
||||
```
|
||||
|
||||
#### Encode
|
||||
|
||||
First, create a struct or class that represents the data you expect.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
struct LoginRequest: Content {
|
||||
var email: String
|
||||
var password: String
|
||||
}
|
||||
```
|
||||
|
||||
Now we are ready to make our request. Let's assume we are making this request inside of a route closure, so we will use the _incoming_ request as our container.
|
||||
|
||||
```swift
|
||||
let loginRequest = LoginRequest(email: "user@vapor.codes", password: "don't look!")
|
||||
let res = try req.client().post("https://api.vapor.codes/login") { loginReq in
|
||||
// encode the loginRequest before sending
|
||||
try loginReq.content.encode(loginRequest)
|
||||
}
|
||||
print(res) // Future<Response>
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
Continuing from our example in the encode section, let's see how we would decode content from the client's response.
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Vapor User",
|
||||
"email": "user@vapor.codes"
|
||||
}
|
||||
```
|
||||
|
||||
First of course we must create a struct or class to represent the data.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
struct User: Content {
|
||||
var name: String
|
||||
var email: String
|
||||
}
|
||||
```
|
||||
|
||||
#### Decode
|
||||
|
||||
Now we are ready to decode the client response.
|
||||
|
||||
```swift
|
||||
let res: Future<Response> // from the Client
|
||||
|
||||
let user = res.flatMap { try $0.content.decode(User.self) }
|
||||
print(user) // Future<User>
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
Let's now take a look at our complete [`Client`](#fixme) request that both encodes and decodes content.
|
||||
|
||||
```swift
|
||||
// Create the LoginRequest data
|
||||
let loginRequest = LoginRequest(email: "user@vapor.codes", password: "don't look!")
|
||||
// POST /login
|
||||
let user = try req.client().post("https://api.vapor.codes/login") { loginReq in
|
||||
// Encode Content before Request is sent
|
||||
return try loginReq.content.encode(loginRequest)
|
||||
}.flatMap { loginRes in
|
||||
// Decode Content after Response is received
|
||||
return try loginRes.content.decode(User.self)
|
||||
}
|
||||
print(user) // Future<User>
|
||||
```
|
||||
|
||||
## Query String
|
||||
|
||||
URL-Encoded Form data can be encoded and decoded from an HTTP request's URI query string just like content. All you need is a class or struct that conforms to [`Content`](#fixme). In these examples, we will be using the following struct.
|
||||
|
||||
```swift
|
||||
struct Flags: Content {
|
||||
var search: String?
|
||||
var isAdmin: Bool?
|
||||
}
|
||||
```
|
||||
|
||||
### Decode
|
||||
|
||||
All [`Request`](#fixme)s have a [`QueryContainer`](#fixme) that you can use to decode the query string.
|
||||
|
||||
```swift
|
||||
let flags = try req.query.decode(Flags.self)
|
||||
print(flags) // Flags
|
||||
```
|
||||
|
||||
### Encode
|
||||
|
||||
You can also encode content. This is useful for encoding query strings when using [`Client`](#fixme).
|
||||
|
||||
```swift
|
||||
let flags: Flags ...
|
||||
try req.query.encode(flags)
|
||||
```
|
||||
|
||||
## JSON
|
||||
|
||||
JSON is a very popular encoding format for APIs and the way in which dates, data, floats, etc are encoded is non-standard. Because of this, Vapor makes it easy to use custom [`JSONDecoder`](#fixme)s when you interact with other APIs.
|
||||
|
||||
```swift
|
||||
// Conforms to Encodable
|
||||
let user: User ...
|
||||
// Encode JSON using custom date encoding strategy
|
||||
try req.content.encode(json: user, using: .custom(dates: .millisecondsSince1970))
|
||||
```
|
||||
|
||||
You can also use this method for decoding.
|
||||
|
||||
```swift
|
||||
// Decode JSON using custom date encoding strategy
|
||||
let user = try req.content.decode(json: User.self, using: .custom(dates: .millisecondsSince1970))
|
||||
```
|
||||
|
||||
If you would like to set a custom JSON encoder or decoder globally, you can do so using [configuration](#configure).
|
||||
|
||||
## Configure
|
||||
|
||||
Use [`ContentConfig`](#fixme) to register custom encoder/decoders for your application. These custom coders will be used anywhere you do `content.encode`/`content.decode`.
|
||||
|
||||
```swift
|
||||
/// Create default content config
|
||||
|
|
|
|||
|
|
@ -26,3 +26,6 @@ let package = Package(
|
|||
|
||||
Use `import Vapor` to access the APIs.
|
||||
|
||||
## API Docs
|
||||
|
||||
The rest of this guide will give you an overview of what is available in the Vapor package. As always, feel free to visit the [API docs](http://api.vapor.codes/vapor/latest/Vapor/index.html) for more in-depth information.
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
# Using WebSockets
|
||||
|
||||
Vapor includes convenience methods for working with the lower level WebSocket [client](../websocket/overview.md#client) and [server](../websocket/overview.md#server).
|
||||
|
||||
## Server
|
||||
|
||||
Vapor's WebSocket server includes the ability to route incoming requests just like its HTTP server.
|
||||
|
||||
When Vapor's main HTTP [`Server`](#fixme) boots it will attempt to create a [`WebSocketServer`](#fixme). If one is registered, it will be added as an HTTP upgrade handler to the server.
|
||||
|
||||
So to create a WebSocket server, all you need to do is register one in [`configure.swift`](../getting-started/structure.md#configureswift).
|
||||
|
||||
```swift
|
||||
// Create a new NIO websocket server
|
||||
let wss = NIOWebSocketServer.default()
|
||||
|
||||
// Add WebSocket upgrade support to GET /echo
|
||||
wss.get("echo") { ws, req in
|
||||
// Add a new on text callback
|
||||
ws.onText { ws, text in
|
||||
// Simply echo any received text
|
||||
ws.send(text)
|
||||
}
|
||||
}
|
||||
|
||||
// Register our server
|
||||
services.register(wss, as: WebSocketServer.self)
|
||||
```
|
||||
|
||||
That's it. Next time you boot your server, you will be able to perform a WebSocket upgrade at `GET /echo`. You can test this using a simple command line tool called [`wsta`](https://github.com/esphen/wsta) available for macOS and Linux.
|
||||
|
||||
```sh
|
||||
$ wsta ws://localhost:8080/echo
|
||||
Connected to ws://localhost:8080/echo
|
||||
hello, world!
|
||||
hello, world!
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
Like Vapor's HTTP router, you can also use routing parameters with your WebSocket server.
|
||||
|
||||
```swift
|
||||
// Add WebSocket upgrade support to GET /chat/:name
|
||||
wss.get("chat", String.parameter) { ws, req in
|
||||
let name = try req.parameters.next(String.self)
|
||||
ws.send("Welcome, \(name)!")
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Now let's test this new route:
|
||||
|
||||
```sh
|
||||
$ wsta ws://localhost:8080/chat/Vapor
|
||||
Connected to ws://localhost:8080/chat/Vapor
|
||||
Welcome, Vapor!
|
||||
```
|
||||
|
||||
## Client
|
||||
|
||||
Vapor also supports connecting to WebSocket servers as a client. The easiest way to connect to a WebSocket server is through the [`webSocket(...)`](#fixme) method on [`Client`](#fixme).
|
||||
|
||||
For this example, we will assume our application connects to a WebSocket server in [`boot.swift`](../getting-started/structure.md#bootswift)
|
||||
|
||||
```swift
|
||||
// connect to echo.websocket.org
|
||||
let done = try app.client().webSocket("ws://echo.websocket.org").flatMap { ws -> Future<Void> in
|
||||
// setup an on text callback that will print the echo
|
||||
ws.onText { ws, text in
|
||||
print("rec: \(text)")
|
||||
// close the websocket connection after we recv the echo
|
||||
ws.close()
|
||||
}
|
||||
|
||||
// when the websocket first connects, send message
|
||||
ws.send("hello, world!")
|
||||
|
||||
// return a future that will complete when the websocket closes
|
||||
return ws.onClose
|
||||
}
|
||||
|
||||
print(done) // Future<Void>
|
||||
|
||||
// wait for the websocket to close
|
||||
try done.wait()
|
||||
```
|
||||
|
|
@ -106,14 +106,16 @@ pages:
|
|||
- 'Overview': 'validation/overview.md'
|
||||
- 'Vapor':
|
||||
- 'Getting Started': 'vapor/getting-started.md'
|
||||
- 'Client': 'vapor/client.md'
|
||||
- 'Content': 'vapor/content.md'
|
||||
- 'WebSocket': 'vapor/websocket.md'
|
||||
- 'WebSocket':
|
||||
- 'Getting Started': 'websocket/getting-started.md'
|
||||
- 'Overview': 'websocket/overview.md'
|
||||
- 'Version (3.0-rc)':
|
||||
- 'Version (3.0)':
|
||||
- '1.5': 'version/1_5.md'
|
||||
- '2.0': 'version/2_0.md'
|
||||
- '3.0-rc': 'version/3_0.md'
|
||||
- '3.0': 'version/3_0.md'
|
||||
- 'Support': 'version/support.md'
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue