mirror of https://github.com/vapor/docs.git
commit
bd6fccfc03
|
|
@ -0,0 +1,3 @@
|
|||
/.couscous
|
||||
.sass-cache
|
||||
.DS_Store
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Contributing to Vapor Docs
|
||||
|
||||
Found a mistake or want to add something? Fork the documentation, fix it, and submit a pull request.
|
||||
|
||||
We'll merge it as soon as we can.
|
||||
|
||||
Thanks!
|
||||
0
getting-started/install-swift-3-macos.md → 1.0/getting-started/install-swift-3-macos.md
Normal file → Executable file
0
getting-started/install-swift-3-macos.md → 1.0/getting-started/install-swift-3-macos.md
Normal file → Executable file
0
getting-started/install-swift-3-ubuntu.md → 1.0/getting-started/install-swift-3-ubuntu.md
Normal file → Executable file
0
getting-started/install-swift-3-ubuntu.md → 1.0/getting-started/install-swift-3-ubuntu.md
Normal file → Executable file
0
getting-started/install-toolbox.md → 1.0/getting-started/install-toolbox.md
Normal file → Executable file
0
getting-started/install-toolbox.md → 1.0/getting-started/install-toolbox.md
Normal file → Executable file
|
Before Width: | Height: | Size: 550 B After Width: | Height: | Size: 550 B |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 240 KiB |
0
template/scripts/highlight.pack.js → 1.0/template/scripts/highlight.pack.js
Normal file → Executable file
0
template/scripts/highlight.pack.js → 1.0/template/scripts/highlight.pack.js
Normal file → Executable file
|
|
@ -0,0 +1,88 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Middleware
|
||||
|
||||
`AuthMiddleware` is at the core of adding authorization to your project. It is responsible for initializing dependencies, checking credentials, and handling sessions.
|
||||
|
||||
## Create
|
||||
|
||||
Once you have something that conforms to `Auth.User`, you can create an `AuthMiddleware`. Let's assume we have a class `User` that conforms to `Auth.User`.
|
||||
|
||||
> Note: You may need to include a module name before `User` to disambiguate.
|
||||
|
||||
```swift
|
||||
import Auth
|
||||
|
||||
let auth = AuthMiddleware(user: User.self)
|
||||
```
|
||||
|
||||
Creating the `AuthMiddleware` can be that simple, or you can customize it with additional initialization arguments.
|
||||
|
||||
### Cookie
|
||||
|
||||
Customize the type of cookie the `AuthMiddleware` creates by passing a `CookieFactory`.
|
||||
|
||||
```swift
|
||||
let auth = AuthMiddleware(user: User.self) { value in
|
||||
return Cookie(
|
||||
name: "vapor-auth",
|
||||
value: value,
|
||||
expires: Date().addingTimeInterval(60 * 60 * 5), // 5 hours
|
||||
secure: true,
|
||||
httpOnly: true
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Cache
|
||||
|
||||
A custom `CacheProtocol` can be passed as well. The `MemoryCache` used by default is not persisted between server restarts and does not allow for sharing between multiple running instances.
|
||||
|
||||
```swift
|
||||
import VaporRedis
|
||||
|
||||
let redis = RedisCache()
|
||||
let auth = AuthMiddleware(user: User.self, cache: redis)
|
||||
```
|
||||
|
||||
> Note: This example uses the [redis-provider](https://github.com/vapor/redis-provider) package.
|
||||
|
||||
### Realm
|
||||
|
||||
To customize the `AuthMiddleware` even further, you can use a custom `Realm`. The `Realm` takes the responsibility of registering and authenticating the user away from the `Auth.User` protocol.
|
||||
|
||||
```swift
|
||||
let facebook = FacebookRealm()
|
||||
let auth = AuthMiddleware(user: User.self, realm: facebook)
|
||||
```
|
||||
|
||||
> Note: `FacebookRealm` is hypothetical.
|
||||
|
||||
## Add
|
||||
|
||||
Once you've created the `AuthMiddleware`, you can add it to the `Droplet`.
|
||||
|
||||
```swift
|
||||
let drop = Droplet()
|
||||
drop.middleware.append(auth)
|
||||
```
|
||||
|
||||
> Note: If you'd like to enable or disable the middleware based on config files, check out [middleware](../vapor/middleware.md).
|
||||
|
||||
### Sharing Cache
|
||||
|
||||
If you'd like the `Droplet` and the `AuthMiddleware` to share the same `CacheProtocol`, pass the same instance to both.
|
||||
|
||||
```
|
||||
import Vapor
|
||||
import VaporRedis
|
||||
|
||||
let redis = RedisCache()
|
||||
let auth = AuthMiddleware(user: User.self, cache: redis)
|
||||
|
||||
let drop = Droplet()
|
||||
|
||||
drop.cache = redis
|
||||
drop.middleware.append(auth)
|
||||
```
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Protect
|
||||
|
||||
Once the `AuthMiddleware` has been enabled, you can use `ProtectMiddleware` to prevent certain routes from being accessed without authorization.
|
||||
|
||||
## Create
|
||||
|
||||
To create a `ProtectMiddleware`, you must give it the error to throw in case authorization fails.
|
||||
|
||||
```swift
|
||||
let error = Abort.custom(status: .forbidden, message: "Invalid credentials.")
|
||||
let protect = ProtectMiddleware(error: error)
|
||||
```
|
||||
|
||||
Here we pass it a simple 403 response.
|
||||
|
||||
## Route Group
|
||||
|
||||
Once the middleware has been created, you can add it to route groups. Learn more about middleware and routing in [route groups](../routing/group.md).
|
||||
|
||||
```
|
||||
drop.grouped(protect).group("secure") { secure in
|
||||
secure.get("about") { req in
|
||||
let user = try req.user()
|
||||
return user
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Visiting `GET /secure/about` will return the authorized user, or an error if no user is authorized.
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Request
|
||||
|
||||
The `auth` property on `Request` lets you authenticate users and also provides some convenience methods for accessing common authorization headers.
|
||||
|
||||
## Authorization
|
||||
|
||||
The authorization header is a great place to send credentials from a client.
|
||||
|
||||
```
|
||||
Authorization: xxxxxxxxxx
|
||||
```
|
||||
|
||||
You can access the authorization header through `req.auth.header`. Two common patterns are basic and bearer.
|
||||
|
||||
### Basic
|
||||
|
||||
Basic authorization consists of a username and password concatenated into a string and base64 encoded.
|
||||
|
||||
```
|
||||
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l
|
||||
```
|
||||
|
||||
This is what an example header looks like. You can read more about basic auth on [wikipedia](https://en.wikipedia.org/wiki/Basic_access_authentication).
|
||||
|
||||
Below is how you access this header using `req.auth`.
|
||||
|
||||
```swift
|
||||
guard let credentials = req.auth.header?.basic else {
|
||||
throw Abort.badRequest
|
||||
}
|
||||
```
|
||||
|
||||
The basic header returns an `APIKey` credential.
|
||||
|
||||
```
|
||||
class APIKey: Credentials {
|
||||
let id: String
|
||||
let secret: String
|
||||
}
|
||||
```
|
||||
|
||||
### Bearer
|
||||
|
||||
Another common method is bearer which consists of a single API key.
|
||||
|
||||
```
|
||||
Authorization: Bearer apikey123
|
||||
```
|
||||
|
||||
It is accessed similarly to the basic header and returns an `AccessToken` credential.
|
||||
|
||||
```
|
||||
class AccessToken: Credentials {
|
||||
let string: String
|
||||
}
|
||||
```
|
||||
|
||||
### Raw
|
||||
|
||||
To access the raw authorization header, use `req.auth.header?.header`.
|
||||
|
||||
## Credentials
|
||||
|
||||
Both Basic and Bearer return something that conforms to `Credentials`. You can always create a custom `Credentials` object for authentication by conforming your own class to `Credentials` or by manually creating an `APIKey`, `AccessToken`, or `Identifier`.
|
||||
|
||||
```swift
|
||||
let key = AccessToken(string: "apikey123")
|
||||
```
|
||||
|
||||
### Input
|
||||
|
||||
You can also create credentials from form or JSON data.
|
||||
|
||||
```swift
|
||||
guard
|
||||
let username = req.data["username"]?.string,
|
||||
let password = req.data["password"]?.string
|
||||
else {
|
||||
throw Abort.badRequest
|
||||
}
|
||||
|
||||
let key = APIKey(id: username, secret: password)
|
||||
```
|
||||
|
||||
## Login
|
||||
|
||||
Once you have some object that conforms to `Credentials`, you can try to login the user.
|
||||
|
||||
```swift
|
||||
try req.auth.login(credentials)
|
||||
```
|
||||
|
||||
If this call succeeds, the user is logged in and a session has been started. They will stay logged in as long as their cookie is valid.
|
||||
|
||||
### Authenticate
|
||||
|
||||
Logging in calls the `authenticate` method on `Auth.User` model you supplied to the `AuthMiddleware`. Make sure you add support for all the credential types you may want to use.
|
||||
|
||||
> Note: If you used a custom Realm, it will be called instead.
|
||||
|
||||
### Identifier
|
||||
|
||||
Another important credential type is the `Identifier` type. This is used by Vapor when fetching the `User` object from the `vapor-auth` cookie. It is also a convenient way to log a user in manually.
|
||||
|
||||
```swift
|
||||
static func authenticate(credentials: Credentials) throws -> Auth.User {
|
||||
switch credentials {
|
||||
...
|
||||
case let id as Identifier:
|
||||
guard let user = try User.find(id.id) else {
|
||||
throw Abort.custom(status: .badRequest, message: "Invalid identifier.")
|
||||
}
|
||||
|
||||
return user
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Adding the `Identifier` case for `Credentials` is easy, just look up the user by the identifier.
|
||||
|
||||
```swift
|
||||
let id = Identifier(id: 42)
|
||||
try req.auth.login(id)
|
||||
```
|
||||
|
||||
Now you can manually log users in with just their identifiers.
|
||||
|
||||
### Ephemeral
|
||||
|
||||
If you just want to log the user in for a single request, disable persistance.
|
||||
|
||||
```swift
|
||||
req.auth.login(credentials, persist: false)
|
||||
```
|
||||
|
||||
> Note: Supporting `Identifier` credentials is required for persisted authentication to work properly.
|
||||
|
||||
## User
|
||||
|
||||
By default, `request.auth.user()` returns the authorized `Auth.User`. This will need to be casted to your internal `User` type for use.
|
||||
|
||||
Adding a convenience method on `Request` is a great way to simplify this.
|
||||
|
||||
```swift
|
||||
extension Request {
|
||||
func user() throws -> User {
|
||||
guard let user = try auth.user() as? User else {
|
||||
throw Abort.custom(status: .badRequest, message: "Invalid user type.")
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now you can access your `User` type with `try req.user()`.
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Auth
|
||||
|
||||
Authentication and authorization is focused around the `Auth.User` protocol. Authentication is analagous to asking: "Who is this?", while authorization is analagous to asking: "What can they do?". Vapor includes an extensible authentication system that you can use as a base for more sophisticated authorization.
|
||||
|
||||
> Note: An [auth-example](https://github.com/vapor/auth-example) project is available on GitHub.
|
||||
|
||||
## User Protocol
|
||||
|
||||
Any type can conform to the `Auth.User` protocol, but they are commonly added onto Fluent `Model`s.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
import Auth
|
||||
|
||||
final class User: Model {
|
||||
var id: Node?
|
||||
var name: String
|
||||
|
||||
...
|
||||
}
|
||||
|
||||
extension User: Auth.User {
|
||||
static func authenticate(credentials: Credentials) throws -> Auth.User {
|
||||
|
||||
}
|
||||
|
||||
static func register(credentials: Credentials) throws -> Auth.User {
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here is an example `User` class with the `Auth.User` protocol requirements stubbed. Note that the name of our class and the protocol are the same. This is why we use the `Auth.` prefix to differentiate the protocol from the `Auth` module from our `User` class.
|
||||
|
||||
### Authenticate
|
||||
|
||||
A user is authenticated when a set of credentials is passed to the static `authenticate` method and the matching user is returned.
|
||||
|
||||
#### Credentials
|
||||
|
||||
```swift
|
||||
protocol Credentials { }
|
||||
```
|
||||
|
||||
The credentials protocol is an empty protocol that any type can conform to. This gives great flexibility to your authentication model, but also requires that you properly handle the case of unsupported credential types.
|
||||
|
||||
#### Access Token
|
||||
|
||||
One of the simplest credential types included is `AccessToken`. It carries a `String` based token that will be used to authenticate the user.
|
||||
|
||||
Let's look at how we might support the access token type.
|
||||
|
||||
```swift
|
||||
static func authenticate(credentials: Credentials) throws -> Auth.User {
|
||||
switch credentials {
|
||||
case let accessToken as AccessToken:
|
||||
guard let user = try User.query().filter("access_token", accessToken.string).first() else {
|
||||
throw Abort.custom(status: .forbidden, message: "Invalid access token.")
|
||||
}
|
||||
|
||||
return user
|
||||
default:
|
||||
let type = type(of: credentials)
|
||||
throw Abort.custom(status: .forbidden, message: "Unsupported credential type: \(type).")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The first step is to cast the credentials to the type we want to support--in this case, `AccessToken`. If we do not have an access token, we will inform the client that the credentials are invalid.
|
||||
|
||||
Once we have the access token, we will use it to query the `User` model for an entry with a matching access token. This is assuming the `users` table or collection has the access tokens stored on it. You may opt to store them somewhere else.
|
||||
|
||||
Once we have found the user associated with the supplied access token, we simply return it.
|
||||
|
||||
#### Identifier
|
||||
|
||||
Vapor uses the `Identifier` credential type internally to lookup users from sessions. You can read more in the [Request](request.md) section.
|
||||
|
||||
### Register
|
||||
|
||||
Similar to the authenticate method, the register method takes credentials. But instead of fetching the user from the data store, it provides a convenient way to create the user. You are not required to register your users through this method.
|
||||
|
||||
## Example
|
||||
|
||||
Here is an example of a User that supports multiple credentials.
|
||||
|
||||
```swift
|
||||
extension User: Auth.User {
|
||||
static func authenticate(credentials: Credentials) throws -> Auth.User {
|
||||
let user: User?
|
||||
|
||||
switch credentials {
|
||||
case let id as Identifier:
|
||||
user = try User.find(id.id)
|
||||
case let accessToken as AccessToken:
|
||||
user = try User.query().filter("access_token", accessToken.string).first()
|
||||
case let apiKey as APIKey:
|
||||
user = try User.query().filter("email", apiKey.id).filter("password", apiKey.secret).first()
|
||||
default:
|
||||
throw Abort.custom(status: .badRequest, message: "Invalid credentials.")
|
||||
}
|
||||
|
||||
guard let u = user else {
|
||||
throw Abort.custom(status: .badRequest, message: "User not found.")
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
static func register(credentials: Credentials) throws -> Auth.User {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Note: Try not to store passwords. If you must, hash and salt them.
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Deploying with Nginx
|
||||
|
||||
Nginx is an extremely fast, battle tested, and easy-to-configure HTTP server and proxy. While Vapor supports directly serving HTTP requests with or without TLS, proxying behind Nginx can provide increased performance, security, and ease-of-use.
|
||||
|
||||
> Note: We recommend proxying Vapor HTTP servers behind Nginx.
|
||||
|
||||
## Overview
|
||||
|
||||
What does it mean to proxy an HTTP server? In short, a proxy acts as a middleman between the public internet and your HTTP server. Requests come to the proxy and then it sends them to Vapor.
|
||||
|
||||
An important feature of this middleman proxy is that it can alter or even redirect the requests. For instance, the proxy can require that the client use TLS (https), rate limit requests, or even serve public files without talking to your Vapor application.
|
||||
|
||||

|
||||
|
||||
### More Detail
|
||||
|
||||
The default port for receiving HTTP requests is port `80` (and `443` for HTTPS). When you bind a Vapor server to port `80`, it will directly receive and respond to the HTTP requests that come to your server. When adding a proxy like Nginx, you bind Vapor to an internal port, like port `8080`.
|
||||
|
||||
> Note: Ports greater than 1024 do not require `sudo` to bind.
|
||||
|
||||
When Vapor is bound to a port besides `80` or `443`, it will not be accessible to the outside internet. You then bind Nginx to port `80` and configure it to route requests to your Vapor server bound at port `8080` (or whichever port you've chosen).
|
||||
|
||||
And that's it. If Nginx is properly configured, you will see your Vapor app responding to requests on port `80`. Nginx proxies the requests and responses invisibly.
|
||||
|
||||
## Install Nginx
|
||||
|
||||
The first step is installing Nginx. One of the great parts of Nginx is the tremendous amount of community resources and documentation surrounding it. Because of this, we will not go into great detail here about installing Nginx as there is almost definitely a tutorial for your specific platform, OS, and provider.
|
||||
|
||||
Tutorials:
|
||||
- [How To Install Nginx on Ubuntu 14.04 LTS](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-14-04-lts)
|
||||
- [How To Install Nginx on Ubuntu 16.04](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-16-04)
|
||||
- [How to Deploy Nginx on Heroku](https://blog.codeship.com/how-to-deploy-nginx-on-heroku/)
|
||||
- [How To Run Nginx in a Docker Container on Ubuntu 14.04](https://www.digitalocean.com/community/tutorials/how-to-run-nginx-in-a-docker-container-on-ubuntu-14-04)
|
||||
|
||||
|
||||
### APT
|
||||
|
||||
Nginx can be installed through APT.
|
||||
|
||||
```sh
|
||||
sudo apt-get update
|
||||
sudo apt-get install nginx
|
||||
```
|
||||
|
||||
Check whether Nginx was installed correctly by visiting your server's IP address in a browser
|
||||
|
||||
```sh
|
||||
http://server_domain_name_or_IP
|
||||
```
|
||||
|
||||
### Service
|
||||
|
||||
Ther service an be started or stopped.
|
||||
|
||||
```sh
|
||||
sudo service nginx stop
|
||||
sudo service nginx start
|
||||
sudo service nginx restart
|
||||
```
|
||||
|
||||
## Booting Vapor
|
||||
|
||||
Nginx can be started an stopped with the `sudo service nginx ...` commands. You will need something similar to start and stop your Vapor server.
|
||||
|
||||
There are many ways to do this, and they depend on which platform you are deploying to. Check out the [Supervisor](supervisor.md) instructions to add commands for starting and stopping your Vapor app.
|
||||
|
||||
## Configure Proxy
|
||||
|
||||
The configuration files for enabled sites can be found in `/etc/nginx/sites-enabled/`.
|
||||
|
||||
Create a new file or copy the example template from `/etc/nginx/sites-available/` to get started.
|
||||
|
||||
Here is an example configuration file for a Vapor project called `Hello` in the home directory.
|
||||
|
||||
```sh
|
||||
server {
|
||||
server_name hello.com;
|
||||
listen 80;
|
||||
|
||||
root /home/vapor/Hello/Public/;
|
||||
|
||||
location @proxy {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_pass_header Server;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass_header Server;
|
||||
proxy_connect_timeout 3s;
|
||||
proxy_read_timeout 10s;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This configuration file assumes the `Hello` project binds to port `8080` when started in production mode.
|
||||
|
||||
### Serving Files
|
||||
|
||||
Nginx can also serve public files without asking your Vapor app. This can improve performance by freeing up the Vapor process for other tasks under heavy load.
|
||||
|
||||
```sh
|
||||
server {
|
||||
...
|
||||
|
||||
# Serve all public/static files via nginx and then fallback to Vapor for the rest
|
||||
try_files $uri @proxy;
|
||||
|
||||
location @proxy {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### TLS
|
||||
|
||||
Adding TLS is relatively straightforward as long as the certificates have been properly generated. To generate TLS certificates for free, check out [Let's Encrypt](https://letsencrypt.org/getting-started/).
|
||||
|
||||
```sh
|
||||
server {
|
||||
...
|
||||
|
||||
listen 443 ssl;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/hello.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/hello.com/privkey.pem;
|
||||
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_dhparam /etc/ssl/certs/dhparam.pem;
|
||||
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:SSL:50m;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
add_header Strict-Transport-Security max-age=15768000;
|
||||
|
||||
...
|
||||
|
||||
location @proxy {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The configuration above are the relatively strict settings for TLS with Nginx. Some of the settings here are not required, but enhance security.
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Supervisor
|
||||
|
||||
[Supervisor](http://supervisord.org) is a process control system that makes it easy to start, stop, and restart your Vapor app.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
sudo apt-get update
|
||||
sudo apt-get install supervisor
|
||||
```
|
||||
|
||||
## Configure
|
||||
|
||||
Each Vapor app on your server should have its own configuration file. For an example `Hello` project, the configuration file would be located at `/etc/supervisor/conf.d/hello.conf`
|
||||
|
||||
```sh
|
||||
[program:hello]
|
||||
command=/home/vapor/hello/.build/release/App serve --env=production
|
||||
directory=/home/vapor/hello/
|
||||
user=www-data
|
||||
stdout_logfile=/var/log/supervisor/%(program_name)-stdout.log
|
||||
stderr_logfile=/var/log/supervisor/%(program_name)-stderr.log
|
||||
```
|
||||
|
||||
As specified in our configuration file the `Hello` project is located in the home folder for the user `vapor`. Make sure `directory` points to the root directory of your project where the `Config/` folder is.
|
||||
|
||||
The `--env=production` flag will disable verbose logging and prioritize the `Config/production` sub folder of your configuration files.
|
||||
|
||||
### Environment
|
||||
|
||||
You can export variables to your Vapor app with supervisor.
|
||||
|
||||
```sh
|
||||
environment=PORT=8123
|
||||
```
|
||||
|
||||
Exported variables can be used in Vapor's configuration files with the `$` prefix.
|
||||
|
||||
`Config/production/servers.json `
|
||||
```json
|
||||
{
|
||||
"my-server": {
|
||||
"port": "$PORT"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The above config file will start a server named `my-server` on the port number exported by supervisor. This is a great way to control how Vapor starts from the supervisor config scripts. Feel free to name the server whatever you like.
|
||||
|
||||
## Start
|
||||
|
||||
You can now load and start your app.
|
||||
|
||||
```sh
|
||||
supervisorctl reread
|
||||
supervisorctl add hello
|
||||
supervisorctl start hello
|
||||
```
|
||||
|
||||
> Note: The `add` command may have already started your app.
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Driver
|
||||
|
||||
Drivers are what power Fluent under the hood. Fluent comes with a memory driver by default and there are many providers for databases like MySQL, SQLite, Mongo, PostgreSQL, and more available as providers.
|
||||
|
||||

|
||||
|
||||
This graphic shows the relation between Drivers and Providers using MySQL as an example. This distinction allows Fluent to be used independently from Vapor.
|
||||
|
||||
If you want to use Fluent without Vapor, you will import Drivers into your package. If you are using Vapor, you will import Providers.
|
||||
|
||||
Search GitHub for:
|
||||
- [Fluent Drivers](https://github.com/vapor?utf8=✓&q=-driver)
|
||||
- [Vapor Providers](https://github.com/vapor?utf8=✓&q=-provider)
|
||||
|
||||
Not all drivers have providers yet, and not all drivers or providers are up to date with the latest Vapor 1.0. This is a great way to contribute!
|
||||
|
||||
## Creating a Driver
|
||||
|
||||
Fluent is a powerful, database agnostic package for persisting your models. It was designed from the beginning to work with both SQL and NoSQL databases alike.
|
||||
|
||||
Any database that conforms to `Fluent.Driver` will be able to power the models in Fluent and Vapor.
|
||||
|
||||
The protocol itself is fairly simple:
|
||||
|
||||
```swift
|
||||
public protocol Driver {
|
||||
var idKey: String { get }
|
||||
func query<T: Entity>(_ query: Query<T>) throws -> Node
|
||||
func schema(_ schema: Schema) throws
|
||||
func raw(_ raw: String, _ values: [Node]) throws -> Node
|
||||
}
|
||||
```
|
||||
|
||||
### ID Key
|
||||
|
||||
The ID key will be used to power functionality like `User.find()`. In SQL, this is often `id`. In MongoDB, it is `_id`.
|
||||
|
||||
### Query
|
||||
|
||||
This method will be called for every query made by Fluent. It is the drivers job to properly understand all of the properties on `Query` and return the desired rows, document, or other data as represented by `Node`.
|
||||
|
||||
### Schema
|
||||
|
||||
The schema method will be called before the database is expected to accept queries for a schema. For some NoSQL databases like MongoDB, this can be ignored. For SQL, this is where `CREATE` and other such commands should be called according to `Schema`.
|
||||
|
||||
### Raw
|
||||
|
||||
This is an optional method that can be used by any Fluent driver that accepts string queries. If your database does not accept such queries, an error can be thrown.
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Model
|
||||
|
||||
`Model` is the base protocol for any of your application's models, especially those you want to persist.
|
||||
|
||||
> `Model` is only available in Vapor, the Fluent equivalent is `Entity`
|
||||
|
||||
## Example
|
||||
|
||||
Let's create a simple `User` model.
|
||||
|
||||
```swift
|
||||
final class User {
|
||||
var name: String
|
||||
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The first step to conforming to `Model` is to import Vapor and Fluent.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
import Fluent
|
||||
```
|
||||
|
||||
Then add the conformance to your class.
|
||||
|
||||
```swift
|
||||
final class User: Model {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The compiler will inform you that some methods need to be implemented to conform.
|
||||
|
||||
### ID
|
||||
|
||||
The first required property is an identifier. This property will contain the identifier when the model is fetched from the database. If it is `nil`, it will be set when the model is saved.
|
||||
|
||||
```swift
|
||||
final class User: Model {
|
||||
var id: Node?
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Node Initializable
|
||||
|
||||
The next requirement is a way to create the model from the persisted data. Model uses `NodeInitializable` to achieve this.
|
||||
|
||||
```swift
|
||||
final class User: Model {
|
||||
init(node: Node, in context: Context) throws {
|
||||
id = try node.extract("id")
|
||||
name = try node.extract("name")
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The keys `id` and `name` are what we expect the columns or fields in the database to be named. The `extract` call is marked with a `try` because it will throw an error if the value is not present or is the wrong type.
|
||||
|
||||
### Node Representable
|
||||
|
||||
Now that we have covered initializing the model, we need to show how to save it back into the database. Model uses `NodeRepresentable` to achieve this.
|
||||
|
||||
```swift
|
||||
final class User: Model {
|
||||
func makeNode(context: Context) throws -> Node {
|
||||
return try Node(node: [
|
||||
"id": id,
|
||||
"name": name
|
||||
])
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
When a `User` is saved, the `makeNode()` method will be called and the resulting `Node` will be saved to the database. The keys `id` and `name` are what we expect the columns or fields in the database to be named.
|
||||
|
||||
> In most of the cases you do not need to be concerned about `context` argument of the `makeNode(context:)` method. It’s a part of the protocol that allows extensibility in more advanced or specific scenarios.
|
||||
|
||||
## Preparations
|
||||
|
||||
Some databases, like MySQL, need to be prepared for a new schema. In MySQL, this means creating a new table. Preparations are also equatable to migrations, as they can be used to alter schemas after they've already been created.
|
||||
|
||||
### Prepare
|
||||
|
||||
Let's assume we are using a SQL database. To prepare the database for our `User` class, we need to create a table. If you are using a database like Mongo, you can leave this method unimplemented.
|
||||
|
||||
```swift
|
||||
final class User {
|
||||
static func prepare(_ database: Database) throws {
|
||||
try database.create("users") { users in
|
||||
users.id()
|
||||
users.string("name")
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Here we create a table named `users` that has an identifier field and a string field with the key `name`. This matches both our `init(node: Node)` and `makeNode() -> Node` methods.
|
||||
|
||||
### Revert
|
||||
|
||||
An optional preparation reversion can be created. This will be run if `vapor run prepare --revert` is called.
|
||||
|
||||
```swift
|
||||
final class User {
|
||||
static func revert(_ database: Database) throws {
|
||||
try database.delete("users")
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Here we are deleting the table named `users`.
|
||||
|
||||
### Preparations as Migrations
|
||||
|
||||
If you want to add a field to your table after you've already created the initial schema, you can create a struct or class that conforms to `Preparation` like so:
|
||||
|
||||
```swift
|
||||
|
||||
struct AddFooToBar: Preparation {
|
||||
static func prepare(_ database: Database) throws {
|
||||
try database.modify("bars", closure: { bar in
|
||||
bar.string("foo", length: 150, optional: false, unique: false, default: nil)
|
||||
})
|
||||
}
|
||||
|
||||
static func revert(_ database: Database) throws {
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, in your Droplet setup, add this line: `drop.preparations.append(AddFooToBar.self)`
|
||||
|
||||
### Droplet
|
||||
|
||||
To run these prepations when the applications boots, you must add the Model to the `Droplet`.
|
||||
|
||||
```swift
|
||||
let drop = Droplet()
|
||||
|
||||
drop.preparations.append(User.self)
|
||||
```
|
||||
|
||||
> Note: Preparations must be appended before the Droplet is run.
|
||||
|
||||
## Full Model
|
||||
|
||||
This is what our final `User` model looks like:
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
import Fluent
|
||||
|
||||
final class User: Model {
|
||||
var id: Node?
|
||||
var name: String
|
||||
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
|
||||
init(node: Node, in context: Context) throws {
|
||||
id = try node.extract("id")
|
||||
name = try node.extract("name")
|
||||
}
|
||||
|
||||
func makeNode(context: Context) throws -> Node {
|
||||
return try Node(node: [
|
||||
"id": id,
|
||||
"name": name
|
||||
])
|
||||
}
|
||||
|
||||
static func prepare(_ database: Database) throws {
|
||||
try database.create("users") { users in
|
||||
users.id()
|
||||
users.string("name")
|
||||
}
|
||||
}
|
||||
|
||||
static func revert(_ database: Database) throws {
|
||||
try database.delete("users")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Interacting
|
||||
|
||||
Now that `User` conforms to `Model`, it has a plethora of new methods like `find()`, `query()`, `makeJSON()` and more.
|
||||
|
||||
### Fetch
|
||||
|
||||
Models can be fetched by their database identifier.
|
||||
|
||||
```swift
|
||||
let user = try User.find(42)
|
||||
```
|
||||
|
||||
### Save
|
||||
|
||||
Newly created models can be saved to the database.
|
||||
|
||||
```swift
|
||||
var user = User(name: "Vapor")
|
||||
try user.save()
|
||||
print(user.id) // prints the new id
|
||||
```
|
||||
|
||||
### Delete
|
||||
|
||||
Persisted models with identifiers can be deleted.
|
||||
|
||||
```swift
|
||||
try user.delete()
|
||||
```
|
||||
|
||||
## Model vs. Entity
|
||||
|
||||
Model has a couple of extra conformances that a pure Fluent entity doesn't have.
|
||||
|
||||
```swift
|
||||
public protocol Model: Entity, JSONRepresentable, StringInitializable, ResponseRepresentable {}
|
||||
```
|
||||
|
||||
As can be seen in the protocol, Vapor models can automatically convert to `JSON`, `Response`, and even be used in type-safe routing.
|
||||
|
||||
## Options
|
||||
|
||||
Change the table/collection name
|
||||
```swift
|
||||
static var entity = "new_name"
|
||||
```
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Query
|
||||
|
||||
The `Query` class is what powers every interaction with Fluent. Whether you're fetching a model with `.find()` or saving to the database, there is a `Query` involved somewhere.
|
||||
|
||||
## Querying Models
|
||||
|
||||
Every type that conforms to [Model](model.md) gets a static `.query()` method.
|
||||
|
||||
```swift
|
||||
let query = try User.query()
|
||||
```
|
||||
|
||||
This is how you can create a `Query<User>`.
|
||||
|
||||
### No Database
|
||||
|
||||
The `.query()` method is marked with `try` because it can throw an error if the Model has not had its static database property set.
|
||||
|
||||
```swift
|
||||
User.database = drop.database
|
||||
```
|
||||
|
||||
This property is set automatically when you pass the Model as a preparation.
|
||||
|
||||
## Filter
|
||||
|
||||
The most common types of queries involve filtering data.
|
||||
|
||||
```swift
|
||||
let smithsQuery = try User.query().filter("last_name", "Smith")
|
||||
```
|
||||
|
||||
Here is the short hand for adding an `equals` filter to the query. As you can see, queries can be chained together.
|
||||
|
||||
In additional to `equals`, there are many other types of `Filter.Comparison`.
|
||||
|
||||
```swift
|
||||
let over21 = try User.query().filter("age", .greaterThanOrEquals, 21)
|
||||
```
|
||||
|
||||
### Scope
|
||||
|
||||
Filters can also be run on sets.
|
||||
|
||||
```swift
|
||||
let coolPets = try Pet.query().filter("type", .in, ["Dog", "Ferret"])
|
||||
```
|
||||
|
||||
Here only Pets of type Dog _or_ Ferret are returned. The opposite works for `notIn`.
|
||||
|
||||
|
||||
### Contains
|
||||
|
||||
Partially matching filters can also be applied.
|
||||
|
||||
```swift
|
||||
let statesWithNew = try State.query().filter("name", contains: "New")
|
||||
```
|
||||
|
||||
## Retrieving
|
||||
|
||||
There are two methods for running a query.
|
||||
|
||||
### All
|
||||
|
||||
All of the matching entities can be fetched. This returns an array of `[Model]`, in this case users.
|
||||
|
||||
```swift
|
||||
let usersOver21 = try User.query().filter("age", .greaterThanOrEquals, 21).all()
|
||||
```
|
||||
|
||||
### First
|
||||
|
||||
The first matching entity can be fetched. This returns an optional `Model?`, in this case a user.
|
||||
|
||||
```swift
|
||||
let firstSmith = try User.query().filter("last_name", "Smith").first()
|
||||
```
|
||||
|
||||
## Union
|
||||
|
||||
Other Models can be joined onto your query to assist in filtering. The results must still be either `[Model]` or `Model?` for whichever type created the query.
|
||||
|
||||
```swift
|
||||
let usersWithCoolPets = try User.query()
|
||||
.union(Pet.self)
|
||||
.filter(Pet.self, "type", .in, ["Dog", "Ferret"])
|
||||
```
|
||||
|
||||
Here the `User` collection is unioned to the `Pet` collection. Only `User`s who have a dog or a ferret will be returned.
|
||||
|
||||
### Keys
|
||||
|
||||
The `union` method assumes that the querying table has a foreign key identifier to the joining table.
|
||||
|
||||
The above example with users and pets assumes the following schema.
|
||||
|
||||
```
|
||||
users
|
||||
- id
|
||||
pets
|
||||
- id
|
||||
- user_id
|
||||
```
|
||||
|
||||
Custom foreign keys can be provided through overloads to `union`.
|
||||
|
||||
## Raw Queries
|
||||
|
||||
Since Fluent is focused on interacting with models, each Query requires a model type. If you want to do raw database queries that aren't based on a model, you should use the underlying Fluent Driver to do so.
|
||||
|
||||
```swift
|
||||
if let mysql = drop.database?.driver as? MySQLDriver {
|
||||
let version = try mysql.raw("SELECT @@version")
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Relation
|
||||
|
||||
Relations allow foreign key based connections between database entities. This is common in SQL-based databases, but can also be used with NoSQL.
|
||||
|
||||
Fluent's relations are named as follows:
|
||||
- Parent (BelongsTo)
|
||||
- Children (HasMany, HasOne)
|
||||
- Siblings (ManyToMany, BelongsToMany)
|
||||
|
||||
## Parent
|
||||
|
||||
The parent relation should be called on an entity that has a foreign key to another entity. For example, assume the following schema:
|
||||
|
||||
```
|
||||
pets
|
||||
- id
|
||||
- owner_id
|
||||
- name
|
||||
- type
|
||||
|
||||
owner
|
||||
- id
|
||||
- name
|
||||
```
|
||||
|
||||
Here each pet can have one owner. To access that owner from the pet, call `.parent()`.
|
||||
|
||||
```swift
|
||||
let pet: Pet = ...
|
||||
let owner = try pet.parent(pet.ownerId, Owner.self).get()
|
||||
```
|
||||
|
||||
The parent method requires the foreign key for the parent as well as the type.
|
||||
|
||||
### Convenience
|
||||
|
||||
To make requesting a parent easier, a method can be added to the model.
|
||||
|
||||
```swift
|
||||
extension Pet {
|
||||
func owner() throws -> Parent<Owner> {
|
||||
return try parent(ownerId)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Since we are extending `Pet`, we no longer need to use `pet.` before the `ownerId`. Furthermore, because we are providing the type information about `Owner` in the return type of the method, we no longer need to pass that as an argument.
|
||||
|
||||
The `Parent<T>` type is a queryable object, meaning you could `delete()` the parent, `filter()`, etc.
|
||||
|
||||
```swift
|
||||
try pet.owner().delete()
|
||||
```
|
||||
|
||||
To fetch the parent, you must call `get()`.
|
||||
|
||||
```swift
|
||||
let owner = try pet.owner().get()
|
||||
```
|
||||
|
||||
## Children
|
||||
|
||||
`Children` are the opposite side of the `Parent` relationship. Assuming the schema from the previous example, the pets could be retrieved from an owner like so:
|
||||
|
||||
```swift
|
||||
let owner: Owner = ...
|
||||
let pets = owner.children(Pet.self).all()
|
||||
```
|
||||
|
||||
Here only the type of child is required.
|
||||
|
||||
### Convenience
|
||||
|
||||
Similarly to `Parent`, convenience methods can be added for children.
|
||||
|
||||
```swift
|
||||
extension Owner {
|
||||
func pets() throws -> Children<Pet> {
|
||||
return try children()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Since the type information is clear from the return value, `Pet.self` does not need to be passed to `children()`.
|
||||
|
||||
The `Children<T>` is also a queryable object like `Parent<T>`. You can call `first()`, `all()`, `filter()`, etc.
|
||||
|
||||
```swift
|
||||
let coolPets = try owner.pets().filter("type", .in, ["Dog", "Ferret"]).all()
|
||||
```
|
||||
|
||||
## Siblings
|
||||
|
||||
`Siblings` work differently from `Children` or `Parent` since they require a `Pivot`.
|
||||
|
||||
For an example, let's say we want to allow our pets to have multiple toys. But we also want the toys to be shared by multiple pets. We need a pivot entity for this.
|
||||
|
||||
```
|
||||
pets
|
||||
- id
|
||||
- type
|
||||
- owner_id
|
||||
|
||||
toys
|
||||
- id
|
||||
- name
|
||||
|
||||
pets_toys
|
||||
- id
|
||||
- pet_id
|
||||
- toy_id
|
||||
```
|
||||
|
||||
Here you can see the pivot entity, `pets_toys`, or `Pivot<Pet, Toy>`.
|
||||
|
||||
### Convenience
|
||||
|
||||
Let's add the convenience methods to `Pet`.
|
||||
|
||||
```swift
|
||||
extension Pet {
|
||||
func toys() throws -> Siblings<Toy> {
|
||||
return try siblings()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And the opposite for `Toy`.
|
||||
|
||||
```swift
|
||||
extension Toy {
|
||||
func pets() throws -> Siblings<Pet> {
|
||||
return try siblings()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now you are free to query pets and toys similarly to children.
|
||||
|
||||
```swift
|
||||
let pet: Pet = ...
|
||||
let toys = pet.toys().all()
|
||||
```
|
||||
|
||||
To create a new many-to-many relationship you can do the following.
|
||||
|
||||
```swift
|
||||
|
||||
var toy: Toy = ... // Create a new toy
|
||||
try toy.save() // Save the toy to the db
|
||||
|
||||
|
||||
var pet: Pet = ... // Create a new pet
|
||||
try pet.save() // Save the pet to the db
|
||||
|
||||
// Link them together in the db
|
||||
var pivot = Pivot<Toy, Pet>(toy, pet) // Create the relationship
|
||||
try pivot.save() // Save the relationship to the db
|
||||
```
|
||||
|
||||
### Preparation
|
||||
|
||||
To prepare for a relationship with a `Pivot`, simply add the pivot to the `Droplet`'s preparations.
|
||||
|
||||
```swift
|
||||
let drop = Droplet()
|
||||
drop.preparations += [
|
||||
Toy.self,
|
||||
Pet.self,
|
||||
Pivot<Toy, Pet>.self
|
||||
]
|
||||
```
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
# Hello, World
|
||||
|
||||
This section assumes you have installed Swift 3.1 and the Vapor Toolbox and have verified they are working.
|
||||
|
||||
!!! tip
|
||||
Note: If you don't want to use the Toolbox, follow the [manual guide](manual.md).
|
||||
|
||||
## New Project
|
||||
|
||||
Let's start by creating a new project called "Hello, World".
|
||||
|
||||
```sh
|
||||
vapor new Hello
|
||||
```
|
||||
|
||||
Vapor's folder structure will probably look familiar to you if you have worked with other web frameworks.
|
||||
|
||||
```
|
||||
Hello
|
||||
├── Sources
|
||||
│ └── App
|
||||
│ └── Controllers
|
||||
│ └── Middleware
|
||||
│ └── Models
|
||||
│ └── main.swift
|
||||
├── Public
|
||||
├── Resources
|
||||
│ └── Views
|
||||
└── Package.swift
|
||||
```
|
||||
|
||||
For our Hello, World project, we will be focusing on the `main.swift` file.
|
||||
|
||||
```
|
||||
Hello
|
||||
└── Sources
|
||||
└── App
|
||||
└── main.swift
|
||||
```
|
||||
!!! tip
|
||||
The `vapor new` command creates a new project with examples and comments about how to use the framework. You can delete these if you want.
|
||||
|
||||
## Code
|
||||
|
||||
### Droplet
|
||||
|
||||
Look for the following line in the `main.swift` file.
|
||||
|
||||
```swift
|
||||
let drop = try Droplet()
|
||||
```
|
||||
|
||||
This is where the one and only `Droplet `for this example will be created. The [Droplet](../vapor/droplet.md) class has a plethora of useful functions on it, and is used extensively.
|
||||
|
||||
### Routing
|
||||
|
||||
Right after the creation of `drop`, add the following code snippet.
|
||||
|
||||
```swift
|
||||
drop.get("hello") { request in
|
||||
return "Hello, world!"
|
||||
}
|
||||
```
|
||||
|
||||
This creates a new route on the `Droplet` that will match all `GET` requests to `/hello`.
|
||||
|
||||
All route closures are passed an instance of [Request](../http/request.md) that contains information such as the URI requested and data sent.
|
||||
|
||||
This route simply returns a string, but anything that is [ResponseRepresentable](../http/response-representable.md) can be returned. Learn more in the [Routing](../routing/basic.md) section of the guide.
|
||||
|
||||
!!! tip
|
||||
Xcode autocomplete may add extraneous type information to your closure's input arguments. This can be deleted to keep the code clean. If you'd like to keep the type information add `import HTTP` to the top of the file.
|
||||
|
||||
### Serving
|
||||
|
||||
At the bottom of the main file, make sure to run your `Droplet`.
|
||||
|
||||
```swift
|
||||
drop.run()
|
||||
```
|
||||
|
||||
Save the file, and switch back to the terminal.
|
||||
|
||||
## Compile & Run
|
||||
|
||||
### Building
|
||||
|
||||
A big part of what makes Vapor so great is Swift's state of the art compiler. Let's fire it up. Make sure you are in the root directory of the project and run the following command.
|
||||
|
||||
```swift
|
||||
vapor build
|
||||
```
|
||||
|
||||
!!! note
|
||||
`vapor build` runs `swift build` in the background.
|
||||
|
||||
The Swift Package Manager will first start by downloading the appropriate dependencies from git. It will then compile and link these dependencies together.
|
||||
|
||||
When the process has completed, you will see `Building Project [Done]`
|
||||
|
||||
!!! tip
|
||||
If you see a message like `unable to execute command: Killed`, you need to increase your swap space. This can happen if you are running on a machine with limited memory.
|
||||
|
||||
#### Release
|
||||
|
||||
Building your application in release mode takes longer, but increases performance.
|
||||
|
||||
```sh
|
||||
vapor build --release
|
||||
```
|
||||
|
||||
### Serving
|
||||
|
||||
Boot up the server by running the following command.
|
||||
|
||||
```sh
|
||||
vapor run serve
|
||||
```
|
||||
|
||||
You should see a message `Server starting...`. You can now visit `http://localhost:8080/hello` in your browser.
|
||||
|
||||
!!! note
|
||||
Certain port numbers require super user access to bind. Simply run `sudo vapor run` to allow access. If you decide to run on a port besides `80`, make sure to direct your browser accordingly.
|
||||
|
||||
#### Production
|
||||
|
||||
Serving your application in the production environment increases its security and performance.
|
||||
|
||||
```sh
|
||||
vapor run serve --env=production
|
||||
```
|
||||
|
||||
Debug errors will be silenced while in the production environment, so make sure to check your logs for errors.
|
||||
|
||||
!!! warning
|
||||
If you compiled your application with `--release`, make sure to add that flag to the `vapor run` command as well. e.g., `vapor run serve --env=production --release`.
|
||||
|
||||
### Hello, World
|
||||
|
||||
You should see the following output in your browser window.
|
||||
|
||||
```
|
||||
Hello, world!
|
||||
```
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# Install Swift 3.1: macOS
|
||||
|
||||
To use Swift 3.1 on macOS, you just need to have Xcode 8 installed.
|
||||
|
||||
## Install Xcode
|
||||
|
||||
Install [Xcode 8](https://itunes.apple.com/us/app/xcode/id497799835?mt=12) from the Mac App Store.
|
||||
|
||||
[](https://itunes.apple.com/us/app/xcode/id497799835?mt=12)
|
||||
|
||||
### Open Xcode
|
||||
|
||||
After Xcode 8 has been downloaded, you must open it to finish the installation. This may take a while.
|
||||
|
||||
## Check
|
||||
|
||||
Double check the installation was successful by running:
|
||||
|
||||
```sh
|
||||
curl -sL check.vapor.sh | bash
|
||||
```
|
||||
|
||||
## Toolbox
|
||||
|
||||
You can now move on to [Install Toolbox](install-toolbox.md).
|
||||
|
||||
## Swift.org
|
||||
|
||||
Check out [Swift.org](https://swift.org)'s extensive guides if you need more detailed instructions for installing Swift 3.0.
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
# Install Swift 3.1: Ubuntu
|
||||
|
||||
Installing Swift 3.1 on Ubuntu only takes a couple of minutes.
|
||||
|
||||
## Quick
|
||||
|
||||
Don't want to type? Run the following script to quickly install Swift 3.1.
|
||||
|
||||
```sh
|
||||
curl -sL swift.vapor.sh/ubuntu | bash
|
||||
```
|
||||
|
||||
!!! note
|
||||
The install script adds Swift to your `~/.bashrc` profile automatically.
|
||||
|
||||
## Manual
|
||||
|
||||
To install manually, you just need to install Swift's dependencies with `apt-get` and download the Swift toolchain.
|
||||
|
||||
### Version
|
||||
|
||||
Swift 3.1 supports the following Ubuntu versions:
|
||||
|
||||
- 14.04 LTS (Trusty Tahr)
|
||||
- 16.04 LTS (Xenial Xerus)
|
||||
- 16.10 (Yakkety Yak)
|
||||
|
||||
To check which version of Ubuntu you have, run:
|
||||
|
||||
```bash
|
||||
lsb_release -a
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
Depending on your version of Ubuntu, you may need some additional tools for the compiler. We'll err on the safe side and install everything you should need
|
||||
|
||||
```sh
|
||||
sudo apt-get update
|
||||
sudo apt-get install clang libicu-dev binutils git libpython2.7-dev libcurl3
|
||||
```
|
||||
|
||||
### Download
|
||||
|
||||
Download the Swift 3.1 toolchain for your Ubuntu version.
|
||||
|
||||
```sh
|
||||
# Ubuntu 14.04
|
||||
wget https://swift.org/builds/swift-3.1-release/ubuntu1404/swift-3.1-RELEASE/swift-3.1-RELEASE-ubuntu14.04.tar.gz
|
||||
|
||||
# Ubuntu 16.04
|
||||
wget https://swift.org/builds/swift-3.1-release/ubuntu1604/swift-3.1-RELEASE/swift-3.1-RELEASE-ubuntu16.04.tar.gz
|
||||
|
||||
# Ubuntu 16.10
|
||||
wget https://swift.org/builds/swift-3.1-release/ubuntu1610/swift-3.1-RELEASE/swift-3.1-RELEASE-ubuntu16.10.tar.gz
|
||||
```
|
||||
|
||||
### Decompress
|
||||
|
||||
After Swift has downloaded, decompress it.
|
||||
|
||||
```sh
|
||||
# Ubuntu 14.04
|
||||
tar zxf swift-3.1-RELEASE-ubuntu14.04.tar.gz
|
||||
|
||||
# Ubuntu 16.04
|
||||
tar zxf swift-3.1-RELEASE-ubuntu16.04.tar.gz
|
||||
|
||||
# Ubuntu 16.10
|
||||
tar zxf swift-3.1-RELEASE-ubuntu16.10.tar.gz
|
||||
```
|
||||
|
||||
### Install
|
||||
|
||||
Move Swift 3.1 to a safe, permanent place on your computer. We'll use `/swift-3.1`, but feel free to choose wherever you like.
|
||||
|
||||
```sh
|
||||
# Ubuntu 14.04
|
||||
mv swift-3.1-RELEASE-ubuntu14.04 /swift-3.1
|
||||
|
||||
# Ubuntu 16.04
|
||||
mv swift-3.1-RELEASE-ubuntu16.04 /swift-3.1
|
||||
|
||||
# Ubuntu 16.10
|
||||
mv swift-3.1-RELEASE-ubuntu16.10 /swift-3.1
|
||||
```
|
||||
|
||||
!!! warning
|
||||
You may need to use `sudo`.
|
||||
|
||||
### Export
|
||||
|
||||
Edit your bash profile using your text editor of choice.
|
||||
|
||||
```sh
|
||||
vim ~/.bashrc
|
||||
```
|
||||
|
||||
Add the following line:
|
||||
|
||||
```sh
|
||||
export PATH=/swift-3.1/usr/bin:"${PATH}"
|
||||
```
|
||||
|
||||
!!! warning
|
||||
If you moved Swift 3.1 to a folder other than `/swift-3.1`, your path will be different.
|
||||
|
||||
## Check
|
||||
|
||||
Double check the installation was successful by running:
|
||||
|
||||
```sh
|
||||
curl -sL check.vapor.sh | bash
|
||||
```
|
||||
|
||||
## Toolbox
|
||||
|
||||
You can now move on to [Install Toolbox](install-toolbox.md)
|
||||
|
||||
## Swift.org
|
||||
|
||||
Check out [Swift.org](https://swift.org)'s guide to [using downloads](https://swift.org/download/#using-downloads) if you need more detailed instructions for installing Swift 3.1.
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
# Install Toolbox
|
||||
|
||||
Vapor's command line interface provides shortcuts and assistance for common tasks.
|
||||
|
||||
<img width="682" alt="Vapor Toolbox" src="https://cloud.githubusercontent.com/assets/1342803/23553208/26af9a0e-0020-11e7-8ed5-1ce09407ae8e.png">
|
||||
|
||||
!!! tip
|
||||
If you do not want to install the Toolbox, checkout the [Manual](manual.md) quickstart.
|
||||
|
||||
## Install
|
||||
|
||||
Run the following script to install the [Toolbox](https://github.com/vapor/toolbox).
|
||||
|
||||
```sh
|
||||
curl -sL toolbox.vapor.sh | bash
|
||||
```
|
||||
|
||||
!!! warning
|
||||
Vapor Toolbox is written in Swift, so you must have Swift 3.1 installed. See the earlier steps of Getting Started for instructions on installing Swift.
|
||||
|
||||
### Verify
|
||||
|
||||
Make sure the Toolbox installed successfully by running the help query. You should see a print out of the available commands. You can run the `--help` option on any Toolbox command.
|
||||
|
||||
```sh
|
||||
vapor --help
|
||||
```
|
||||
## Create A Project
|
||||
|
||||
Now that you have installed the Toolbox, you can create your first Vapor project following the [Hello, World guide](hello-world.md).
|
||||
|
||||
## Updating
|
||||
|
||||
The toolbox can update itself. This may be useful if you experience any issues in the future.
|
||||
|
||||
```sh
|
||||
vapor self update
|
||||
```
|
||||
|
||||
## Templates
|
||||
|
||||
The toolbox can create a project from the Vapor basic-template or any other git repo.
|
||||
|
||||
```sh
|
||||
vapor new <name> [--template=<repo-url-or-github-path>]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
The toolbox will build an absolute URL based on what you pass as the template option.
|
||||
|
||||
- `--template=light` clones `http://github.com/vapor/light-template`.
|
||||
- `--template=user/repo` clones `http://github.com/user/repo`.
|
||||
- `--template=http://example.com/repo-path` clones the full url given.
|
||||
|
||||
!!! note
|
||||
If you do not specify a template option, the project will be built from Vapor's [basic template](https://github.com/vapor/basic-template).
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
# Manual Quickstart
|
||||
|
||||
Learn how to create a Vapor project _without_ the Toolbox using just Swift 3 and the Swift Package Manager.
|
||||
|
||||
This document assumes that you have Swift 3.1 installed.
|
||||
|
||||
!!! tip
|
||||
If you'd prefer to use the Toolbox, learn how to install it [here](install-toolbox.md). If you've installed the Toolbox, follow the toolbox guide [here](hello-world.md).
|
||||
|
||||
## Check
|
||||
|
||||
To check that your environment is compatible, run the following script:
|
||||
|
||||
```bash
|
||||
curl -sL check.vapor.sh | bash
|
||||
```
|
||||
|
||||
## Make new project using SwiftPM
|
||||
|
||||
Open your terminal
|
||||
|
||||
!!! note
|
||||
For our example, we'll be using the Desktop folder.
|
||||
|
||||
```bash
|
||||
cd ~/Desktop
|
||||
mkdir Hello
|
||||
cd Hello
|
||||
swift package init --type executable
|
||||
```
|
||||
|
||||
Your folder should look like this:
|
||||
|
||||
```
|
||||
├── Package.swift
|
||||
├── Sources
|
||||
│ └── main.swift
|
||||
└── Tests
|
||||
```
|
||||
|
||||
### Edit `Package.swift`
|
||||
|
||||
Open your `Package.swift` file:
|
||||
|
||||
```bash
|
||||
open Package.swift
|
||||
```
|
||||
|
||||
And add Vapor as a dependency. Here's how your file will look.
|
||||
|
||||
### Package.swift
|
||||
|
||||
```swift
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Hello",
|
||||
dependencies: [
|
||||
.Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2)
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
!!! warning
|
||||
We try to keep this document up to date, however, you can view latest releases [here](https://github.com/vapor/vapor/releases).
|
||||
|
||||
### Edit `main.swift`
|
||||
|
||||
A simple hello world:
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
let drop = Droplet()
|
||||
|
||||
drop.get("hello") { req in
|
||||
return "Hello Vapor"
|
||||
}
|
||||
|
||||
drop.run()
|
||||
```
|
||||
|
||||
## Compile & Run
|
||||
|
||||
The first `build` command can take a while to fetch dependencies.
|
||||
|
||||
```bash
|
||||
swift build
|
||||
.build/debug/Hello serve
|
||||
```
|
||||
|
||||
!!! warning
|
||||
If different, replace `Hello` above with the name of your executable (as defined in `Package.swift`).
|
||||
|
||||
### Production
|
||||
|
||||
Compiling in Swift's release mode and setting Vapor's environment to production will make your app more secure and performant.
|
||||
|
||||
```sh
|
||||
swift build --configuraiton release
|
||||
.build/release/Hello serve --env=production
|
||||
```
|
||||
|
||||
## View
|
||||
|
||||
Go to your favorite browser and visit `http://localhost:8080/hello`
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# Xcode
|
||||
|
||||
The first thing you'll probably notice about Vapor and Swift Package Manager projects in general is that they don't include an Xcode project. In fact, when SPM generates packages, the `.xcodeproj` file is `.gitignore`d by default.
|
||||
|
||||
This means we don't have to worry about `.pbxproj` conflicts, and it's easy for different platforms to utilize their own editors.
|
||||
|
||||
## Generate Project
|
||||
|
||||
### Vapor Toolbox
|
||||
|
||||
To generate a new Xcode project for a project, use:
|
||||
|
||||
```sh
|
||||
vapor xcode
|
||||
```
|
||||
|
||||
!!! tip
|
||||
If you'd like to automatically open the Xcode project, use `vapor xcode -y`
|
||||
|
||||
### Manual
|
||||
|
||||
To generate a new Xcode project manually.
|
||||
|
||||
```sh
|
||||
swift package generate-xcodeproj
|
||||
```
|
||||
|
||||
Open the project and continue normally.
|
||||
|
||||
## Flags
|
||||
|
||||
For some packages with underlying C dependencies, users will need to pass linker flags during **build** and **project generation**. Make sure to consult the guides associated with those dependencies. For example, MySQL:
|
||||
|
||||
```
|
||||
swift package generate-xcodeproj -Xswiftc -I/usr/local/include/mysql -Xlinker -L/usr/local/lib
|
||||
```
|
||||
|
||||
!!! tip
|
||||
The Vapor Toolbox automatically detects and adds linker flags required for Vapor packages.
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
> Module: `import HTTP`
|
||||
|
||||
# Body
|
||||
|
||||
The `HTTP.Body` represents the payload of an `HTTP.Message`, and is used to pass the underlying data. Some examples of this in practice would be `JSON`, `HTML` text, or the bytes of an image. Let's look at the implementation:
|
||||
|
||||
```swift
|
||||
public enum Body {
|
||||
case data(Bytes)
|
||||
case chunked((ChunkStream) throws -> Void)
|
||||
}
|
||||
```
|
||||
|
||||
## Data Case
|
||||
|
||||
The `data` case is by far the most common use for a `Body` in an `HTTP.Message`. It is simply an array of bytes. The serialization protocol or type associated with these bytes is usually defined by the `Content-Type` header. Let's look at some examples.
|
||||
|
||||
### Application/JSON
|
||||
|
||||
If our `Content-Type` header contains `application/json`, then the underlying bytes represent serialized JSON.
|
||||
|
||||
```swift
|
||||
if let contentType = req.headers["Content-Type"], contentType.contains("application/json"), let bytes = req.body.bytes {
|
||||
let json = try JSON(bytes: bytes)
|
||||
print("Got JSON: \(json)")
|
||||
}
|
||||
```
|
||||
|
||||
### Image/PNG
|
||||
|
||||
If our `Content-Type` contains `image/png`, then the underlying bytes represent an encoded png.
|
||||
|
||||
```swift
|
||||
if let contentType = req.headers["Content-Type"], contentType.contains("image/png"), let bytes = req.body.bytes {
|
||||
try database.save(image: bytes)
|
||||
}
|
||||
```
|
||||
|
||||
## Chunked Case
|
||||
|
||||
The `chunked` case only applies to outgoing `HTTP.Message`s in Vapor. It is traditionally a responder's role to collect an entire chunked encoding before passing it on. We can use this to send a body asynchronously.
|
||||
|
||||
```swift
|
||||
let body: Body = Body.chunked(sender)
|
||||
return Response(status: .ok, body: body)
|
||||
```
|
||||
|
||||
We can implement this manually, or use Vapor's built in convenience initializer for chunked bodies:
|
||||
|
||||
```swift
|
||||
return 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.
|
||||
|
||||
## BodyRepresentable
|
||||
|
||||
In addition to the concrete `Body` type, as is common in Vapor, we also have wide support for `BodyRepresentable`. This means objects that we're commonly converting to `Body` type can be used interchangeably. For example:
|
||||
|
||||
```swift
|
||||
return Response(body: "Hello, World!")
|
||||
```
|
||||
|
||||
In the above example, string is converted to bytes and added to the body.
|
||||
|
||||
> In practice, it is better to use `return "Hello, World!"`. Vapor will automatically be able to set the `Content-Type` to appropriate values.
|
||||
|
||||
Let's look at how it's implemented:
|
||||
|
||||
```swift
|
||||
public protocol BodyRepresentable {
|
||||
func makeBody() -> Body
|
||||
}
|
||||
```
|
||||
|
||||
### Custom
|
||||
|
||||
We can conform our own types to this as well where applicable. Let's pretend we have a custom data type, `.vpr`. Let's conform our `VPR` file type model:
|
||||
|
||||
```swift
|
||||
extension VPRFile: HTTP.BodyRepresentable {
|
||||
func makeBody() -> Body {
|
||||
// collect bytes
|
||||
return .data(bytes)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> You may have noticed above, that the protocol throws, but our implementation does not. This is completely valid in Swift and will allow you to not throw if you're ever calling the function manually.
|
||||
|
||||
Now we're able to include our `VPR` file directly in our `Responses`.
|
||||
|
||||
```swift
|
||||
drop.get("files", ":file-name") { request in
|
||||
let filename = try request.parameters.extract("file-name") as String
|
||||
let file = VPRFileManager.fetch(filename)
|
||||
return Response(status: .ok, headers: ["Content-Type": "file/vpr"], body: file)
|
||||
}
|
||||
```
|
||||
|
||||
In practice, if we're repeating this often, we'll probably conform `VPRFile` directly to `ResponseRepresentable`
|
||||
|
||||
```swift
|
||||
extension VPRFile: HTTP.ResponseRepresentable {
|
||||
func makeResponse() -> Response {
|
||||
return Response(
|
||||
status: .ok,
|
||||
headers: ["Content-Type": "file/vpr"],
|
||||
body: file
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here's our above example now:
|
||||
|
||||
```swift
|
||||
drop.get("files", ":file-name") { request in
|
||||
let filename = try request.parameters.extract("file-name") as String
|
||||
return VPRFileManager.fetch(filename)
|
||||
}
|
||||
```
|
||||
|
||||
We could also use type-safe routing to make this even more concise:
|
||||
|
||||
```swift
|
||||
drop.get("files", String.self) { request, filename in
|
||||
return VPRFileManager.fetch(filename)
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
> Module: `import HTTP`
|
||||
|
||||
# Client
|
||||
|
||||
The client provided by `HTTP` is used to make outgoing requests to remote servers. Let's look at a simple outgoing request.
|
||||
|
||||
## QuickStart
|
||||
|
||||
Let's jump right in to make a simple HTTP Request. Here's a basic `GET` request using your Vapor `Droplet`.
|
||||
|
||||
```swift
|
||||
let query = ...
|
||||
let spotifyResponse = try drop.client.get("https://api.spotify.com/v1/search?type=artist&q=\(query)")
|
||||
print(spotifyR)
|
||||
```
|
||||
|
||||
### Clean Up
|
||||
|
||||
The url above can be a little tricky to read, so let's use the query parameter to clean it up a little bit:
|
||||
|
||||
```swift
|
||||
try drop.client.get("https://api.spotify.com/v1/search", query: ["type": "artist", "q": query])
|
||||
```
|
||||
|
||||
### Continued
|
||||
|
||||
In addition to `GET` requests, Vapor's client provides support for most common HTTP functions. `GET`, `POST`, `PUT`, `PATCH`, `DELETE`
|
||||
|
||||
### POST as json
|
||||
```swift
|
||||
try drop.client.post("http://some-endpoint/json", headers: ["Content-Type": "application/json"], body: myJSON.makeBody())
|
||||
```
|
||||
|
||||
### POST as x-www-form-urlencoded
|
||||
```swift
|
||||
try drop.client.post("http://some-endpoint", headers: [
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
], body: Body.data( Node(node: [
|
||||
"email": "mymail@vapor.codes"
|
||||
]).formURLEncoded()))
|
||||
```
|
||||
|
||||
### Full Request
|
||||
|
||||
To access additional functionality or custom methods, use the underlying `request` function directly.
|
||||
|
||||
```swift
|
||||
public static func get(_ method: Method,
|
||||
_ uri: String,
|
||||
headers: [HeaderKey: String] = [:],
|
||||
query: [String: CustomStringConvertible] = [:],
|
||||
body: Body = []) throws -> Response
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```swift
|
||||
try drop.client.request(.other(method: "CUSTOM"), "http://some-domain", headers: ["My": "Header"], query: ["key": "value"], body: [])
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
The `Config/clients.json` file can be used to modify the client's settings.
|
||||
|
||||
### TLS
|
||||
|
||||
Host and certificate verification can be disabled.
|
||||
|
||||
> Note: Use extreme caution when modifying these settings.
|
||||
|
||||
```json
|
||||
{
|
||||
"tls": {
|
||||
"verifyHost": false,
|
||||
"verifyCertificates": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Mozilla
|
||||
|
||||
The Mozilla certificates are included by default to make fetching content from secure sites easy.
|
||||
|
||||
```json
|
||||
{
|
||||
"tls": {
|
||||
"certificates": "mozilla"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced
|
||||
|
||||
In addition to our Droplet, we can also use and interact with the `Client` manually. Here's how our default implementation in Vapor looks:
|
||||
|
||||
```swift
|
||||
let response = try Client<TCPClientStream>.get("http://some-endpoint/mine")
|
||||
```
|
||||
|
||||
The first thing we likely noticed is `TCPClientStream` being used as a Generic value. This will be the underlying connection that the `HTTP.Client` can use when performing the request. By conforming to the underlying `ClientStream`, an `HTTP.Client` can accept custom stream implementations seamlessly.
|
||||
|
||||
## Save Connection
|
||||
|
||||
Up to this point, we've been interacting with the Client via `class` or `static` level functions. This allows us to end the connection upon a completed request and is the recommended interaction for most use cases. For some advanced situations, we may want to reuse a connection. For these, we can initialize our client and perform multiple requests like this.
|
||||
|
||||
```swift
|
||||
let pokemonClient = try drop?.client.make(scheme: "http", host: "pokeapi.co")
|
||||
for i in 0...1 {
|
||||
let response = try pokemonClient?.get(path: "/api/v2/pokemon/", query: ["limit": 20, "offset": i])
|
||||
print("response: \(response)")
|
||||
}
|
||||
```
|
||||
|
||||
## ClientProtocol
|
||||
|
||||
Up to this point, we've focused on the built in `HTTP.Client`, but users can also include their own customized clients by conforming to `HTTP.ClientProtocol`. Let's look at the implementation:
|
||||
|
||||
```swift
|
||||
public protocol Responder {
|
||||
func respond(to request: Request) throws -> Response
|
||||
}
|
||||
|
||||
public protocol Program {
|
||||
var host: String { get }
|
||||
var port: Int { get }
|
||||
var securityLayer: SecurityLayer { get }
|
||||
// default implemented
|
||||
init(host: String, port: Int, securityLayer: SecurityLayer) throws
|
||||
}
|
||||
|
||||
public protocol ClientProtocol: Program, Responder {
|
||||
var scheme: String { get }
|
||||
var stream: Stream { get }
|
||||
init(scheme: String, host: String, port: Int, securityLayer: SecurityLayer) throws
|
||||
}
|
||||
```
|
||||
|
||||
By conforming to these underlying functions, we immediately gain access to the public `ClientProtocol` apis we viewed above.
|
||||
|
||||
## Customize Droplet
|
||||
|
||||
If we've introduced a custom conformance to `HTTP.ClientProtocol`, we can pass this into our droplet without changing the underlying behavior in our application.
|
||||
|
||||
For example:
|
||||
|
||||
```swift
|
||||
let drop = Droplet()
|
||||
|
||||
drop.client = MyCustomClient.self
|
||||
```
|
||||
|
||||
Going forward, all of your calls to `drop.client` will use `MyCustomClient.self`:
|
||||
|
||||
```swift
|
||||
drop.client.get(... // uses `MyCustomClient`
|
||||
```
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# CORS
|
||||
|
||||
Vapor by default provides a middleware for implementing proper support for Cross-Origin Resource Sharing (CORS) named `CORSMiddleware`.
|
||||
|
||||
"Cross-Origin Resource Sharing (CORS) is a specification that enables truly open access across domain-boundaries. If you serve public content, please consider using CORS to open it up for universal JavaScript / browser access." - [http://enable-cors.org/](http://enable-cors.org/)
|
||||
|
||||
To learn more about middlewares, please visit the Middleware section of the documentation [here](https://vapor.github.io/documentation/guide/middleware.html).
|
||||
|
||||

|
||||
*Image Author: [Wikipedia](https://commons.wikimedia.org/wiki/File:Flowchart_showing_Simple_and_Preflight_XHR.svg)*
|
||||
|
||||
## Basic
|
||||
|
||||
First of all, add the CORS middleware into your droplet middlewares array.
|
||||
|
||||
```swift
|
||||
# Insert CORS before any other middlewares
|
||||
drop.middleware.insert(CORSMiddleware(), at: 0)
|
||||
```
|
||||
|
||||
> Note: Make sure you insert CORS middleware before any other throwing middlewares, like the AbortMiddleware or similar. Otherwise the proper headers might not be added to the response.
|
||||
|
||||
`CORSMiddleware` has a default configuration which should suit most users, with values as follows:
|
||||
|
||||
- **Allowed Origin**
|
||||
- Value of origin header in the request.
|
||||
- **Allowed Methods**
|
||||
- `GET`, `POST`, `PUT`, `OPTIONS`, `DELETE`, `PATCH`
|
||||
- **Allowed Headers**
|
||||
- `Accept`, `Authorization`, `Content-Type`, `Origin`, `X-Requested-With`
|
||||
|
||||
## Advanced
|
||||
|
||||
All settings and presets can be customized by advanced users. There's two ways of doing this, either you programatically create and configure a `CORSConfiguration` object or you can put your configuration into a Vapor's JSON config file.
|
||||
|
||||
See below for how to set up both and what are the options.
|
||||
|
||||
### Configuration
|
||||
|
||||
The `CORSConfiguration` struct is used to configure the `CORSMiddleware`. You can instanitate one like this:
|
||||
|
||||
```swift
|
||||
let configuration = CORSConfiguration(allowedOrigin: .custom("https://vapor.codes"),
|
||||
allowedMethods: [.get, .post, .options],
|
||||
allowedHeaders: ["Accept", "Authorization"],
|
||||
allowCredentials: false,
|
||||
cacheExpiration: 600,
|
||||
exposedHeaders: ["Cache-Control", "Content-Language"])
|
||||
```
|
||||
|
||||
After creating a configuration you can add the CORS middleware.
|
||||
|
||||
```swift
|
||||
drop.middleware.insert(CORSMiddleware(configuration: configuration), at: 0)
|
||||
```
|
||||
|
||||
> Note: Please consult the documentation in the source code of the `CORSConfiguration` for more information about available values for the settings.
|
||||
|
||||
|
||||
### JSON Config
|
||||
|
||||
Optionally, `CORSMiddleware` can be configured using the Vapor's `Config` which is created out of the json files contained in your Config folder. You will need to create a file called `cors.json` or `CORS.json` in your Config folder in your project and add the required keys.
|
||||
|
||||
Example of how such a file could look as follows:
|
||||
|
||||
```swift
|
||||
{
|
||||
"allowedOrigin": "origin",
|
||||
"allowedMethods": "GET,POST,PUT,OPTIONS,DELETE,PATCH",
|
||||
"allowedHeaders": ["Accept", "Authorization", "Content-Type", "Origin", "X-Requested-With"]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
> Note: Following keys are required: `allowedOrigin`, `allowedMethods`, `allowedHeaders`. If they are not present an error will be thrown while instantiating the middleware.
|
||||
>
|
||||
> Optionally you can also specify the keys `allowCredentials` (Bool), `cacheExpiration` (Int) and `exposedHeaders` ([String]).
|
||||
|
||||
Afterwards you can add the middleware using the a throwing overload of the initialiser that accepts Vapor's `Config`.
|
||||
|
||||
```swift
|
||||
let drop = Droplet()
|
||||
|
||||
do {
|
||||
drop.middleware.insert(try CORSMiddleware(configuration: drop.config), at: 0)
|
||||
} catch {
|
||||
fatalError("Error creating CORSMiddleware, please check that you've setup cors.json correctly.")
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
> Module: `import HTTP`
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
### Route 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 HTTP.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:
|
||||
|
||||
```json
|
||||
{
|
||||
"hello": "world"
|
||||
}
|
||||
```
|
||||
|
||||
Will still be accessible through data.
|
||||
|
||||
```swift
|
||||
let world = request.data["hello"]?.string
|
||||
```
|
||||
> Note: Force unwrap should never be used.
|
||||
|
||||
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 Parameters
|
||||
|
||||
The same applies to query convenience:
|
||||
|
||||
```swift
|
||||
let query = request.query?["hello"] // String?
|
||||
let name = request.query?["name"]?.string // String?
|
||||
let age = request.query?["age"]?.int // Int?
|
||||
let rating = request.query?["rating"]?.double // Double?
|
||||
```
|
||||
|
||||
## 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"]
|
||||
```
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
> Module: `import HTTP`
|
||||
|
||||
# Responder
|
||||
|
||||
The `Responder` is a simple protocol defining the behavior of objects that can accept a `Request` and return a `Response`. Most notably in Vapor, it is the core API endpoint that connects the `Droplet` to the `Server`. Let's look at the definition:
|
||||
|
||||
```swift
|
||||
public protocol Responder {
|
||||
func respond(to request: Request) throws -> Response
|
||||
}
|
||||
```
|
||||
|
||||
> The responder protocol is most notably related to Droplet and it's relationship with a server. Average users will not likely interact with it much.
|
||||
|
||||
## Simple
|
||||
|
||||
Of course, Vapor provides some conveniences for this, and in practice, we will often call:
|
||||
|
||||
```swift
|
||||
try drop.run()
|
||||
```
|
||||
|
||||
## Manual
|
||||
|
||||
As we just mentioned, the Vapor `Droplet` itself conforms to `Responder`, connecting it to the `Server`. This means if we wanted to serve our droplet manually, we could do:
|
||||
|
||||
```swift
|
||||
let server = try Server<TCPServerStream, Parser<Request>, Serializer<Response>>(port: port)
|
||||
try server.start(responder: droplet) { error in
|
||||
print("Got error: \(error)")
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced
|
||||
|
||||
We can conform our own objects to `Responder` and pass them to `Servers`. Let's look at an example:
|
||||
|
||||
```swift
|
||||
final class Responder: HTTP.Responder {
|
||||
func respond(to request: Request) throws -> Response {
|
||||
let body = "Hello World".makeBody()
|
||||
return Response(body: body)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This only returns `"Hello World"` for every request, it's most commonly going to be linked with a router of some type.
|
||||
|
||||
|
||||
```swift
|
||||
final class Responder: HTTP.Responder {
|
||||
let router: Router = ...
|
||||
|
||||
func respond(to request: Request) throws -> Response {
|
||||
return try router.route(request)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We'll then pass this responder to a server and let it go.
|
||||
|
||||
```swift
|
||||
let server = try Server<TCPServerStream, Parser<Request>, Serializer<Response>>(port: port)
|
||||
|
||||
print("visit http://localhost:\(port)/")
|
||||
try server.start(responder: Responder()) { error in
|
||||
print("Got error: \(error)")
|
||||
}
|
||||
```
|
||||
|
||||
This can be used as a jumping off point for applications looking to implement features manually.
|
||||
|
||||
## Client
|
||||
|
||||
The `HTTP.Client` is itself a `Responder` although, instead of handling the `Request` itself, it passes it on to the underlying URI.
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
> Module: `import HTTP`
|
||||
|
||||
# ResponseRepresentable
|
||||
|
||||
Traditionally HTTP servers take a `Request` and return a `Response`. Vapor is no different, but we can take advantage of Swift's powerful protocols to be a bit more flexible to the user facing API.
|
||||
|
||||
Let's start with the definition of `ResponseRepresentable`
|
||||
|
||||
```swift
|
||||
public protocol ResponseRepresentable {
|
||||
func makeResponse() throws -> Response
|
||||
}
|
||||
```
|
||||
|
||||
By conforming to this protocol, we can more flexibly return things that conform instead of creating the response manually each time. Vapor provides some of these by default. Including (but not limited to):
|
||||
|
||||
### String
|
||||
|
||||
Because string conforms to `ResponseRepresentable`, we can return it directly in a Vapor route handler.
|
||||
|
||||
```swift
|
||||
drop.get("hello") { request in
|
||||
return "Hello, World!"
|
||||
}
|
||||
```
|
||||
|
||||
### JSON
|
||||
|
||||
`JSON` can be returned directly instead of recreating a response each time.
|
||||
|
||||
```swift
|
||||
drop.get("hello") { request in
|
||||
return try JSON(node: [
|
||||
"hello": "world",
|
||||
"some-numbers": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
]
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
Of course, we can also return Responses for anything not covered:
|
||||
|
||||
```swift
|
||||
drop.get("hello") { request in
|
||||
return Response(status: .ok, headers: ["Content-Type": "text/plain"], body: "Hello, World!")
|
||||
}
|
||||
```
|
||||
|
||||
## Conforming
|
||||
|
||||
All we need to do to return our own objects is conform them to `ResponseRepresentable`. Let's look at an example type, a simple blog post model:
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
|
||||
struct BlogPost {
|
||||
let id: String
|
||||
let content: String
|
||||
let createdAt: NSDate
|
||||
}
|
||||
```
|
||||
|
||||
And now, let's conform it to response representable.
|
||||
|
||||
```swift
|
||||
import HTTP
|
||||
import Foundation
|
||||
|
||||
extension BlogPost: ResponseRepresentable {
|
||||
func makeResponse() throws -> Response {
|
||||
let json = try JSON(node:
|
||||
[
|
||||
"id": id,
|
||||
"content": content,
|
||||
"created-at": createdAt.timeIntervalSince1970
|
||||
]
|
||||
)
|
||||
return try json.makeResponse()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Don't forget to import HTTP.
|
||||
|
||||
Now that we've modeled our BlogPost, we can return it directly in route handlers.
|
||||
|
||||
```swift
|
||||
drop.post("post") { req in
|
||||
guard let content = request.data["content"] else { throw Error.missingContent }
|
||||
let post = Post(content: content)
|
||||
try post.save(to: database)
|
||||
return post
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
> Module: `import HTTP`
|
||||
|
||||
# Response
|
||||
|
||||
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 HTTP.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)
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Server
|
||||
|
||||
The server is responsible for accepting connections from clients, parsing their requests, and delivering them a response.
|
||||
|
||||
## Default
|
||||
|
||||
Starting your Droplet with a default server is simple.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
let drop = Droplet()
|
||||
|
||||
drop.run()
|
||||
```
|
||||
|
||||
The default server will bind to host `0.0.0.0` at port `8080`.
|
||||
|
||||
## Config
|
||||
|
||||
If you are using a `Config/servers.json` file, this is where you can easily change your host and port or even boot multiple servers.
|
||||
|
||||
```json
|
||||
{
|
||||
"default": {
|
||||
"port": "$PORT:8080",
|
||||
"host": "0.0.0.0",
|
||||
"securityLayer": "none"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The default `servers.json` is above. The port with try to resolve the environment variable `$PORT` or fallback to `8080`.
|
||||
|
||||
### Multiple
|
||||
|
||||
You can start multiple servers in the same application. This is especially useful if you want to boot an `HTTP` and `HTTPS` server side by side.
|
||||
|
||||
```json
|
||||
{
|
||||
"plaintext": {
|
||||
"port": "80",
|
||||
"host": "vapor.codes",
|
||||
"securityLayer": "none"
|
||||
},
|
||||
"secure": {
|
||||
"port": "443",
|
||||
"host": "vapor.codes",
|
||||
"securityLayer": "tls",
|
||||
"tls": {
|
||||
"certificates": "none",
|
||||
"signature": "selfSigned"
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## TLS
|
||||
|
||||
TLS (formerly SSL) can be configured with a variety of different certificate and signature types.
|
||||
|
||||
### Verify
|
||||
|
||||
Verificiation of hosts and certificates can be disabled. They are enabled by default.
|
||||
|
||||
> Note: Be extremely careful when disabling these options.
|
||||
|
||||
```json
|
||||
"tls": {
|
||||
"verifyHost": false,
|
||||
"verifyCertificates": false
|
||||
}
|
||||
```
|
||||
|
||||
### Certificates
|
||||
|
||||
#### None
|
||||
|
||||
```json
|
||||
"tls": {
|
||||
"certificates": "none"
|
||||
}
|
||||
```
|
||||
|
||||
#### Chain
|
||||
|
||||
```json
|
||||
"tls": {
|
||||
"certificates": "chain",
|
||||
"chainFile": "/path/to/chainfile"
|
||||
}
|
||||
```
|
||||
|
||||
#### Files
|
||||
|
||||
```json
|
||||
"tls": {
|
||||
"certificates": "files",
|
||||
"certificateFile": "/path/to/cert.pem",
|
||||
"privateKeyFile": "/path/to/key.pem"
|
||||
}
|
||||
```
|
||||
|
||||
#### Certificate Authority
|
||||
|
||||
```json
|
||||
"tls": {
|
||||
"certificates": "ca"
|
||||
}
|
||||
```
|
||||
|
||||
### Signature
|
||||
|
||||
#### Self Signed
|
||||
|
||||
```json
|
||||
"tls": {
|
||||
"signature": "selfSigned"
|
||||
}
|
||||
```
|
||||
|
||||
#### Signed File
|
||||
|
||||
```json
|
||||
"tls": {
|
||||
"signature": "signedFile",
|
||||
"caCertificateFile": "/path/to/file"
|
||||
}
|
||||
```
|
||||
|
||||
#### Signed Directory
|
||||
|
||||
```json
|
||||
"tls": {
|
||||
"signature": "signedDirectory",
|
||||
"caCertificateDirectory": "/path/to/dir"
|
||||
}
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
Here is an example `servers.json` file using certificate files with a self signed signature and host verification redundantly set to `true`.
|
||||
|
||||
```json
|
||||
{
|
||||
"secure": {
|
||||
"port": "8443",
|
||||
"host": "0.0.0.0",
|
||||
"securityLayer": "tls",
|
||||
"tls": {
|
||||
"verifyHost": true,
|
||||
"certificates": "files",
|
||||
"certificateFile": "/vapor/certs/cert.pem",
|
||||
"privateKeyFile": "/vapor/certs/key.pem",
|
||||
"signature": "selfSigned"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Manual
|
||||
|
||||
Servers can also be configured manually, without configuration files.
|
||||
|
||||
> Note: If servers are configured programatically, they override any config settings.
|
||||
|
||||
### Simple
|
||||
|
||||
The `run` method on the Droplet takes a dictionary of server configuration objects. The key is the name of the server.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
let drop = Droplet()
|
||||
|
||||
drop.run(servers: [
|
||||
"default": (host: "vapor.codes", port: 8080, securityLayer: .none)
|
||||
]
|
||||
```
|
||||
|
||||
### TLS
|
||||
|
||||
TLS can also be configured manually, and works similarly to the `servers.json` config files described above.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
import TLS
|
||||
|
||||
let drop = Droplet()
|
||||
|
||||
let config = try TLS.Config(
|
||||
mode: .server,
|
||||
certificates: .files(
|
||||
certificateFile: "/Users/tanner/Desktop/certs/cert.pem",
|
||||
privateKeyFile: "/Users/tanner/Desktop/certs/key.pem",
|
||||
signature: .selfSigned
|
||||
),
|
||||
verifyHost: true,
|
||||
verifyCertificates: true
|
||||
)
|
||||
|
||||
drop.run(servers: [
|
||||
"plaintext": ("vapor.codes", 8080, .none),
|
||||
"secure": ("vapor.codes", 8443, .tls(config)),
|
||||
])
|
||||
````
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg
|
||||
width="50px"
|
||||
height="50px"
|
||||
viewBox="0 0 50 50"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<title>Vapor</title>
|
||||
<desc>The Vapor droplet logo in pink and blue.</desc>
|
||||
<defs>
|
||||
<linearGradient
|
||||
x1="1.11022302e-14%"
|
||||
y1="5.88522518%"
|
||||
x2="1.11022302e-14%"
|
||||
y2="59.5616958%"
|
||||
id="pink-and-blue"
|
||||
>
|
||||
<stop stop-color="#F7CAC9" offset="0%"></stop>
|
||||
<stop stop-color="#92A8D1" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="droplet" fill="url(#pink-and-blue)">
|
||||
<path
|
||||
d="M17.6186417,50 C17.6186417,50 35.2941176,27.6875238 35.2941176,17.8366268 C35.2941176,7.98572985 27.3932603,0 17.6470588,0 C7.90085736,0 0,7.98572985 0,17.8366268 C0,27.6875238 17.6186417,50 17.6186417,50 Z"
|
||||
transform="translate(17.647059, 25.000000) scale(1, -1) translate(-17.647059, -25.000000)"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg
|
||||
width="50px"
|
||||
height="50px"
|
||||
viewBox="0 0 50 50"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<title>Vapor</title>
|
||||
<desc>The Vapor droplet logo in white.</desc>
|
||||
<g id="droplet" fill="#FFFFFF">
|
||||
<path
|
||||
d="M17.6186417,50 C17.6186417,50 35.2941176,27.6875238 35.2941176,17.8366268 C35.2941176,7.98572985 27.3932603,0 17.6470588,0 C7.90085736,0 0,7.98572985 0,17.8366268 C0,27.6875238 17.6186417,50 17.6186417,50 Z"
|
||||
transform="translate(17.647059, 25.000000) scale(1, -1) translate(-17.647059, -25.000000)"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 699 B |
|
|
@ -0,0 +1,115 @@
|
|||
# Vapor Documentation
|
||||
|
||||
This is the documentation for Vapor, a Web Framework for Swift that works on iOS, macOS, and Ubuntu; and all of the packages that Vapor offers.
|
||||
|
||||
Vapor is the most used web framework for Swift. It provides a beautifully expressive and easy to use foundation for your next website or API.
|
||||
|
||||
## Where To Start
|
||||
|
||||
If this is your first time using Vapor, head to the [Getting Started](getting-started/install-swift-3-macos.md) section to install Swift and create your first app.
|
||||
|
||||
### Viewing Mediums
|
||||
|
||||
You can read this guide by clicking through the folders and markdown files on [GitHub](https://github.com/vapor/documentation) or through the rendered [website](https://docs.vapor.codes).
|
||||
|
||||
## Other Sources
|
||||
|
||||
Here are some other great places to find information about Vapor.
|
||||
|
||||
### API
|
||||
|
||||
Auto-generated API documentation is located at [api.vapor.codes](http://api.vapor.codes).
|
||||
|
||||
### Stack Overflow
|
||||
|
||||
View or ask questions related to Vapor on Stack Overflow using the [`vapor`](http://stackoverflow.com/questions/tagged/vapor) tag.
|
||||
|
||||
### GitHub
|
||||
|
||||
#### Source Code
|
||||
|
||||
To view the framework's source code and code documentation, visit [Vapor's GitHub](https://github.com/vapor/vapor).
|
||||
|
||||
#### Issues
|
||||
|
||||
To view open bug reports and feature requests, or to create one, visit the [issues](https://github.com/vapor/vapor/issues) tab on [Vapor's GitHub](https://github.com/vapor/vapor).
|
||||
|
||||
## Packages
|
||||
|
||||
Vapor is a modular framework built for a modular language. Code is split up into modules which are grouped to form packages. Packages can be added to your project by adding the package's Git url to your `Package.swift` file. Once a package is included, all of its modules will be available to `import`. You can read more about packages and modules in the Swift Package Manager [conceptual overview](https://swift.org/package-manager/).
|
||||
|
||||
Below is a list of packages and modules that come with or can be used by Vapor projects. Packages will have a link to their respective GitHub page.
|
||||
|
||||
### Included
|
||||
|
||||
Here is a list of all the packages and modules included with Vapor.
|
||||
|
||||
!!! tip
|
||||
While these packages are included in Vapor by default, they can also be used individually.
|
||||
|
||||
- [Vapor](https://github.com/vapor/vapor): Swift's most used web framework.
|
||||
- Auth: User authentication and persistance.
|
||||
- Sessions: Secure, ephemeral cookie based data storage.
|
||||
- Cookies: HTTP cookies.
|
||||
- Routing: Advanced router with type-safe parameterization.
|
||||
- [Engine](https://github.com/vapor/engine): Core transport layers.
|
||||
- HTTP: Pure Swift HTTP client and server.
|
||||
- URI: Pure Swift URI parsing and serializing.
|
||||
- WebSockets: Full-duplex communication channels over a single TCP connection.
|
||||
- SMTP: Send email using Sendgrid and Gmail.
|
||||
- [Multipart](https://github.com/vapor/multipart): Fast, streaming, non-blocking multipart parser and serializer.
|
||||
- Multipart: Parses and serializes `multipart/mixed`.
|
||||
- FormData: Parses and serializes `multipart/form-data`.
|
||||
- [JSON](https://github.com/vapor/json): Conveniences for working with JSON in Swift.
|
||||
- [Console](https://github.com/vapor/console): Swift wrapper for console IO and commands.
|
||||
- [TLS](https://github.com/vapor/tls): Swift wrapper for CLibreSSL's new TLS.
|
||||
- [Crypto](https://github.com/vapor/crypto): Cryptography from LibreSSL and Swift.
|
||||
- Digests: Hashing with and without authentication.
|
||||
- Ciphers: Encryption and decryption
|
||||
- Random: Pseudo and cryptographically secure randomness.
|
||||
- BCrypt: Pure Swift implementation.
|
||||
- [Node](https://github.com/vapor/node): Data structure for easy type conversions.
|
||||
- [Polymorphic](https://github.com/vapor/polymorphic): Syntax for easily accessing values from common types like JSON.
|
||||
- [Path Indexable](https://github.com/vapor/path-indexable): A protocol for powerful subscript access of common types like JSON.
|
||||
- [Core](https://github.com/vapor/core): Core extensions, type-aliases, and functions that facilitate common tasks.
|
||||
- [Socks](https://github.com/vapor/socks): Swift C Socket API wrapper.
|
||||
|
||||
### Extras
|
||||
|
||||
These are officially supported packages for Vapor that are not included by default.
|
||||
|
||||
- [Fluent](https://github.com/vapor/fluent): Models, relationships, and querying for NoSQL and SQL databases.
|
||||
- [MySQL](https://github.com/vapor/mysql): Robust MySQL interface for Swift.
|
||||
- [MySQL Driver](https://github.com/vapor/mysql-driver): MySQL driver for Fluent.
|
||||
- [MySQL Provider](https://github.com/vapor/mysql-provider): MySQL provider for Vapor.
|
||||
- [Leaf](https://github.com/vapor/leaf): An extensible templating language.
|
||||
- [Redbird](https://github.com/vapor/redbird): Pure-Swift Redis client implemented from the original protocol spec..
|
||||
- [Redis Provider](https://github.com/vapor/redis-provider): Redis cache provider for Vapor.
|
||||
- [JWT](https://github.com/vapor/jwt): JSON Web Tokens in Swift.
|
||||
- [JWT Provider](https://github.com/vapor/jwt-provider): JWT conveniences for Vapor.
|
||||
|
||||
### Third Party
|
||||
|
||||
These are packages created by community members that work great with Vapor.
|
||||
|
||||
- [PostgreSQL](https://github.com/vapor/postgresql): Robust PostgreSQL interface for Swift.
|
||||
- [PostgreSQL Driver](https://github.com/vapor/postgresql-driver): PostgreSQL driver for Fluent.
|
||||
- [PostgreSQL Provider](https://github.com/vapor/postgresql-provider): PostgreSQL provider for Vapor.
|
||||
- [MongoKitten*](https://github.com/OpenKitten/MongoKitten): Native MongoDB driver for Swift, written in Swift
|
||||
- [Mongo Driver](https://github.com/vapor/mongo-driver): MongoKitten driver for Fluent.
|
||||
- [Mongo Provider](https://github.com/vapor/mongo-provider): MongoKitten provider for Vapor.
|
||||
- [MainecoonVapor](https://github.com/OpenKitten/MainecoonVapor): MongoKitten ORM for Vapor.
|
||||
- [Kitura Provider](https://github.com/vapor/kitura-provider): Use IBM's Kitura HTTP server in Vapor.
|
||||
- [SwiftyBeaver](https://github.com/SwiftyBeaver/SwiftyBeaver-Vapor): Adds the powerful logging of SwiftyBeaver to Vapor.
|
||||
- [APNS](https://github.com/matthijs2704/vapor-apns): Simple APNS Library for Vapor (Swift).
|
||||
- [VaporS3Signer](https://github.com/JustinM1/VaporS3Signer): Generate V4 Auth Header/Pre-Signed URL for AWS S3 REST API
|
||||
- [Flock](https://github.com/jakeheis/Flock): Automated deployment of Swift projects to servers
|
||||
- [VaporFlock](https://github.com/jakeheis/VaporFlock): Use Flock to deploy Vapor applications
|
||||
- [VaporForms](https://github.com/bygri/vapor-forms): Brings simple, dynamic and re-usable web form handling to Vapor.
|
||||
- [Jobs](https://github.com/BrettRToomey/Jobs): A minimalistic job/background-task system for Swift.
|
||||
- [Heimdall](https://github.com/himani93/heimdall): An easy to use HTTP request logger.
|
||||
|
||||
|
||||
## Authors
|
||||
|
||||
[Tanner Nelson](mailto:tanner@vapor.codes), [Logan Wright](mailto:logan@vapor.codes), and the hundreds of members of Vapor.
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# JSON
|
||||
|
||||
JSON is an integral part of Vapor. It powers Vapor's [Config](../settings/config.md) and is incredibly easy to use in both requests and responses.
|
||||
|
||||
## Request
|
||||
|
||||
JSON is automatically available in `request.data` alongside form-urlencoded data and query data. This allows you to focus on making a great API, not worrying about what content types data will be sent in.
|
||||
|
||||
```swift
|
||||
drop.get("hello") { request in
|
||||
guard let name = request.data["name"]?.string else {
|
||||
throw Abort.badRequest
|
||||
}
|
||||
return "Hello, \(name)!"
|
||||
}
|
||||
```
|
||||
|
||||
This will return a greeting for any HTTP method or content type that the `name` is sent as, including JSON.
|
||||
|
||||
### JSON Only
|
||||
|
||||
To specifically target JSON, use the `request.json` property.
|
||||
|
||||
```swift
|
||||
drop.post("json") { request in
|
||||
guard let name = request.json?["name"]?.string else {
|
||||
throw Abort.badRequest
|
||||
}
|
||||
|
||||
return "Hello, \(name)!"
|
||||
}
|
||||
```
|
||||
The above snippet will only work if the request is sent with JSON data.
|
||||
|
||||
## Response
|
||||
|
||||
To respond with JSON, simply wrap your data structure with `JSON(node: )`
|
||||
|
||||
```swift
|
||||
drop.get("version") { request in
|
||||
return try JSON(node: [
|
||||
"version": "1.0"
|
||||
])
|
||||
}
|
||||
```
|
||||
|
||||
## Middleware
|
||||
|
||||
The `JSONMiddleware` is included in the `Droplet`'s middleware by default. You can remove it if you don't want JSON to be parsed.
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Leaf
|
||||
|
||||
Welcome to Leaf. Leaf's goal is to be a simple templating language that can make generating views easier. There's a lot of great templating languages, use what's best for you, maybe that's Leaf! The goals of Leaf are as follows:
|
||||
|
||||
- Small set of strictly enforced rules
|
||||
- Consistency
|
||||
- Parser first mentality
|
||||
- Extensibility
|
||||
|
||||
## Syntax
|
||||
### Structure
|
||||
|
||||
Leaf Tags are made up of 4 Elements:
|
||||
- Token: `#` is the Token
|
||||
- Name: A `string` that identifies the tag
|
||||
- Parameter List: `()` May accept 0 or more arguments
|
||||
- Body (optional): `{}` Must be separated from the Parameter List by a space
|
||||
|
||||
There can be many different usages of these 4 elements depending on the Tag's implementation. Let's look at a few examples of how Leaf's built-in Tags might be used:
|
||||
|
||||
- `#()`
|
||||
- `#(variable)`
|
||||
- `#import("template")`
|
||||
- `#export("link") { <a href="#()"></a> }`
|
||||
- `#index(friends, "0")`
|
||||
- `#loop(friends, "friend") { <li>#(friend.name)</li> }`
|
||||
- `#raw() { <a href="#raw">Anything goes!@#$%^&*</a> }`
|
||||
|
||||
### Using the `#` token in HTML
|
||||
|
||||
The `#` token cannot be escaped. Use the `#()` or `#raw() {}` Tag to output a `#` in a Leaf Template. `#()` => `#`
|
||||
|
||||
### Raw HTML
|
||||
|
||||
All Leaf output is escaped by default. Use the `#raw() {}` Tag for unescaped output.
|
||||
`#raw() { <a href="#link">Link</a> }` => `<a href="#link">Link</a>`
|
||||
> IMPORTANT! Make sure you are not using the `#raw() {}` Tag with user input.
|
||||
|
||||
### Chaining
|
||||
|
||||
The double token: `##` indicates a chain. It can be applied to any standard Tag. If the previous Tag fails, the chained Tag will be given an opportunity to run.
|
||||
|
||||
```
|
||||
#if(hasFriends) ##embed("getFriends")
|
||||
```
|
||||
|
||||
### Leaf's built-in Tags
|
||||
|
||||
#### Token: `#()`
|
||||
|
||||
```
|
||||
#() #()hashtags #()FTW => # #Hashtags #FTW
|
||||
```
|
||||
|
||||
#### Raw: `#raw() {}`
|
||||
|
||||
```
|
||||
#raw() {
|
||||
Do whatever w/ #'s here, this code won't be rendered as leaf document and is not escaped.
|
||||
It's a great place for things like Javascript or large HTML sections.
|
||||
}
|
||||
```
|
||||
|
||||
#### Equal: `#equal(lhs, rhs) {}`
|
||||
|
||||
```
|
||||
#equal(leaf, leaf) { Leaf == Leaf } => Leaf == Leaf
|
||||
#equal(leaf, mustache) { Leaf == Mustache } =>
|
||||
```
|
||||
|
||||
#### Variable: `#(variable)`
|
||||
|
||||
```
|
||||
Hello, #(name)!
|
||||
```
|
||||
|
||||
#### Loop: `#loop(object, "index")`
|
||||
|
||||
```
|
||||
#loop(friends, "friend") {
|
||||
Hello, #(friend.name)!
|
||||
}
|
||||
```
|
||||
#### Index: `#index(object, _ index: Int|String)`
|
||||
|
||||
```
|
||||
Hello, #index(friends, 0)!
|
||||
Hello, #index(friends, "best")!
|
||||
```
|
||||
|
||||
#### If - Else: `#if(bool) ##else() { this }`
|
||||
|
||||
```
|
||||
#if(entering) {
|
||||
Hello, there!
|
||||
} ##if(leaving) {
|
||||
Goodbye!
|
||||
} ##else() {
|
||||
I've been here the whole time.
|
||||
}
|
||||
```
|
||||
|
||||
#### Import: `#import("template")`
|
||||
#### Export: `#export("template") { Leaf/HTML }`
|
||||
#### Extend: `#extend("template")`
|
||||
#### Embed: `#embed("template")`
|
||||
|
||||
> When using these Layout Tags, omit the template file's .leaf extension.
|
||||
|
||||
```
|
||||
/// base.leaf
|
||||
<!DOCTYPE html>
|
||||
#import("html")
|
||||
|
||||
/// html.leaf
|
||||
#extend("base")
|
||||
|
||||
#export("html") { <html>#embed("body")</html> }
|
||||
|
||||
/// body.leaf
|
||||
<body></body>
|
||||
```
|
||||
|
||||
Leaf renders `html.leaf` as:
|
||||
|
||||
```
|
||||
<!DOCTYPE html>
|
||||
<html><body></body></html>
|
||||
```
|
||||
|
||||
### Custom Tags
|
||||
|
||||
Look at the existing tags for advanced scenarios, let's look at a basic example by creating `Index` together. This tag will take two arguments, an array, and an index to access.
|
||||
|
||||
```swift
|
||||
class Index: BasicTag {
|
||||
let name = "index"
|
||||
|
||||
func run(arguments: [Argument]) throws -> Node? {
|
||||
guard
|
||||
arguments.count == 2,
|
||||
let array = arguments[0].value?.nodeArray,
|
||||
let index = arguments[1].value?.int,
|
||||
index < array.count
|
||||
else { return nil }
|
||||
return array[index]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We can now register this Tag in our `main.swift` file with:
|
||||
|
||||
```swift
|
||||
if let leaf = drop.view as? LeafRenderer {
|
||||
leaf.stem.register(Index())
|
||||
}
|
||||
```
|
||||
|
||||
And use it just like we did [above](#index).
|
||||
|
||||
> Note: Use of non-alphanumeric characters in Tag Names is **strongly discouraged** and may be disallowed in future versions of Leaf.
|
||||
|
||||
## Syntax Highlighting
|
||||
|
||||
### Atom
|
||||
|
||||
[language-leaf](https://atom.io/packages/language-leaf) by ButkiewiczP
|
||||
|
||||
### Xcode
|
||||
|
||||
It is not currently possible to implement Leaf Syntax Highlighting in Xcode, however, using Xcode's HTML Syntax Coloring can help a bit. Select one or more Leaf files and then choose Editor > Syntax Coloring > HTML. Your selected Leaf files will now use Xcode's HTML Syntax Coloring. Unfortunately the usefulness of this is limited because this association will be removed when `vapor xcode` is run.
|
||||
|
||||
There appears to be a way to [make Xcode file associations persist](http://stackoverflow.com/questions/9050035/how-to-make-xcode-recognize-a-custom-file-extension-as-objective-c-for-syntax-hi) but that requires a bit more kung-fu.
|
||||
|
||||
### VS Code
|
||||
|
||||
[html-leaf](https://marketplace.visualstudio.com/items?itemName=Francisco.html-leaf) by FranciscoAmado
|
||||
|
||||
### CLion & AppCode
|
||||
|
||||
Some preliminary work has been done to implement a Leaf Plugin for CLion & AppCode but lack of skill and interest in Java has slowed progress! If you have IntelliJ SDK experience and want to help with this, message Tom Holland on [Vapor Slack](http://vapor.team)
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Basic Routing
|
||||
|
||||
Routing is one of the most critical parts of a web framework. The router decides which requests get which responses.
|
||||
|
||||
Vapor has a plethora of functionality for routing including route builders, groups, and collections. In this section, we will look at the basics of routing.
|
||||
|
||||
## Register
|
||||
|
||||
The most basic route includes a method, path, and closure.
|
||||
|
||||
```swift
|
||||
drop.get("welcome") { request in
|
||||
return "Hello"
|
||||
}
|
||||
```
|
||||
|
||||
The standard HTTP methods are available including `get`, `post`, `put`, `patch`, `delete`, and `options`.
|
||||
|
||||
```swift
|
||||
drop.post("form") { request in
|
||||
return "Submitted with a POST request"
|
||||
}
|
||||
```
|
||||
|
||||
You can also use `any` to match all methods.
|
||||
|
||||
## Nesting
|
||||
|
||||
To nest paths (adding `/`s in the URL), simply add commas.
|
||||
|
||||
```swift
|
||||
drop.get("foo", "bar", "baz") { request in
|
||||
return "You requested /foo/bar/baz"
|
||||
}
|
||||
```
|
||||
|
||||
You can also use `/`, but commas are often easier to type and work better with type safe route [parameters](parameters.md).
|
||||
|
||||
## Alternate
|
||||
|
||||
An alternate syntax that accepts a `Method` as the first parameter is also available.
|
||||
|
||||
```swift
|
||||
drop.add(.trace, "welcome") { request in
|
||||
return "Hello"
|
||||
}
|
||||
```
|
||||
|
||||
This may be useful if you want to register routes dynamically or use a less common method.
|
||||
|
||||
## Request
|
||||
|
||||
Each route closure is given a single [Request](../http/request.md). This contains all of the data associated with the request that led to your route closure being called.
|
||||
|
||||
## Response Representable
|
||||
|
||||
A route closure can return in three ways:
|
||||
|
||||
- `Response`
|
||||
- `ResponseRepresentable`
|
||||
- `throw`
|
||||
|
||||
### Response
|
||||
|
||||
A custom [Response](../http/response.md) can be returned.
|
||||
|
||||
```swift
|
||||
drop.get("vapor") { request in
|
||||
return Response(redirect: "http://vapor.codes")
|
||||
}
|
||||
```
|
||||
|
||||
This is useful for creating special responses like redirects. It is also useful for cases where you want to add cookies or other items to the response.
|
||||
|
||||
### Response Representable
|
||||
|
||||
As you have seen in the previous examples, `String`s can be returned in route closures. This is because they conform to [ResponseRepresentable](../http/response-representable.md)
|
||||
|
||||
A lot of types in Vapor conform to this protocol by default:
|
||||
- String
|
||||
- Int
|
||||
- JSON
|
||||
- Model
|
||||
|
||||
```swift
|
||||
drop.get("json") { request in
|
||||
return try JSON(node: [
|
||||
"number": 123,
|
||||
"text": "unicorns",
|
||||
"bool": false
|
||||
])
|
||||
}
|
||||
```
|
||||
|
||||
> If you are curious about what `node:` means, read more about [Node](https://github.com/vapor/node)
|
||||
|
||||
### Throwing
|
||||
|
||||
If you are unable to return a response, you may `throw` any object that conforms to `Error`. Vapor comes with a default error enum `Abort`.
|
||||
|
||||
```swift
|
||||
drop.get("404") { request in
|
||||
throw Abort.notFound
|
||||
}
|
||||
```
|
||||
|
||||
You can customize the message of these errors by using `Abort`
|
||||
|
||||
```swift
|
||||
drop.get("error") { request in
|
||||
throw Abort.custom(status: .badRequest, message: "Sorry 😱")
|
||||
}
|
||||
```
|
||||
|
||||
These errors are caught by default in the `AbortMiddleware` where they are turned into a JSON response like the following.
|
||||
|
||||
```json
|
||||
{
|
||||
error: true,
|
||||
message: "<the message>"
|
||||
}
|
||||
```
|
||||
|
||||
If you want to override this behavior, remove the `AbortMiddleware` from the `Droplet`'s middleware and add your own.
|
||||
|
||||
## Fallback
|
||||
|
||||
Fallback routes allow you to match multiple layers of nesting slashes.
|
||||
|
||||
```swift
|
||||
app.get("anything", "*") { request in
|
||||
return "Matches anything after /anything"
|
||||
}
|
||||
```
|
||||
|
||||
For example, the above route matches all of the following and more:
|
||||
|
||||
- /anything
|
||||
- /anything/foo
|
||||
- /anything/foo/bar
|
||||
- /anything/foo/bar/baz
|
||||
- ...
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Route Collections
|
||||
|
||||
Route collections allow multiple routes and route groups to be organized in different files or modules.
|
||||
|
||||
## Example
|
||||
|
||||
Here is an example of a route collection for the `v1` portion of an API.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
import HTTP
|
||||
import Routing
|
||||
|
||||
class V1Collection: RouteCollection {
|
||||
typealias Wrapped = HTTP.Responder
|
||||
func build<B: RouteBuilder where B.Value == Wrapped>(_ builder: B) {
|
||||
let v1 = builder.grouped("v1")
|
||||
let users = v1.grouped("users")
|
||||
let articles = v1.grouped("articles")
|
||||
|
||||
users.get { request in
|
||||
return "Requested all users."
|
||||
}
|
||||
|
||||
articles.get(Article.self) { request, article in
|
||||
return "Requested \(article.name)"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This class could be place in any file, and we could add it to our droplet or even another route group.
|
||||
|
||||
```swift
|
||||
let v1 = V1Collection()
|
||||
drop.collection(v1)
|
||||
```
|
||||
|
||||
The `Droplet` will then be passed to the `build(_:)` method of your route collection and have the various routes added to it.
|
||||
|
||||
### Breakdown
|
||||
|
||||
For those that are curious, let's break down the route collection line by line to better understand what is going on.
|
||||
|
||||
```swift
|
||||
typealias Wrapped = HTTP.Responder
|
||||
```
|
||||
|
||||
This limits our route collection to adding HTTP responders. While the underlying router is capable of routing any type, Vapor routes HTTP responders exclusively. If we want to use this route collection with Vapor, it's wrapped type needs to match.
|
||||
|
||||
```swift
|
||||
func build<B: RouteBuilder where B.Value == Wrapped>(_ builder: B) {
|
||||
```
|
||||
|
||||
This method accepts a route builder and also verifies that the route builder accepts `Wrapped` or, as defined in the last line, `HTTP.Responder`s. Vapor's `Droplet` and any route [group](group.md) created with Vapor are `RouteBuilder`s that accept HTTP responders.
|
||||
|
||||
```swift
|
||||
let v1 = builder.grouped("v1")
|
||||
```
|
||||
|
||||
From there you can create routes with the `builder` as usual. The `builder: B` will work exactly like a `Droplet` or route [group](group.md). Any methods that work there will work on this builder.
|
||||
|
||||
|
||||
## Empty Initializable
|
||||
|
||||
You can even add `EmptyInitializable` to your route collection if it has an empty `init` method. This will allow you to add the route collection via its type name.
|
||||
|
||||
```swift
|
||||
class V1Collection: RouteCollection, EmptyInitializable {
|
||||
init() { }
|
||||
...
|
||||
```
|
||||
|
||||
Now we can add the collection without initializing it.
|
||||
|
||||
```swift
|
||||
drop.collection(V1Collection.self)
|
||||
```
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Introduction
|
||||
|
||||
Instead of defining all of your request handling logic as Closures in route files, you may wish to organize this behavior
|
||||
using Controller classes. Controllers can group related request handling logic into a single class. Controllers are stored
|
||||
in the `Sources/App/Controllers` directory.
|
||||
|
||||
# Basic Controller
|
||||
|
||||
## Defining Controllers
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
import HTTP
|
||||
|
||||
final class FirstController {
|
||||
func index(request: Request) throws -> ResponseRepresentable {
|
||||
return try JSON(node: [
|
||||
"message": "This is FirstController's index method"
|
||||
])
|
||||
}
|
||||
}
|
||||
```
|
||||
You can define a route to this controller action like so:
|
||||
|
||||
```swift
|
||||
drop.get("getindex") {request in
|
||||
return try FirstController().index(request: request)
|
||||
}
|
||||
```
|
||||
Now, when a request matches the specified route URI, the Index method on the FirstController
|
||||
class will be executed. Of course, the route parameters will also be passed to the method.
|
||||
|
||||
--------
|
||||
|
||||
# Resource Controllers
|
||||
|
||||
Vapor resource routing assigns the typical "CRUD" routes to a controller with a single line of code.
|
||||
|
||||
```swift
|
||||
drop.resource("URI", Controller())
|
||||
```
|
||||
This single route declaration creates multiple routes to handle a variety of actions on the resource.
|
||||
The generated controller will already have methods stubbed for each of these actions, including
|
||||
notes informing you of the HTTP verbs and URIs they handle.
|
||||
|
||||
| Verb | URI | Action |
|
||||
| :-------------: | :-------------: | :-----------: |
|
||||
| GET | test/index | test.index |
|
||||
| POST | test/create | test.create |
|
||||
| GET | test/show | test.show |
|
||||
| PUT | test/replace | test.replace |
|
||||
| PATCH | test/destroy | test.destroy |
|
||||
| DELETE | test/destroy | test.destroy |
|
||||
| DELETE | test/clear | test.clear |
|
||||
|
||||
You can also custom method name, add `makeResource` method in the controller
|
||||
|
||||
```swift
|
||||
func makeResource() -> Resource<First> {
|
||||
return Resource(
|
||||
index: index,
|
||||
store: create,
|
||||
show: show,
|
||||
replace: replace,
|
||||
modify: update,
|
||||
destroy: delete,
|
||||
clear: clear
|
||||
)
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Route Groups
|
||||
|
||||
Grouping routes together makes it easy to add common prefixes, middleware, or hosts to multiple routes.
|
||||
|
||||
Route groups have two different forms: Group and Grouped.
|
||||
|
||||
### Group
|
||||
|
||||
Group (without the "ed" at the end) takes a closure that is passed a `GroupBuilder`.
|
||||
|
||||
```swift
|
||||
drop.group("v1") { v1 in
|
||||
v1.get("users") { request in
|
||||
// get the users
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Grouped
|
||||
|
||||
Grouped returns a `GroupBuilder` that you can pass around.
|
||||
|
||||
```swift
|
||||
let v1 = drop.grouped("v1")
|
||||
v1.get("users") { request in
|
||||
// get the users
|
||||
}
|
||||
```
|
||||
|
||||
### Middleware
|
||||
|
||||
You can add middleware to a group of routes. This is especially useful for authentication.
|
||||
|
||||
```swift
|
||||
drop.group(AuthMiddleware()) { authorized in
|
||||
authorized.get("token") { request in
|
||||
// has been authorized
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Host
|
||||
|
||||
You can limit the host for a group of routes.
|
||||
|
||||
```swift
|
||||
drop.group(host: "vapor.codes") { vapor
|
||||
vapor.get { request in
|
||||
// only responds to requests to vapor.codes
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Chaining
|
||||
|
||||
Groups can be chained together.
|
||||
|
||||
```swift
|
||||
drop.grouped(host: "vapor.codes").grouped(AuthMiddleware()).group("v1") { authedSecureV1 in
|
||||
// add routes here
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Routing Parameters
|
||||
|
||||
Traditional web frameworks leave room for error in routing by using strings for route parameter names and types. Vapor takes advantage of Swift's closures to provide a safer and more intuitive method for accessing route parameters.
|
||||
|
||||
## Type Safe
|
||||
|
||||
To create a type safe route simply replace one of the parts of your path with a `Type`.
|
||||
|
||||
```swift
|
||||
drop.get("users", Int.self) { request, userId in
|
||||
return "You requested User #\(userId)"
|
||||
}
|
||||
```
|
||||
|
||||
This creates a route that matches `users/:id` where the `:id` is an `Int`. Here's what it would look like using manual route parameters.
|
||||
|
||||
```swift
|
||||
drop.get("users", ":id") { request in
|
||||
guard let userId = request.parameters["id"]?.int else {
|
||||
throw Abort.badRequest
|
||||
}
|
||||
|
||||
return "You requested User #\(userId)"
|
||||
}
|
||||
```
|
||||
|
||||
Here you can see that type safe routing saves ~3 lines of code and also prevents runtime errors like misspelling `:id`.
|
||||
|
||||
## String Initializable
|
||||
|
||||
Any type that conforms to `StringInitializable` can be used as a type-safe routing parameter. By default, the following types conform:
|
||||
|
||||
- String
|
||||
- Int
|
||||
- Model
|
||||
|
||||
`String` is the most generic and always matches. `Int` only matches when the string supplied can be turned into an integer. `Model` only matches when the string, used as an identifier, can be used to find the model in the database.
|
||||
|
||||
Our previous example with users can be further simplified.
|
||||
|
||||
```swift
|
||||
drop.get("users", User.self) { request, user in
|
||||
return "You requested \(user.name)"
|
||||
}
|
||||
```
|
||||
|
||||
Here the identifier supplied is automatically used to lookup a user. For example, if `/users/5` is requested, the `User` model will be asked for a user with identifier `5`. If one is found, the request succeeds and the closure is called. If not, a not found error is thrown.
|
||||
|
||||
Here is what this would look like if model didn't conform to `StringInitializable`.
|
||||
|
||||
```swift
|
||||
drop.get("users", Int.self) { request, userId in
|
||||
guard let user = try User.find(userId) else {
|
||||
throw Abort.notFound
|
||||
}
|
||||
|
||||
return "You requested User #\(userId)"
|
||||
}
|
||||
```
|
||||
|
||||
Altogether, type safe routing can save around 6 lines of code from each route.
|
||||
|
||||
### Protocol
|
||||
|
||||
Conforming your own types to `StringInitializable` is easy.
|
||||
|
||||
```swift
|
||||
public protocol StringInitializable {
|
||||
init?(from string: String) throws
|
||||
}
|
||||
```
|
||||
|
||||
Here is what `Model`'s conformance looks like for those who are curious.
|
||||
|
||||
```swift
|
||||
extension Model {
|
||||
public init?(from string: String) throws {
|
||||
if let model = try Self.find(string) {
|
||||
self = model
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `init` method can both `throw` and return `nil`. This allows you to `throw` your own errors. Or, if you want the default error and behavior, just return `nil`.
|
||||
|
||||
### Limits
|
||||
|
||||
Type safe routing is currently limited to three path parts. This is usually remedied by adding route [groups](group.md).
|
||||
|
||||
```swift
|
||||
drop.group("v1", "users") { users in
|
||||
users.get(User.self, "posts", Post.self) { request, user, post in
|
||||
return "Requested \(post.name) for \(user.name)"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The resulting path for the above example is `/v1/users/:userId/posts/:postId`. If you are clamoring for more type safe routing, please let us know and we can look into increasing the limit of three.
|
||||
|
||||
## Manual
|
||||
|
||||
As shown briefly above, you are still free to do traditional routing. This can be useful for especially complex situations.
|
||||
|
||||
```swift
|
||||
drop.get("v1", "users", ":userId", "posts", ":postId", "comments", ":commentId") { request in
|
||||
let userId = try request.parameters.extract("userId") as Int
|
||||
let postId = try request.parameters.extract("postId") as Int
|
||||
let commentId = try request.parameters.extract("commentId") as Int
|
||||
|
||||
return "You requested comment #\(commentId) for post #\(postId) for user #\(userId)"
|
||||
}
|
||||
```
|
||||
|
||||
> Property `request.parameters` is used to extract parameters encoded in the URI _path_ (for example, `/v1/users/1` has a parameter `:userId` equal to `"1"`). In case of parameters passed as a part of a _query_ (e.g. `/v1/search-user?userId=1`), the `request.data` should be used (e.g. `let userId = request.data["userId"]?.string`).
|
||||
|
||||
Request parameters can be accessed either as a dictionary or using the `extract` syntax which throws instead of returning an optional.
|
||||
|
||||
### Groups
|
||||
|
||||
Manual request parameters also work with [groups](group.md).
|
||||
|
||||
```swift
|
||||
let userGroup = drop.grouped("users", ":userId")
|
||||
userGroup.get("messages") { req in
|
||||
let user = try req.parameters.extract("userId") as User
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Query Parameters
|
||||
|
||||
Request query parameters can be accessed either as a dictionary or using the `extract` syntax which throws instead of returning an optional.
|
||||
|
||||
## Optional Syntax
|
||||
|
||||
Optional syntax is the easiest way to handle optional query parameters.
|
||||
|
||||
```swift
|
||||
drop.get("comments") { request in
|
||||
if let rating = request.query?["rating"]?.int {
|
||||
return "You requested comments with rating greater than #\(rating)"
|
||||
}
|
||||
return "You requested all comments"
|
||||
}
|
||||
```
|
||||
|
||||
## Extract Syntax
|
||||
|
||||
Extract syntax might be useful to *enforce* the presence of query parameters and throw an exception if they are not present.
|
||||
To use this syntax first we need to ensure the query object is present with a `guard`.
|
||||
|
||||
```swift
|
||||
drop.get("comments") { request in
|
||||
guard let rating = request.query?["rating"]?.int else {
|
||||
throw Abort.custom(status: .preconditionFailed, message: "Please include a rating")
|
||||
}
|
||||
return "You requested comments with rating greater than #\(rating)"
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Sessions
|
||||
|
||||
Sessions help you store information about a user between requests. As long as the client supports cookies, sessions are easy to create.
|
||||
|
||||
## Middleware
|
||||
|
||||
Enable sessions on your `Droplet` by adding an instance of `SessionMiddleware`.
|
||||
|
||||
```swift
|
||||
import Sessions
|
||||
|
||||
let memory = MemorySessions()
|
||||
let sessions = SessionsMiddleware(sessions: memory)
|
||||
```
|
||||
|
||||
Then add to the `Droplet`.
|
||||
|
||||
```
|
||||
let drop = Droplet()
|
||||
drop.middleware.append(sessions)
|
||||
```
|
||||
|
||||
> Note: If you'd like to enable or disable the middleware based on config files, check out [middleware](../vapor/middleware.md).
|
||||
|
||||
## Request
|
||||
|
||||
After `SessionMiddleware` has been enabled, you can access the `req.sessions()` method to get access to session data.
|
||||
|
||||
```swift
|
||||
let data = try req.session().data
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
Let's create an example that remembers the user's name.
|
||||
|
||||
### Store
|
||||
|
||||
```swift
|
||||
drop.post("remember") { req in
|
||||
guard let name = req.data["name"]?.string else {
|
||||
throw Abort.badRequest
|
||||
}
|
||||
|
||||
try req.session().data["name"] = Node.string(name)
|
||||
|
||||
return "Remebered name."
|
||||
}
|
||||
```
|
||||
|
||||
On `POST /remember`, fetch a `name` from the request input, then store this name into the session data.
|
||||
|
||||
### Fetch
|
||||
|
||||
On `GET /remember`, fetch the `name` from the session data and return it.
|
||||
|
||||
```swift
|
||||
drop.get("remember") { req in
|
||||
guard let name = try req.session().data["name"]?.string else {
|
||||
return throw Abort.custom(status: .badRequest, message: "Please POST the name first.")
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
```
|
||||
|
||||
## Cookie
|
||||
|
||||
The session will be stored using the `vapor-session` cookie.
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
!!! warning
|
||||
This section may contain outdated information.
|
||||
|
||||
# Config
|
||||
|
||||
An application's configuration settings. Cloud applications generally require complex configurations that can adjust based on their environment. Vapor intends to provide a flexible configuration interaction that can be customized for a given user.
|
||||
|
||||
## QuickStart
|
||||
|
||||
For Vapor applications, configuration files are expected to be nested under a top level folder named `Config`. Here's an example of a basic config featuring a single `servers` configuration.
|
||||
|
||||
```bash
|
||||
./
|
||||
├── Config/
|
||||
│ ├── servers.json
|
||||
```
|
||||
|
||||
And an example of how this might look:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"http": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 8080
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
What that's saying, is that our application should start a single server named 'http' serving port `8080` on host `0.0.0.0`. This represents the following url: `http://localhost:8080`.
|
||||
|
||||
### Custom Keys
|
||||
|
||||
Let's add a custom key to the `servers.json` file:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"http": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 8080,
|
||||
"custom-key": "custom value"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This can be accessed from your application's config using the following.
|
||||
|
||||
```swift
|
||||
let customValue = drop.config["servers", "http", "custom-key"]?.string ?? "default"
|
||||
```
|
||||
|
||||
That's it, feel free to add and utilize keys as necessary to make your application configuration easier.
|
||||
|
||||
## Config Syntax
|
||||
|
||||
You can access your config directory with the following syntax. `app.config[<#file-name#>, <#path#>, <#to#>, <#file#>]`. For example, let's hypothesize that in addition to the `servers.json` file we mentioned earlier, there is also a `keys.json` that looks like this:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"test-names": [
|
||||
"joe",
|
||||
"jane",
|
||||
"sara"
|
||||
],
|
||||
"mongo": {
|
||||
"url" : "www.customMongoUrl.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We can access this file by making sure the first argument in our subscript is keys. To get the first name in our list:
|
||||
|
||||
```swift
|
||||
let name = drop.config["keys", "test-names", 0]?.string ?? "default"
|
||||
```
|
||||
|
||||
Or our mongo url:
|
||||
|
||||
```swift
|
||||
let mongoUrl = drop.config["keys", "mongo", "url"]?.string ?? "default"
|
||||
```
|
||||
|
||||
## Advanced Configurations
|
||||
|
||||
Having the default `servers.json` is great, but what about more complex scenarios. For example, what if we want a different host in production and in development? These complex scenarios can be achieved by adding additional folders to our `Config/` directory. Here's an example of a folder structure that's setup for production and development environments.
|
||||
|
||||
```bash
|
||||
WorkingDirectory/
|
||||
├── Config/
|
||||
│ ├── servers.json
|
||||
│ ├── production/
|
||||
│ │ └── servers.json
|
||||
│ ├── development/
|
||||
│ │ └── servers.json
|
||||
│ └── secrets/
|
||||
│ └── servers.json
|
||||
```
|
||||
|
||||
> You can specify the environment through the command line by using --env=. Custom environments are also available, a few are provided by default: production, development, and testing.
|
||||
|
||||
```bash
|
||||
vapor run --env=production
|
||||
```
|
||||
|
||||
### PRIORITY
|
||||
|
||||
Config files will be accessed in the following priority.
|
||||
|
||||
1. CLI (see below)
|
||||
2. Config/secrets/
|
||||
3. Config/name-of-environment/
|
||||
4. Config/
|
||||
|
||||
What this means is that if a user calls `app.config["servers", "host"]`, the key will be searched in the CLI first, then the `secrets/` directory, then the top level default configs.
|
||||
|
||||
> `secrets/` directory should very likely be added to the gitignore.
|
||||
|
||||
### EXAMPLE
|
||||
|
||||
Let's start with the following JSON files.
|
||||
|
||||
#### `servers.json`
|
||||
|
||||
```JSON
|
||||
{
|
||||
"http": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 9000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `production/servers.json`
|
||||
|
||||
```JSON
|
||||
{
|
||||
"http": {
|
||||
"host": "127.0.0.1",
|
||||
"port": "$PORT"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> The `"$NAME"` syntax is available for all values to access environment variables.
|
||||
|
||||
Please notice that `servers.json`, and `production/servers.json` both declare the same keys: `host`, and `port`. In our application, we'll call:
|
||||
|
||||
```swift
|
||||
// will load 0.0.0.0 or 127.0.0.1 based on above config
|
||||
let host = drop.config["servers", "http", "host"]?.string ?? "0.0.0.0"
|
||||
// will load 9000, or environment variable port.
|
||||
let port = drop.config["servers", "http", "port"]?.int ?? 9000
|
||||
```
|
||||
|
||||
## COMMAND LINE
|
||||
|
||||
In addition to json files nested within the `Config/` directory, we can also use the command line to pass arguments into our config. By default, these values will be set as the "cli" file, but more complex options are also available.
|
||||
|
||||
#### 1. `--KEY=VALUE`
|
||||
|
||||
Arguments set through the command line can be accessed through config's cli file. For example, the following CLI command:
|
||||
|
||||
```bash
|
||||
--mongo-password=$MONGO_PASSWORD
|
||||
```
|
||||
|
||||
would be accessible within your application by using the following:
|
||||
|
||||
```swift
|
||||
let mongoPassword = drop.config["cli", "mongo-password"]?.string
|
||||
```
|
||||
|
||||
#### 2. `--CONFIG:FILE-NAME.KEY=CUSTOM-VALUE`
|
||||
|
||||
If you want command line arguments set to a file besides "cli", you can use this more advanced specification. For example, the following CLI command:
|
||||
|
||||
```bash
|
||||
--config:keys.analytics=124ZH61F
|
||||
```
|
||||
|
||||
would be accessible within your application by using the following:
|
||||
|
||||
```swift
|
||||
let analyticsKey = drop.config["keys", "analytics"]?.string
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue