Skip to the content
OrielDocs
0.1.0-alpha.10GitHub

The basics

HTTP requests

The request() helper gives you the current HTTP request: its URL, headers and input, the files it uploaded and the cookies it sent.

Interacting with the request#

Accessing the request#

Call request() anywhere in your code while a request is handled: in a route, a controller, a middleware or a view. It returns the same object each time for the same request:

app/Http/Controllers/UserController.ts
import { redirect, request } from '@orieljs/framework'import { Controller } from './Controller'export class UserController extends Controller {  async store() {    const name = request().input<string>('name')    // Store the user...    return redirect('/users')  }}

The same goes for a route's closure:

Route.get('/', () => {  const page = request().query('page', '1')  return `Page ${page}`})

request('name') is a short form of request().input('name'), and takes a default too.

Route parameters#

A route's parameters are still the action's arguments, in the order of the URI:

Route.put('/user/{id}', [UserController, 'update'])
app/Http/Controllers/UserController.ts
import { redirect, request } from '@orieljs/framework'import { User } from '../../Models/User'import { Controller } from './Controller'export class UserController extends Controller {  async update(id: string) {    const user = await User.findOrFail(id)    await user.update({ name: request().input<string>('name') })    return redirect('/users')  }}

request().route('id') reads one of them from anywhere else, after model binding, and request().route() gives them all.

Request path, host, and method#

Retrieving the request path#

path() returns the request's path without the slashes at its ends. For http://example.com/foo/bar, it is foo/bar; for the home page, /:

const uri = request().path()

Inspecting the request path and route#

is() checks the path against one or more patterns, where * matches anything:

if (request().is('admin/*')) {  // ...}

routeIs() checks the name of the route the request matched, the same way:

if (request().routeIs('admin.*')) {  // ...}

Retrieving the request URL#

url() returns the URL without its query string, and fullUrl() with it:

const url = request().url()const urlWithQueryString = request().fullUrl()

Retrieving the request host#

root() returns the scheme and the host, as https://example.com, and secure() tells you whether the request came over HTTPS. Behind a proxy, both follow the proxy's X-Forwarded-* headers once you trust it.

Retrieving the request method#

method() returns the request's verb, upper-cased, and isMethod() compares it in any case:

const method = request().method()if (request().isMethod('post')) {  // ...}

A form's _method field, or an X-HTTP-Method-Override header, changes the method of a POST: see form method spoofing.

Request headers#

header() returns a header, by its name in any case, or undefined when the request doesn't have it. A second argument is the default:

const value = request().header('X-Header-Name')const withDefault = request().header('X-Header-Name', 'default')

bearerToken() returns the token of an Authorization: Bearer header, or undefined, and userAgent() the User-Agent header:

const token = request().bearerToken()

Request IP address#

ip() returns the address of the client that sent the request, or undefined when the server doesn't know it:

const ipAddress = request().ip()

Behind a trusted proxy, it is the client's address from X-Forwarded-For. Treat an IP address as information the client controls.

Content negotiation#

expectsJson() tells you whether the request wants a JSON response: its Accept header asks for JSON first, or it is an AJAX request that accepts anything. wantsJson() checks the Accept header alone, and ajax() the X-Requested-With: XMLHttpRequest header:

if (request().expectsJson()) {  // ...}

The exception handler uses expectsJson() to choose between an HTML error page and JSON: see Error handling.

The web-standard request#

request().raw is the web-standard Request the server received. Oriel reads JSON, URL-encoded and multipart bodies for you; a body of any other type is left unread, so read it from raw:

Route.post('/hooks/raw', async () => {  const bytes = await request().raw.arrayBuffer()  return { received: bytes.byteLength }})

Input#

Retrieving input#

Retrieving all input data#

all() returns the request's input, from the query string and the body, with its uploaded files:

const input = request().all()

Retrieving an input value#

input() returns one value, whatever the request's verb, from the body or, when the body hasn't the key, from the query string. A second argument is the default when the key is missing:

const name = request().input('name')const withDefault = request().input('name', 'Sally')

Form fields named with brackets, such as products[0][name], arrive nested. Reach into them with dots:

const name = request().input('products.0.name')

input() with no key returns every value, without the files.

input() returns unknown unless you give it a type, as in input<string>('name'). That type is a cast, not a check: the client can send anything, so validate the input you rely on.

Retrieving input from the query string#

query() reads the query string only, with the same defaults, and with no key returns it all:

const name = request().query('name')const withDefault = request().query('name', 'Helen')const query = request().query()

Retrieving JSON input values#

A request with a JSON body, sent with a Content-Type of application/json (or any type that ends in +json), has the JSON as its input, so input() reads it with dots too:

const name = request().input('user.name')

content() returns the raw text of a JSON or URL-encoded body. A body that isn't valid JSON reads as empty input.

Retrieving stringable input values#

string() returns a value as a Stringable, so you can chain string methods onto it. A missing value is the empty string, or the default you give:

const name = request().string('name').trim().toString()

str() is the same method under a shorter name.

Retrieving integer input values#

integer() reads a value as a whole number, from its leading digits, as PHP's intval does. A missing value gives the default (0 unless you give one), and text that doesn't start with a number gives 0:

const perPage = request().integer('per_page', 15)

Retrieving boolean input values#

Checkboxes and other fields send truthy text, such as on. boolean() returns true for 1, '1', true, 'true', 'on' and 'yes', in any case, and false for everything else. A missing value gives the default (false unless you give one):

const archived = request().boolean('archived')

Retrieving a portion of the input data#

only() and except() take a list of keys, or the keys as arguments, and accept dots:

const credentials = request().only(['username', 'password'])const sameCredentials = request().only('username', 'password')const withoutCard = request().except(['credit_card'])const withoutCardEither = request().except('credit_card')

Input presence#

has() tells you whether every key you give is present, even with an empty value, and hasAny() whether one of them is:

if (request().has('name')) {  // ...}if (request().has(['name', 'email'])) {  // ...}if (request().hasAny(['name', 'email'])) {  // ...}

filled() tells you whether a value is present and not blank: not null, not text made only of spaces, not an empty list or object. missing() is the opposite of has():

if (request().filled('name')) {  // ...}if (request().missing('name')) {  // ...}

Merging additional input#

merge() adds values to the request's input, replacing those it already has:

request().merge({ votes: 0 })

Old input#

Oriel can keep a request's input for the next request, so a form can show what the user typed after a failed validation. When validation fails, Oriel does this for you, and leaves out the password fields.

Flashing input then redirecting#

withInput() on a redirect flashes the input to the session. It takes the input to flash, or flashes all of it:

const back = redirect('/form').withInput()const toRoute = redirect().route('user.create').withInput()const withoutPassword = redirect('/form').withInput(request().except('password'))

Retrieving old input#

old() returns a value the previous request flashed, or the default you give. In a TSX view, it fills a field again:

resources/views/user/create.tsx
import { csrfField, old } from '@orieljs/framework'export default function Create() {  return (    <form method="post" action="/users">      {csrfField()}      <input type="text" name="username" value={old('username', '')} />      <button type="submit">Create</button>    </form>  )}

old() works in the rest of your code too, as does request().session().getOldInput('username'). Laravel's $request->flash(), flashOnly() and flashExcept() aren't on Oriel's request: flash input with withInput(), or with the session's flashInput().

Cookies#

Retrieving cookies from requests#

The cookies Oriel sets are encrypted, and a cookie that doesn't decrypt is dropped, so the client can't change one. cookie() returns a cookie's decrypted value, or undefined:

const value = request().cookie('name')

Cookie.get('name') reads the same value through the Cookie facade, and request().cookies() returns them all. Responses covers setting cookies.

Cookies are decrypted by the web middleware group: in routes/api.ts, cookie() returns them as the client sent them.

A cookie your scripts write isn't encrypted, so EncryptCookies drops it as one that doesn't decrypt. List its name with EncryptCookies.except('appearance') in bootstrap/app.ts to read it as it is, as the starter kits do.

Input trimming and normalization#

Two global middleware tidy the input of every request before your code sees it, as in Laravel: TrimStrings trims every text value, except the password, password_confirmation and current_password fields, and ConvertEmptyStringsToNull turns empty text into null. They apply to the query string and to the body, JSON included. They always run: Laravel's switches to turn them off, for every request or some, aren't in Oriel.

Files#

Retrieving uploaded files#

file() returns an uploaded file from a multipart form, or undefined, and hasFile() tells you whether there is one. A file field left empty doesn't count:

const file = request().file('photo')if (request().hasFile('photo')) {  // ...}

allFiles() returns every uploaded file, by field name.

File names and extensions#

An uploaded file has the name, the MIME type and the size the browser gave it, in clientName, mimeType and size. extension() guesses the extension from the file's content, so it can differ from the one in its name, which clientExtension() returns:

const extension = file.extension() // 'png', from the bytesconst clientExtension = file.clientExtension() // 'jpg', from photo.jpg

extension() reads the first bytes of the file. When they show a known format, its extension wins. Otherwise the client's claim is kept, unless it names a format the bytes would have shown, or a type a browser renders or runs, such as html or svg; then plain text is txt, and anything else has no extension.

Other file methods#

hashName() returns a random 40-character name with that extension, of the kind store() gives the file, and a new one each time you call it. arrayBuffer(), text() and stream() read the file's content:

const name = file.hashName() // 'Xb3bTx...Q9.png'const text = await file.text()const stream = file.stream()

Storing uploaded files#

store() saves an uploaded file on one of your filesystem disks, in the folder you name, under a new random name of the kind hashName() makes. It returns the file's path on the disk, such as images/<name>.png. A second argument names the disk; without it, the default disk is used:

const path = await file.store('images')const onS3 = await file.store('images', 's3')

storeAs() takes the file's name as well:

const path = await file.storeAs('images', 'filename.jpg')const onS3 = await file.storeAs('images', 'filename.jpg', 's3')

Disks are configured in config/filesystems.ts; File storage covers them.

Body size limits#

Oriel reads a JSON, URL-encoded or multipart body into memory before your code runs, and refuses a body larger than http.max_body_size: 8 MB unless you change it. A larger body gets a 413 ("Content Too Large"), and when the Content-Length header gives the size, the body isn't read at all. The limit is the same for every route.

A JSON body can also nest objects and arrays only so deep: past http.max_json_depth, 512 levels unless you change it, the request gets a 400 ("The request body is nested too deeply.").

Raise them in config/http.ts, where max_body_size is a number of bytes, and null turns either limit off:

config/http.ts
export default {  trusted_proxies: [],  csrf_except: [],  redirect_guests_to: '/login',  max_body_size: 20 * 1024 * 1024,  max_json_depth: 512,}

Configuring trusted proxies#

Behind a load balancer or a proxy that ends TLS, your app sees the proxy's request, over HTTP, from the proxy's address. Tell Oriel which proxies to trust with trusted_proxies in config/http.ts: exact addresses, IPv4 ranges such as 10.0.0.0/8, or * for any:

config/http.ts
export default {  trusted_proxies: ['192.168.1.1', '10.0.0.0/8'],  csrf_except: [],  redirect_guests_to: '/login',  max_body_size: 8 * 1024 * 1024,}

For a request from a trusted proxy, ip() reads the client's address from X-Forwarded-For, and the scheme and host come from X-Forwarded-Proto and X-Forwarded-Host, so url(), route() and secure() match what the browser asked for. The list may also be a comma-separated string, so it can come from your .env: trusted_proxies: env('TRUSTED_PROXIES', '').

Trusting all proxies#

When you can't know your load balancers' addresses, as with many cloud providers, trust them all with '*'. Do it only when nothing can reach your app except through them: any client could then set the X-Forwarded-* headers.

Laravel's trusted hosts (trustHosts()) aren't in Oriel: let your web server or load balancer refuse unknown hosts.

Privacy policy ·

esc