Merge branch 'master' into style-guide

This commit is contained in:
Jimmy McDermott 2018-08-22 07:44:24 -05:00
commit 9cfba7670d
16 changed files with 782 additions and 215 deletions

211
3.0/docs/auth/api.md Normal file
View File

@ -0,0 +1,211 @@
# API Authentication
This guide will introduce you to stateless authentication—a method of authentication commonly used for protecting API endpoints.
## Concept
In Computer Science (especially web frameworks), the concept of Authentication means verifying the _identity_ of a user. This is not to be confused with Authorization which verifies _privileges_ to a given resource
This package allows you to implement stateless authorization using the following tools:
- *`"Authorization"` header*: Used to send credentials in an HTTP request.
- *Middleware*: Detects credentials in request and fetches authenticated user.
- *Model*: Represents an authenticated user and its identifying information.
### Authorization Header
This packages makes use of two common authorization header formats: basic and bearer.
#### Basic
Basic authorization contains a username and password. They are joined together by a `:` and then base64 encoded.
A basic authorization header containing the username `Alladin` and password `OpenSesame` would look like this:
```http
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l
```
Although basic authorization can be used to authenticate each request to your server, most web applications usually create an ephemeral token for this purpose instead.
#### Bearer
Bearer authorization simply contains a token. A bearer authorization header containing the token `cn389ncoiwuencr` would look like this:
```http
Authorization: Bearer cn389ncoiwuencr
```
The bearer authorization header is very common in APIs since it can be sent easily with each request and contain an ephemeral token.
### Middleware
The usage of Middleware is critical to this package. If you are not familiar with how Middleware works in Vapor, feel free to brush up by reading [Vapor → Middleware](../vapor/middleware.md).
Authentication middleware is responsible for reading the credentials from the request and fetching the identifier user. This usually means checking the `"Authorization"` header, parsing the credentials, and doing a database lookup.
For each model / authentication method you use, you will add one middleware to your application. All of this package's middlewares are composable, meaning you can add multiple middlewares to one route and they will work together. If one middleware fails to authorize a user, it will simply forward the request for the next middleware to try.
If you would like to ensure that a certain model's authentication has succeeded _before_ running your route, you must add an instance of [`GuardAuthenticationMiddleware`](https://api.vapor.codes/auth/latest/Authentication/Classes/GuardAuthenticationMiddleware.html).
### Model
Fluent models are _what_ the middlewares authenticate. Learn more about models by reading [Fluent → Models](../fluent/models.md). If authentication is succesful, the middleware will have fetched your model from the database and stored it on the request. This means you can access an authenticated model synchronously in your route.
In your route closure, you use the following methods to check for authentication:
- `authenticated(_:)`: Returns type if authenticated, `nil` if not.
- `isAuthenticated(_:)`: Returns `true` if supplied type is authenticated.
- `requireAuthenticated(_:)`: Returns type if authenticated, `throws` if not.
Typical usage looks like the following:
```swift
// use middleware to protect a group
let protectedGroup = router.group(...)
// add a protected route
protectedGroup.get("test") { req in
// require that a User has been authed by middleware or throw
let user = try req.requireAuthenticated(User.self)
// say hello to the user
return "Hello, \(user.name)."
}
```
## Methods
This package supports two basic types of stateless authentication.
- _Token_: Uses the bearer authorization header.
- _Password_: Uses the basic authorization header.
For each authentication type, there is a separate middleware and model protocol.
### Password Authentication
Password authentication uses the basic authorization header (username and password) to verify a user. With this method, the username and password must be sent with each request to a protected endpoint.
To use password authentication, you will first need to conform your Fluent model to `PasswordAuthenticatable`.
```swift
extension User: PasswordAuthenticatable {
/// See `PasswordAuthenticatable`.
static var usernameKey: WritableKeyPath<User, String> {
return \.email
}
/// See `PasswordAuthenticatable`.
static var passwordKey: WritableKeyPath<User, String> {
return \.passwordHash
}
}
```
Note that the `passwordKey` should point to the _hashed_ password. Never store passwords in plaintext.
Once you have created an authenticatable model, the next step is to add middleware to your protected route.
```swift
// Use user model to create an authentication middleware
let password = User.basicAuthMiddleware(using: BCryptDigest())
// Create a route closure wrapped by this middleware
router.grouped(password).get("hello") { req in
///
}
```
Here we are using `BCryptDigest` as the [`PasswordVerifier`](https://api.vapor.codes/auth/latest/Authentication/Protocols/PasswordVerifier.html) since we are assuming the user's password is stored as a BCrypt hash.
Now, to fetch the authenticated user in the route closure, you can use [`requireAuthenticated(_:)`](https://api.vapor.codes/auth/latest/Authentication/Extensions/Request.html#/s:5Vapor7RequestC14AuthenticationE20requireAuthenticatedxxmKAD15AuthenticatableRzlF).
```swift
let user = try req.requireAuthenticated(User.self)
return "Hello, \(user.name)."
```
The `requireAuthenticated` method will automatically throw an appropriate unauthorized error if the valid credentials were not supplied. Because of this, using [`GuardAuthenticationMiddleware`](https://api.vapor.codes/auth/latest/Authentication/Classes/GuardAuthenticationMiddleware.html) to protect the route from unauthenticated access is not required.
### Token Authentication
Token authentication uses the bearer authorization header (token) to lookup a token and its related user. With this method, the token must be sent with each request to a protected endpoint.
Unlike password authentication, token authentication relies on _two_ Fluent models. One for the token and one for the user. The token model should be a _child_ of the user model.
Here is an example of a very basic `User` and associated `UserToken`.
```swift
struct User: Model {
var id: Int?
var name: String
var email: String
var passwordHash: String
var tokens: Children<User, UserToken> {
return children(\.userID)
}
}
struct UserToken: Model {
var id: Int?
var string: String
var userID: User.ID
var user: Parent<UserToken, User> {
return parent(\.userID)
}
}
```
The first step to using token authentication is to conform your user and token models to their respective `Authenticatable` protocols.
```swift
extension UserToken: Token {
/// See `Token`.
typealias UserType = User
/// See `Token`.
static var tokenKey: WritableKeyPath<UserToken, String> {
return \.string
}
/// See `Token`.
static var userIDKey: WritableKeyPath<UserToken, User.ID> {
return \.userID
}
}
```
Once the token is conformed to `Token`, setting up the user model is easy.
```swift
extension User: TokenAuthenticatable {
/// See `TokenAuthenticatable`.
typealias TokenType = UserToken
}
```
Once you have conformed your models, the next step is to add middleware to your protected route.
```swift
// Use user model to create an authentication middleware
let token = User.tokenAuthMiddleware()
// Create a route closure wrapped by this middleware
router.grouped(token).get("hello") {
//
}
```
Now, to fetch the authenticated user in the route closure, you can use [`requireAuthenticated(_:)`](https://api.vapor.codes/auth/latest/Authentication/Extensions/Request.html#/s:5Vapor7RequestC14AuthenticationE20requireAuthenticatedxxmKAD15AuthenticatableRzlF).
```swift
let user = try req.requireAuthenticated(User.self)
return "Hello, \(user.name)."
```
The `requireAuthenticated` method will automatically throw an appropriate unauthorized error if the valid credentials were not supplied. Because of this, using [`GuardAuthenticationMiddleware`](https://api.vapor.codes/auth/latest/Authentication/Classes/GuardAuthenticationMiddleware.html) to protect the route from unauthenticated access is not required.

View File

@ -0,0 +1,49 @@
# Getting Started with Auth
Auth ([vapor/auth](https://github.com/vapor/auth)) is a framework for adding authentication to your application. It builds on top of [Fluent](../fluent/getting-started) by using models as the basis of authentication.
!!! tip
There is a Vapor API template with Auth pre-configured available.
See [Getting Started &rarr; Toolbox &rarr; Templates](../getting-started/toolbox.md#templates).
Let's take a look at how you can get started using Auth.
## Package
The first step to using Auth is adding it as a dependency to your project in your SPM package manifest file.
```swift
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "MyApp",
dependencies: [
/// Any other dependencies ...
// 👤 Authentication and Authorization framework for Fluent.
.package(url: "https://github.com/vapor/auth.git", from: "2.0.0"),
],
targets: [
.target(name: "App", dependencies: ["Authentication", ...]),
.target(name: "Run", dependencies: ["App"]),
.testTarget(name: "AppTests", dependencies: ["App"]),
]
)
```
Auth currently provides one module `Authentication`. In the future, there will be a separate module named `Authorization` for performing more advanced auth.
## Provider
Once you have succesfully added the Auth package to your project, the next step is to configure it in your application. This is usually done in [`configure.swift`](../getting-started/structure.md#configureswift).
```swift
import Authentication
// register Authentication provider
try services.register(AuthenticationProvider())
```
That's it for basic setup. The next step is to create an authenticatable model.

102
3.0/docs/auth/web.md Normal file
View File

@ -0,0 +1,102 @@
# Web Authentication
This guide will introduce you to session-based authentication&mdash;a method of authentication commonly used for protecting web (front-end) pages.
## Concept
In Computer Science (especially web frameworks), the concept of Authentication means verifying the _identity_ of a user. This is not to be confused with Authorization which verifies _privileges_ to a given resource
Session-based authentication uses cookies to re-authenticate users with each request to your website. It performs this logic via a middleware that you add to your application or specific routes.
You are responsible for initially authenticating the user to your application (either manually or by using methods from the [Stateless (API)](api.md) section). Once you have authenticated the user once, the middleware will use cookies to re-authenticate the user on subsequent requests automatically.
## Example
Let's take a look at a simple session-based authentication example.
### Pre-requisites
In order to do session-based authentication, you must have a way to initially authenticate your user. In other words, you need a method for logging them in. The [Stateless (API)](api.md) section covers some of these methods, but it's entirely up to you.
You will also need to have sessions configured for your application. You can learn more about this in [Vapor &rarr; Sessions](../vapor/sessions.md). Usually this will require adding the `SessionsMiddleware` and choosing a `KeyedCache`.
```swift
config.prefer(MemoryKeyedCache.self, for: KeyedCache.self)
var middlewares = MiddlewareConfig()
middlewares.use(SessionsMiddleware.self)
// ...
services.register(middlewares)
```
### Model
Once you are ready to enable session-based authentication, the first step is to conform your user model to [`SessionAuthenticatable`](https://api.vapor.codes/auth/latest/Authentication/Protocols/SessionAuthenticatable.html).
```swift
extension User: SessionAuthenticatable { }
```
The conformance is empty since all of the required methods have default implementations.
### Middleware
Once your model is conformed, you can use it to create an `AuthenticationSessionsMiddleware`.
```swift
// create auth sessions middleware for user
let session = User.authSessionsMiddleware()
// create a route group wrapped by this middleware
let auth = router.grouped(session)
// create new route in this route group
auth.get("hello") { req -> String in
//
}
```
Create a route group wrapped by this middleware using the route grouping methods. Any routes you want to support session-based authentication should use this route group.
You can also apply this middleware globally to your application if you'd like.
### Route
Inside of any route closure wrapped by the session auth middleware, we can access our authenticated model using the [`authenticated(_:)`](https://api.vapor.codes/auth/latest/Authentication/Extensions/Request.html#/s:5Vapor7RequestC14AuthenticationE13authenticatedxSgxmKAD15AuthenticatableRzlF) methods.
```swift
let user = try req.requireAuthenticated(User.self)
return "Hello, \(user.name)!"
```
Here we are using the method prefixed with `require` to throw an error if the user was not succesfully authenticated.
If you visit this route now, you should see a message saying no user has been authenticated. Let's resolve this by creating a way for our user to login!
!!! note
Use [`GuardAuthenticationMiddleware`](https://api.vapor.codes/auth/latest/Authentication/Classes/GuardAuthenticationMiddleware.html) to protect routes that do not call `requireAuthenticated(_:)` or otherwise require authentication.
### Login
For the sake of this example, we will just log in a pre-defined user with a fixed ID.
```swift
auth.get("login") { req -> Future<String> in
return User.find(1, on: req).map { user in
guard let user = user else {
throw Abort(.badRequest)
}
try req.authenticate(user)
return "Logged in"
}
}
```
Remember that this login route must go through the `AuthenticationSessionsMiddleware`. The middleware is what will detect that we have authenticated a user and later restore the authentication automatically.
Upon visiting `/hello`, you should recieve an error message stating that you are not logged in. If you then visit `/login` first, followed by `/hello` you should see that you are now successfully logged in!
If you open the inspector, you should notice a new cookie named `"vapor-session"` has been added to your browser.

41
3.0/docs/crypto/otp.md Normal file
View File

@ -0,0 +1,41 @@
# TOTP and HOTP
One-time passwords (OTPs) are commonly used as a form of [two-factor authentication](https://en.wikipedia.org/wiki/Multi-factor_authentication). Crypto can be used to generate both TOTP and HOTP in accordance with [RFC 6238](https://tools.ietf.org/html/rfc6238) and [RFC 4226](https://tools.ietf.org/html/rfc4226
) respectively.
- **TOTP**: Time-based One-Time Password. Generates password by combining shared secret with unix timestamp.
- **HOTP**: HMAC-Based One-Time Password. Similar to TOTP, except an incrementing counter is used instead of a timestamp. Each time a new OTP is generated, the counter increments.
## Generating OTP
OTP generation is similar for both TOTP and HOTP. The only difference is that HOTP requires the current counter to be passed.
```swift
import Crypto
// Generate TOTP
let code = TOTP.SHA1.generate(secret: "hi")
print(code) "123456"
// Generate HOTP
let code = HOTP.SHA1.generate(secret: "hi", counter: 0)
print(code) "208503"
```
View the API docs for [`TOTP`](#fixme) and [`HOTP`](#fixme) for more information.
## Base 32
TOTP and HOTP shared secrets are commonly transferred using Base32 encoding. Crypto provides conveniences for converting to/from Base32.
```swift
import Crypto
// shared secret
let secret: Data = ...
// base32 encoded secret
let encodedSecret = secret.base32EncodedString()
```
See Crypto's [`Data`](#fixme) extensions for more information.

View File

@ -22,7 +22,7 @@ cd Hello
## Generate Xcode Project
Let's now use the [Vapor Toolbox's `xcode`](toolbox#xcode) command to generate an Xcode project.
Let's now use the [Vapor Toolbox's `xcode`](toolbox.md) command to generate an Xcode project.
This will allow us to build and run our app from inside of Xcode, just like an iOS app.
```sh

View File

@ -40,6 +40,7 @@ a different template by passing the `--template` flag.
|------|------------------|-----------------------------------|
| API | `--template=api` | JSON API with Fluent database. |
| Web | `--template=web` | HTML website with Leaf templates. |
| Auth | `--template=auth-template`| JSON API with Fluent DB and Auth. |
!!! info
There are lots of unofficial Vapor templates on GitHub under the <a href="https://github.com/search?utf8=✓&q=topic%3Avapor+topic%3Atemplate&type=Repositories" target="_blank">`vapor` + `template` topcs &rarr;</a>.

View File

@ -0,0 +1,38 @@
# Getting Started with JWT
JWT ([vapor/jwt](https://github.com/vapor/jwt)) is a package for parsing and serializing **J**SON **W**eb **T**okens supporting both HMAC and RSA signing. JWTs are often used for implementing _decentralized_ authentication and authorization.
Since all of the authenticated user's information can be embedded _within_ a JWT, there is no need to query a central authentication server with each request to your service. Unlike standard bearer tokens that must be looked up in a centralized database, JWTs contain cryptographic signatures that can be used to independently verify their authenticity.
If implemented correctly, JWTs can be a powerful tool for making your application [horizontally scalable](https://stackoverflow.com/questions/11707879/difference-between-scaling-horizontally-and-vertically-for-databases). Learn more about JWT at [jwt.io](https://jwt.io).
!!! tip
If your goal is not horizontal scalability, a standard bearer token will likely be a better solution. JWTs have some downsides worth considering such as the inability to revoke a token once it has been issued (until it expires normally).
Let's take a look at how you can get started using JWT.
## Package
The first step to using JWT is adding it as a dependency to your project in your SPM package manifest file.
```swift
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "MyApp",
dependencies: [
/// Any other dependencies ...
// 🔏 JSON Web Token signing and verification (HMAC, RSA).
.package(url: "https://github.com/vapor/jwt.git", from: "3.0.0"),
],
targets: [
.target(name: "App", dependencies: ["JWT", ...]),
.target(name: "Run", dependencies: ["App"]),
.testTarget(name: "AppTests", dependencies: ["App"]),
]
)
```
That's it for basic setup. The next section will give you an overview of the package's APIs. As always, feel free to visit the [API Docs](https://api.vapor.codes/jwt/latest/JWT/index.html) for more specific information.

186
3.0/docs/jwt/overview.md Normal file
View File

@ -0,0 +1,186 @@
# Using JWT
JSON Web Tokens are a great tool for implementing _decentralized_ authentication and authorization. Once you are finished configuring your app to use the JWT package (see [JWT &rarr; Getting Started](getting-started.md)), you are ready to begin using JWTs in your app.
## Structure
Like other forms of token-based auth, JWTs are sent using the bearer authorization header.
```http
GET /hello HTTP/1.1
Authorization: Bearer <token>
...
```
In the example HTTP request above, `<token>` would be replaced by the serialized JWT. [jwt.io](https://jwt.io) hosts an online tool for parsing and serializing JWTs. We will use that tool to create a token for testing.
![JWT.io](https://user-images.githubusercontent.com/1342803/44101613-ce328e04-9fb5-11e8-9aed-2d9900d0c40c.png)
### Header
The header is mainly used to specify which algorithm was used to generate the token's signature. This is used by the accepting app to verify the token's authenticity.
Here is the raw JSON data for our header:
```json
{
"alg": "HS256",
"typ": "JWT"
}
```
This specifies the HMAC SHA-256 signing algorithm and that our token is indeed a JWT.
### Payload
The payload is where you store information to identify the authenticated user. You can store any data you want here, but be careful not to store too much as some web browsers limit HTTP header sizes.
The payload is also where you store _claims_. Claims are standardized key / value pairs that many JWT implementations can recognize and act on automatically. A commonly used claim is _Expiration Time_ which stores the token's expiration date as a unix timestamp at key `"exp"`. See a full list of supported claims in [RFC 7519 &sect; 4.1](https://tools.ietf.org/html/rfc7519#section-4.1).
To keep things simple, we will just include our user's identifier and name in the payload:
```json
{
"id": 42,
"name": "Vapor Developer"
}
```
### Secret
Last but not least is the secret key used to sign and verify the JWT. For this example, we are using the `HS256` algorithm (specified in the JWT header). HMAC algorithms use a single secret key for both signing and verifying.
To keep things simple, we will use the following string as our key:
```
secret
```
Other algorithms, like RSA, use asymmetric (public and private) keys. With these types of algorithms, only the _private_ key is able to create (sign) JWTs. Both the _public_ and _private_ keys can verify JWTs. This allows for an added layer of security as you can distribute the public key to services that should only be able to verify tokens, not create them.
### Serialized
Finally, here is our fully serialized token. This will be sent via the bearer authorization header.
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NDIsIm5hbWUiOiJWYXBvciBEZXZlbG9wZXIifQ.__Dm_tr1Ky2VYhZNoN6XpEkaRHjtRgaM6HdgDFcc9PM
```
Each segment is separated by a `.`. The overall structure of the token is the following:
```
<header>.<payload>.<signature>
```
Note that the header and payload segments are simply base64-url encoded JSON. It is important to remember that all information your store in a normal JWT is publically readable.
## Parse
Let's take a look at how to parse and verify incoming JWTs.
### Payload
First, we need to create a `Codable` type that represents our payload. This should also conform to [`JWTPayload`](https://api.vapor.codes/jwt/latest/JWT/Protocols/JWTPayload.html).
```swift
struct User: JWTPayload {
var id: Int
var name: String
func verify(using signer: JWTSigner) throws {
// nothing to verify
}
}
```
Since our simple payload does not include any claims, we can leave the `verify(using:)` method empty for now.
### Route
Now that our payload type is ready, we can parse and verify an incoming JWT.
```swift
import JWT
import Vapor
router.get("hello") { req -> String in
// fetches the token from `Authorization: Bearer <token>` header
guard let bearer = req.http.headers.bearerAuthorization else {
throw Abort(.unauthorized)
}
// parse JWT from token string, using HS-256 signer
let jwt = try JWT<User>(from: bearer.token, verifiedUsing: .hs256(key: "secret"))
return "Hello, \(jwt.payload.name)!"
}
```
This snippet creates a new route at `GET /hello`. The first part of the route handler fetches the `<token>` value from the bearer authorization header. The second part uses the [`JWT`](https://api.vapor.codes/jwt/latest/JWT/Structs/JWT.html) struct to parse the token using an `HS256` signer.
Once the JWT is parsed, we access the [`payload`](https://api.vapor.codes/jwt/latest/JWT/Structs/JWT.html#/s:3JWTAAV7payloadxvp) property which contains an instance of our `User` type. We then access the `name` property to say hello!
Run the following request and check the output:
```http
GET /hello HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NDIsIm5hbWUiOiJWYXBvciBEZXZlbG9wZXIifQ.__Dm_tr1Ky2VYhZNoN6XpEkaRHjtRgaM6HdgDFcc9PM
Content-Length: 0
```
You should see the following response:
```http
HTTP/1.1 200 OK
Content-Length: 23
Hello, Vapor Developer!
```
## Serialize
Let's take a look at how to create and sign a JWT.
### Payload
First, we need to create a `Codable` type that represents our payload. This should also conform to [`JWTPayload`](https://api.vapor.codes/jwt/latest/JWT/Protocols/JWTPayload.html).
```swift
struct User: JWTPayload {
var id: Int
var name: String
func verify(using signer: JWTSigner) throws {
// nothing to verify
}
}
```
Since our simple payload does not include any claims, we can leave the `verify(using:)` method empty for now.
### Route
Now that our payload type is ready, we can generate a JWT.
```swift
router.post("login") { req -> String in
// create payload
let user = User(id: 42, name: "Vapor Developer")
// create JWT and sign
let data = try JWT(payload: user).sign(using: .hs256(key: "secret"))
return String(data: data, encoding: .utf8) ?? ""
}
```
This snippet creates a new route at `POST /login`. The first part of the route handler creates an instance of our `User` payload type. The second part creates an instance of `JWT` using our payload, and calls the [`sign(using:)`](https://api.vapor.codes/jwt/latest/JWT/Structs/JWT.html#/s:3JWTAAV4sign10Foundation4DataVAA9JWTSignerC5using_tKF) method. This method returns `Data`, which we convert to a `String`.
If you visit this route, you should get the following output:
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NDIsIm5hbWUiOiJWYXBvciBEZXZlbG9wZXIifQ.__Dm_tr1Ky2VYhZNoN6XpEkaRHjtRgaM6HdgDFcc9PM
```
If you plug that JWT into [jwt.io](https://jwt.io) and enter the secret (`secret`), you should see the encoded data and a message "Signature Verified".

View File

@ -1,100 +0,0 @@
# Redis basic usage
To interact with Redis, you first need to construct a Redis client.
The Redis library primarily supports TCP sockets.
This requires a hostname, port and worker. The eventloop will be used for Redis' Socket. The hostname and port have a default. The hostname is defaulted to `localhost`, and the port to Redis' default port `6379`.
```swift
let client = try RedisClient.connect(on: worker) // Future<RedisClient>
```
The `connect` method will return a future containing the TCP based Redis Client.
## Redis Data Types
Redis has 6 data types:
- null
- Int
- Error
- Array
- Basic String (used for command names and basic replies only)
- Bulk String (used for Strings and binary data blobs)
You can instantiate one from the static functions and variables on `RedisData`.
```swift
let null = RedisData.null
let helloWorld = RedisData.bulkString("Hello World")
let three = RedisData.integer(3)
let oneThroughTen = RedisData.array([
.integer(1),
.integer(2),
.integer(3),
.integer(4),
.integer(5),
.integer(6),
.integer(7),
.integer(8),
.integer(9),
.integer(10)
])
```
The above is the explicit way of defining Redis Types. You can also use literals in most scenarios:
```swift
let array = RedisData.array([
[
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
],
"Hello World",
"One",
"Two",
.null,
.null,
"test"
])
```
## CRUD using Redis
From here on it is assumed that your client has been successfully created and is available in the variable `client` as a `RedisClient`.
### Creating a record
Creating a record is done using a `RedisData` for a value and a key.
```swift
client.set("world", forKey: "hello")
```
This returns a future that'll indicate successful or unsuccessful insertion.
### Reading a record
Reading a record is similar, only you'll get a warning if you don't use the returned future.
The `Future<RedisData>` for the key "hello" will be "world" if you created the record as shown above.
```swift
let futureRecord = client.getData(forKey: "hello") // Future<RedisData>
```
### Deleting a record
Deleting a record is similar but allows querying the keys, too.
```swift
client.delete(keys: ["hello"])
```
Where the above command will remove the key "hello", the next command will delete **all** keys from the Redis database.
```swift
client.delete(keys: ["*"])
```

View File

@ -1,17 +0,0 @@
# Custom commands
Many commands are not (yet) implemented by the driver using a convenience function. This does not mean the feature/command is not usable.
[(Almost) all functions listed here](https://redis.io/commands) work out of the box using custom commands.
## Usage
The Redis client has a `run` function that allows you to run these commands.
The following code demonstrates a "custom" implementation for [GET](https://redis.io/commands/get).
```swift
let future = client.run(command: "GET", arguments: ["my-key"]) // Future<RedisData>
```
This future will contain the result as specified in the article on the redis command page or an error.

View File

@ -1,36 +1,68 @@
!!! warning
Redis 3.0 is still in beta. Some documentation may be missing or out of date.
# Getting Started with Redis
# Redis
Redis ([vapor/redis](https://github.com/vapor/redis)) is a pure-Swift, event-driven, non-blocking Redis client built on top of SwiftNIO.
Redis is a Redis client library that can communicate with a Redis database.
You can use this package to interact send Redis commands to your server directly, or as a cache through Vapor's `KeyedCache` interface.
### What is Redis?
Let's take a look at how you can get started using Redis.
Redis is an in-memory data store used as a database, cache and message broker. It supports most common data structures. Redis is most commonly used for caching data such as sessions and notifications (between multiple servers).
## Package
Redis works as a key-value store, but allows querying the keys, unlike most databases.
## With and without Vapor
To include it in your package, add the following to your `Package.swift` file.
The first step to using Redis is adding it as a dependency to your project in your SPM package manifest file.
```swift
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "Project",
name: "MyApp",
dependencies: [
...
.package(url: "https://github.com/vapor/redis.git", .upToNextMajor(from: "3.0.0")),
/// Any other dependencies ...
// ⚡Non-blocking, event-driven Redis client.
.package(url: "https://github.com/vapor/redis.git", from: "3.0.0"),
],
targets: [
.target(name: "Project", dependencies: ["Redis", ... ])
.target(name: "App", dependencies: ["Redis", ...]),
.target(name: "Run", dependencies: ["App"]),
.testTarget(name: "AppTests", dependencies: ["App"]),
]
)
```
If this is your first time adding a dependency, you should read our introduction to [Package.swift](../getting-started/spm.md).
## Provider
Once you have succesfully added the Auth package to your project, the next step is to configure it in your application. This is usually done in [`configure.swift`](../getting-started/structure.md#configureswift).
```swift
import Redis
// register Redis provider
try services.register(RedisProvider())
```
That's it for basic setup. The next step is to create a Redis connection and send a command.
## Command
First, create a new connection to your Redis database. This package is built on top of DatabaseKit, so you can use any of its convenience methods for creating a new connection. See [DatabaseKit &rarr; Overview](../database-kit/overview.md) for more information.
```swift
router.get("redis") { req -> Future<String> in
return req.withNewConnection(to: .redis) { redis in
// use redis connection
}
}
```
Once you have a connection, you can use it to send a command. Let's send the `"INFO"` command which should return information about our Redis server.
```swift
// send INFO command to redis
return redis.command("INFO")
// map the resulting RedisData to a String
.map { $0.string ?? "" }
```
Run your app and query `GET /redis`. You should see information about your Redis server printed as output. Congratulations!
Use `import Redis` to access Redis' APIs.

View File

@ -0,0 +1,92 @@
# Using Redis
Redis ([vapor/redis](https://github.com/vapor/redis)) is a pure-Swift, event-driven, non-blocking Redis client built on top of SwiftNIO.
You can use this package to interact send Redis commands to your server directly, or as a cache through Vapor's `KeyedCache` interface.
## Redis Commands
Let's take a look at how to send and recieve data using Redis commands.
### Connection
The first thing you will need to send a Redis command is a connection. This package is built on top of DatabaseKit, so you can use any of its convenience methods for creating a new connection.
For this example, we will use the `withNewConnection(to:)` method to create a new connection to Redis.
```swift
router.get("redis") { req -> Future<String> in
return req.withNewConnection(to: .redis) { redis in
// use redis connection
}
}
```
See [DatabaseKit &rarr; Overview](../database-kit/overview.md) for more information.
### Available Commands
See [`RedisClient`](https://api.vapor.codes/redis/latest/Redis/Classes/RedisClient.html) for a list of all available commands. Here we'll take a look at some common commands.
#### Get / Set
Redis's `GET` and `SET` commands allow you to store and later retrieve data from the server. You can pass any `Codable` type as the value to this command.
```swift
router.get("set") { req -> Future<HTTPStatus> in
// create a new redis connection
return req.withNewConnection(to: .redis) { redis in
// save a new key/value pair to the cache
return redis.set("hello", to: "world")
// convert void future to HTTPStatus.ok
.transform(to: .ok)
}
}
router.get("get") { req -> Future<String> in
// create a new redis connection
return req.withNewConnection(to: .redis) { redis in
// fetch the key/value pair from the cache, decoding a String
return redis.get("hello", as: String.self)
// handle nil case
.map { $0 ?? "" }
}
}
```
#### Delete
Redis's `DELETE` command allows you to clear a previously stored key/value pair.
```swift
router.get("del") { req -> Future<HTTPStatus> in
// create a new redis connection
return req.withNewConnection(to: .redis) { redis in
// fetch the key/value pair from the cache, decoding a String
return redis.delete("hello")
// convert void future to HTTPStatus.ok
.transform(to: .ok)
}
}
```
See [`RedisClient`](https://api.vapor.codes/redis/latest/Redis/Classes/RedisClient.html) for a list of all available commands.
## Keyed Cache
You can also use Redis as the backend to Vapor's [`KeyedCache`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Protocols/KeyedCache.html) protocol.
```swift
router.get("set") { req -> Future<HTTPStatus> in
let string = try req.query.get(String.self, at: "string")
return try req.keyedCache(for: .redis).set("string", to: string)
.transform(to: .ok)
}
router.get("get") { req -> Future<String> in
return try req.keyedCache(for: .redis).get("string", as: String.self)
.unwrap(or: Abort(.badRequest, reason: "No string set yet."))
}
```
See [DatabaseKit &rarr; Overview](../database-kit/overview/#keyed-cache) for more information.

View File

@ -1,18 +0,0 @@
# Pipelining
Pipelining is used for sending multiple commands at once. The performance advantages become apparent when sending a large number of queries. Redis' pipelining cuts down latency by reducing the RTT (Round Trip Time) between the client and server. Pipelining also reduces the amount of IO operations Redis has to perform, this increases the amount of queries per second Redis can handle.
### Use cases
Sometimes multiple commands need to be executed at once. Instead of sending those commands individually in a loop, pipelining allows the commands to be batched and sent in one request. A common scenario might be needing to set a key and increment a count, pipelining those commands would be ideal.
### Enqueuing Commands
```swift
let pipeline = connection.makePipeline()
let result = try pipeline
.enqueue(command: "SET", arguments: ["KEY", "VALUE"])
.enqueue(command: "INCR", arguments: ["COUNT"])
.execute() // Future<[RedisData]>
```
Note: Commands will not be executed until execute is called.

View File

@ -1,58 +0,0 @@
# Publish & Subscribe
Redis' Publish and Subscribe model is really useful for notifications.
### Use cases
Pub/sub is used for notifying subscribers of an event.
A simple and common event for example would be a chat message.
A channel consists of a name and group of listeners. Think of it as being `[String: [Listener]]`.
When you send a notification to a channel you need to provide a payload.
Each listener will get a notification consisting of this payload.
Channels must be a string. For chat groups, for example, you could use the database identifier.
### Publishing
You cannot get a list of listeners, but sending a payload will emit the amount of listeners that received the notification.
Sending (publishing) an event is done like so:
```swift
// Any redis data
let notification: RedisData = "My-Notification"
client.publish(notification, to: "my-channel")
```
If you want access to the listener count:
```swift
let notifiedCount = client.publish(notification, to: "my-channel") // Future<Int>
```
### Subscribing
To subscribe for notifications you're rendering an entire Redis Client useless in exchange for listening to events.
A single client can listen to one or more channels, which is provided using a set of unique channel names. The result of subscribing is a `SubscriptionStream`.
```swift
let notifications = client.subscribe(to: ["some-notification-channel", "other-notification-channel"])
```
If you try to use the client after subscribing, all operations will fail. These errors are usually emitted through the Future.
This stream will receive messages asynchronously from the point of `draining`. This works like any other async stream.
Notifications consist of the channel and payload.
```swift
notifications.drain { notification in
print(notification.channel)
let payload = notification.payload
// TODO: Process the payload
}
```

View File

@ -0,0 +1,3 @@
# Middleware
Coming soon.

View File

@ -24,6 +24,10 @@ pages:
- 'Async':
- 'Getting Started': 'async/getting-started.md'
- 'Overview': 'async/overview.md'
- 'Auth':
- 'Getting Started': 'auth/getting-started.md'
- 'Stateless (API)': 'auth/api.md'
- 'Sessions (Web)': 'auth/web.md'
- 'Console':
- 'Getting Started': 'console/getting-started.md'
- 'Overview': 'console/overview.md'
@ -36,6 +40,7 @@ pages:
- 'Ciphers': 'crypto/ciphers.md'
- 'Asymmetric': 'crypto/asymmetric.md'
- 'Random': 'crypto/random.md'
- 'TOTP & HOTP': 'crypto/otp.md'
- 'Database Kit':
- 'Getting Started': 'database-kit/getting-started.md'
- 'Overview': 'database-kit/overview.md'
@ -51,6 +56,9 @@ pages:
- 'Client': 'http/client.md'
- 'Server': 'http/server.md'
- 'Message': 'http/message.md'
- 'JWT':
- 'Getting Started': 'jwt/getting-started.md'
- 'Overview': 'jwt/overview.md'
- 'Leaf':
- 'Getting Started': 'leaf/getting-started.md'
- 'Overview': 'leaf/overview.md'
@ -67,10 +75,7 @@ pages:
- 'Getting Started': 'postgresql/getting-started.md'
- 'Redis':
- 'Getting Started': 'redis/getting-started.md'
- 'Basics': 'redis/basics.md'
- 'Custom commands': 'redis/custom-commands.md'
- 'Publish and Subscribe': 'redis/pub-sub.md'
- 'Pipeline': 'redis/pipeline.md'
- 'Overview': 'redis/overview.md'
- 'Routing':
- 'Getting Started': 'routing/getting-started.md'
- 'Overview': 'routing/overview.md'