Vai al contenuto

Routing

Il routing è il processo che individua l'handler appropriato per una richiesta in arrivo. Al centro del routing di Vapor c'è un router trie-node ad alte prestazioni proveniente da RoutingKit.

Panoramica

Per capire come funziona il routing in Vapor, bisogna prima comprendere alcune nozioni di base sulle richieste HTTP. Diamo un'occhiata alla seguente richiesta di esempio.

GET /hello/vapor HTTP/1.1
host: vapor.codes
content-length: 0

Questa è una semplice richiesta HTTP GET all'URL /hello/vapor. Questo è il tipo di richiesta HTTP che il tuo browser farebbe se lo puntassi al seguente URL.

http://vapor.codes/hello/vapor

Metodo HTTP

La prima parte della richiesta è il metodo HTTP. GET è il metodo HTTP più comune, ma ce ne sono diversi che userai spesso. Questi metodi HTTP sono spesso associati alla semantica CRUD.

Metodo CRUD
GET Read
POST Create
PUT Replace
PATCH Update
DELETE Delete

Path della Richiesta

Subito dopo il metodo HTTP c'è l'URI della richiesta. L'URI consiste in un path che inizia con / e una query string opzionale dopo ?. Il metodo HTTP e il path sono ciò che Vapor usa per instradare le richieste.

Dopo l'URI c'è la versione HTTP seguita da zero o più header e infine un body. Poiché questa è una richiesta GET, non ha un body.

Metodi del Router

Vediamo come questa richiesta potrebbe essere gestita in Vapor.

app.get("hello", "vapor") { req in
    return "Hello, vapor!"
}

Tutti i metodi HTTP comuni sono disponibili come metodi su Application. Accettano uno o più argomenti stringa che rappresentano il path della richiesta separato da /.

Nota che potresti scrivere questo anche usando on seguito dal metodo.

app.on(.GET, "hello", "vapor") { ... }

Con questa route registrata, la richiesta HTTP di esempio di cui sopra risulterà nella seguente risposta HTTP.

HTTP/1.1 200 OK
content-length: 13
content-type: text/plain; charset=utf-8

Hello, vapor!

Parametri di Route

Ora che abbiamo direzionato con successo una richiesta in base al metodo HTTP e al path, proviamo a rendere il path dinamico. Nota che il nome "vapor" è hardcoded sia nel path che nella risposta. Rendiamolo dinamico in modo da poter visitare /hello/<qualsiasi nome> e ottenere una risposta.

app.get("hello", ":name") { req -> String in
    let name = req.parameters.get("name")!
    return "Hello, \(name)!"
}

Usando un componente del path con il prefisso :, indichiamo al router che questo è un componente dinamico. Qualsiasi stringa fornita qui corrisponderà ora a questa route. Possiamo poi usare req.parameters per accedere al valore della stringa.

Se esegui nuovamente la richiesta di esempio, otterrai ancora una risposta che saluta vapor. Tuttavia, ora puoi includere qualsiasi nome dopo /hello/ e vederlo incluso nella risposta. Proviamo /hello/swift.

GET /hello/swift HTTP/1.1
content-length: 0
HTTP/1.1 200 OK
content-length: 13
content-type: text/plain; charset=utf-8

Hello, swift!

Ora che hai capito le basi, dai un'occhiata a ciascuna sezione per saperne di più su parametri, gruppi e altro.

Route

Una route specifica un request handler per un dato metodo HTTP e un path URI. Può anche memorizzare metadati aggiuntivi.

Metodi

Le route possono essere registrate direttamente sulla tua Application usando vari helper per i metodi HTTP.

// risponde a GET /foo/bar/baz
app.get("foo", "bar", "baz") { req in
    ...
}

Gli handler delle route supportano la restituzione di qualsiasi cosa che sia ResponseEncodable. Questo include Content, una closure async e qualsiasi EventLoopFuture dove il valore future è ResponseEncodable.

Puoi specificare il tipo di ritorno di una route usando -> T prima di in. Questo può essere utile in situazioni dove il compilatore non riesce a determinare il tipo di ritorno.

app.get("foo") { req -> String in
    return "bar"
}

Questi sono i metodi helper di route supportati:

  • get
  • post
  • patch
  • put
  • delete

Oltre agli helper per i metodi HTTP, c'è una funzione on che accetta il metodo HTTP come parametro di input.

// risponde a OPTIONS /foo/bar/baz
app.on(.OPTIONS, "foo", "bar", "baz") { req in
    ...
}

Componente del Path

Ogni metodo di registrazione delle route accetta una lista variadica di PathComponent. Questo tipo è esprimibile tramite string literal e ha quattro casi:

  • Costante (foo)
  • Parametro (:foo)
  • Qualsiasi (*)
  • Catchall (**)

Costante

Questo è un componente di route statico. Solo le richieste con una stringa esattamente corrispondente in questa posizione saranno permesse.

// risponde a GET /foo/bar/baz
app.get("foo", "bar", "baz") { req in
    ...
}

Parametro

Questo è un componente di route dinamico. Qualsiasi stringa in questa posizione sarà accettata. Un componente del path parametrico è specificato con il prefisso :. La stringa che segue : verrà usata come nome del parametro. Puoi usare il nome per recuperare successivamente il valore del parametro dalla richiesta.

// risponde a GET /foo/bar/baz
// risponde a GET /foo/qux/baz
// ...
app.get("foo", ":bar", "baz") { req in
    ...
}

Qualsiasi

Questo è molto simile al parametro tranne che il valore viene scartato. Questo componente del path è specificato semplicemente come *.

// risponde a GET /foo/bar/baz
// risponde a GET /foo/qux/baz
// ...
app.get("foo", "*", "baz") { req in
    ...
}

Catchall

Questo è un componente di route dinamico che corrisponde a uno o più componenti. È specificato usando semplicemente **. Qualsiasi stringa in questa posizione o nelle posizioni successive verrà abbinata nella richiesta.

// risponde a GET /foo/bar
// risponde a GET /foo/bar/baz
// ...
app.get("foo", "**") { req in
    ...
}

Parametri

Quando si usa un componente del path parametro (con prefisso :), il valore dell'URI in quella posizione verrà memorizzato in req.parameters. Puoi usare il nome del componente del path per accedere al valore.

// risponde a GET /hello/foo
// risponde a GET /hello/bar
// ...
app.get("hello", ":name") { req -> String in
    let name = req.parameters.get("name")!
    return "Hello, \(name)!"
}

Suggerimento

Possiamo essere certi che req.parameters.get non restituirà mai nil qui poiché il path della nostra route include :name. Tuttavia, se stai accedendo ai parametri di route in un middleware o in codice attivato da più route, vorrai gestire la possibilità di nil.

Suggerimento

Se vuoi recuperare i parametri della query URL, ad esempio /hello/?name=foo, devi usare le Content API di Vapor per gestire i dati codificati in URL nella query string dell'URL. Consulta il riferimento Content per maggiori dettagli.

req.parameters.get supporta anche il casting del parametro a tipi LosslessStringConvertible automaticamente.

// risponde a GET /number/42
// risponde a GET /number/1337
// ...
app.get("number", ":x") { req -> String in
    guard let int = req.parameters.get("x", as: Int.self) else {
        throw Abort(.badRequest)
    }
    return "\(int) is a great number"
}

I valori dell'URI abbinati da Catchall (**) verranno memorizzati in req.parameters come [String]. Puoi usare req.parameters.getCatchall per accedere a questi componenti.

// risponde a GET /hello/foo
// risponde a GET /hello/foo/bar
// ...
app.get("hello", "**") { req -> String in
    let name = req.parameters.getCatchall().joined(separator: " ")
    return "Hello, \(name)!"
}

Body Streaming

Quando si registra una route usando il metodo on, puoi specificare come il body della richiesta deve essere gestito. Il comportamento di default è che i body delle richieste vengono raccolti in memoria prima di chiamare il tuo handler. Questo è utile poiché permette alla decodifica del contenuto della richiesta di essere sincrona anche se la tua applicazione legge le richieste in arrivo in modo asincrono.

Per impostazione predefinita, Vapor limiterà la raccolta del body in streaming a 16KB di dimensione. Puoi configurarlo usando app.routes.

// Aumenta il limite di raccolta del body in streaming a 500kb
app.routes.defaultMaxBodySize = "500kb"

Se un body in streaming che viene raccolto supera il limite configurato, verrà lanciato un errore 413 Payload Too Large.

Per configurare la strategia di raccolta del body della richiesta per una singola route, usa il parametro body.

// Raccoglie i body in streaming (fino a 1mb di dimensione) prima di chiamare questa route.
app.on(.POST, "listings", body: .collect(maxSize: "1mb")) { req in
    // Gestisce la richiesta.
}

Se un maxSize viene passato a collect, sovrascriverà il valore predefinito dell'applicazione per quella route. Per usare il valore predefinito dell'applicazione, ometti l'argomento maxSize.

Per richieste di grandi dimensioni, come gli upload di file, raccogliere il body della richiesta in un buffer può potenzialmente mettere sotto pressione la memoria del sistema. Per evitare che il body della richiesta venga raccolto, usa la strategia stream.

// Il body della richiesta non verrà raccolto in un buffer.
app.on(.POST, "upload", body: .stream) { req in
    ...
}

Quando il body della richiesta è in streaming, req.body.data sarà nil. Devi usare req.body.drain per gestire ogni chunk man mano che viene inviato alla tua route.

Routing Case-Insensitive

Il comportamento predefinito per il routing è sia case-sensitive che case-preserving. I componenti del path Constant possono in alternativa essere gestiti in modo case-insensitive e case-preserving ai fini del routing; per abilitare questo comportamento, configura prima dell'avvio dell'applicazione:

app.routes.caseInsensitive = true

Non vengono apportate modifiche alla richiesta originante; gli handler delle route riceveranno i componenti del path della richiesta senza modifiche.

Visualizzare le Route

Puoi accedere alle route della tua applicazione accedendo al servizio Routes o usando app.routes.

print(app.routes.all) // [Route]

Vapor viene anche fornito con un comando routes che stampa tutte le route disponibili in una tabella formattata ASCII.

$ swift run App routes
+--------+----------------+
| GET    | /              |
+--------+----------------+
| GET    | /hello         |
+--------+----------------+
| GET    | /todos         |
+--------+----------------+
| POST   | /todos         |
+--------+----------------+
| DELETE | /todos/:todoID |
+--------+----------------+

Metadati

Tutti i metodi di registrazione delle route restituiscono la Route creata. Questo ti permette di aggiungere metadati al dizionario userInfo della route. Ci sono alcuni metodi predefiniti disponibili, come l'aggiunta di una descrizione.

app.get("hello", ":name") { req in
    ...
}.description("says hello")

Gruppi di Route

Il raggruppamento delle route ti permette di creare un insieme di route con un prefisso del path o middleware specifici. Il raggruppamento supporta sia la sintassi builder che quella basata su closure.

Tutti i metodi di raggruppamento restituiscono un RouteBuilder, il che significa che puoi combinare, abbinare e annidare infinitamente i tuoi gruppi con altri metodi di costruzione delle route.

Prefisso del Path

I gruppi di route con prefisso del path ti permettono di anteporre uno o più componenti del path a un gruppo di route.

let users = app.grouped("users")
// GET /users
users.get { req in
    ...
}
// POST /users
users.post { req in
    ...
}
// GET /users/:id
users.get(":id") { req in
    let id = req.parameters.get("id")!
    ...
}

Qualsiasi componente del path che puoi passare in metodi come get o post può essere passato in grouped. Esiste anche una sintassi alternativa basata su closure.

app.group("users") { users in
    // GET /users
    users.get { req in
        ...
    }
    // POST /users
    users.post { req in
        ...
    }
    // GET /users/:id
    users.get(":id") { req in
        let id = req.parameters.get("id")!
        ...
    }
}

L'annidamento di gruppi di route con prefisso del path ti permette di definire concisamente API CRUD.

app.group("users") { users in
    // GET /users
    users.get { ... }
    // POST /users
    users.post { ... }

    users.group(":id") { user in
        // GET /users/:id
        user.get { ... }
        // PATCH /users/:id
        user.patch { ... }
        // PUT /users/:id
        user.put { ... }
    }
}

Middleware

Oltre ad aggiungere prefissi ai componenti del path, puoi anche aggiungere middleware ai gruppi di route.

app.get("fast-thing") { req in
    ...
}
app.group(RateLimitMiddleware(requestsPerMinute: 5)) { rateLimited in
    rateLimited.get("slow-thing") { req in
        ...
    }
}

Questo è particolarmente utile per proteggere sottoinsiemi delle tue route con diversi middleware di autenticazione.

app.post("login") { ... }
let auth = app.grouped(AuthMiddleware())
auth.get("dashboard") { ... }
auth.get("logout") { ... }

Redirect

I redirect sono utili in diversi scenari, come il reindirizzamento di vecchie posizioni verso nuove per la SEO, il reindirizzamento di un utente non autenticato alla pagina di login o il mantenimento della compatibilità con le versioni precedenti della tua API.

Per reindirizzare una richiesta, usa:

req.redirect(to: "/some/new/path")

Puoi anche specificare il tipo di redirect, per esempio per reindirizzare una pagina in modo permanente (in modo che la tua SEO venga aggiornata correttamente) usa:

req.redirect(to: "/some/new/path", redirectType: .permanent)

I diversi tipi di Redirect sono:

  • .permanent - restituisce un redirect 301 Permanent
  • .normal - restituisce un redirect 303 see other. Questo è il valore predefinito di Vapor e dice al client di seguire il redirect con una richiesta GET.
  • .temporary - restituisce un redirect 307 Temporary. Questo dice al client di preservare il metodo HTTP usato nella richiesta.

Per scegliere il codice di stato di reindirizzamento appropriato consulta l'elenco completo