Ir para o conteúdo

Requests

Lilya disponibiliza a classe Request. Este objecto é uma interface conveniente entre o pedido recebido e o âmbito ASGI.

Isso significa que você não precisa aceder diretamente ao âmbito e extrair todas as informações necessárias para um objecto do tipo request.

from lilya.requests import Request

A classe Request

Uma instância de Request recebe um parâmetro scope, um parâmetro receive e um parâmetro send.

from lilya.enums import ScopeType
from lilya.requests import Request
from lilya.responses import PlainText
from lilya.types import Receive, Scope, Send


async def app(scope: Scope, receive: Receive, send: Send):
    assert scope["type"] == ScopeType.HTTP

    request = Request(scope=scope, receive=receive, send=send)
    data = f"{request.method} {request.url.path}"

    response = PlainText(content=data)
    await response(scope, receive, send)

Os pedidos, como mencionado anteriormente, apresentam uma interface para o scope, o que significa que se você usar request['app'] ou request['headers'] ou request['path'], irá obter as mesmas informações que estavam a ser obtidas no scope.

Nota

Se não houver necessidade de aceder ao corpo do pedido, pode instanciar um pedido sem fornecer um argumento receive.

Exemplo

from lilya.requests import Request

request = Request(scope)

Atributos

Existem muitos atributos disponíveis que pode aceder dentro do request.

Método

O método do pedido que é utilizado para aceder.

from lilya.requests import Request

request = Request(scope)

request.method

URL

from lilya.requests import Request

request = Request(scope)

request.url

Esta propriedade expõe todos os componentes que podem ser extraídos da URL.

Exemplo

from lilya.requests import Request

request = Request(scope)

request.url.port
request.url.path
request.url.scheme
request.url.netloc
request.url.query

Cabeçalho

Lilya usa o multidict para os seus cabeçalhos (headers) e adiciona-lhe alguns recursos extra.

from lilya.requests import Request

request = Request(scope)

request.headers['content-type']

Parâmetros de Consulta

Lilya usa o multidict para os seus parâmetros de consulta (query params) e adiciona-lhe alguns recursos extra.

from lilya.requests import Request

request = Request(scope)

request.query_params['search']

Parâmetros de URL

Extraído diretamente do scope como um dicionário em Python.

from lilya.requests import Request

request = Request(scope)

request.path_params['username']

Endereço do Cliente

O endereço remoto do cliente é exposto como uma classe de dados request.client.

from lilya.requests import Request

request = Request(scope)

request.client.host
request.client.port

Cookies

Extraído diretamente dos cabeçalhos (headers) e analisado como um dicionário em Python.

from lilya.requests import Request

request = Request(scope)

request.cookies.get('a-cookie')

Corpo (body)

Aqui é diferente. Para extrair e usar o body, um argumento send deve ser passado para a instância do pedido e pode ser extraído de diferentes maneiras.

Como bytes
from lilya.requests import Request

pedido = Request(scope, send)

await request.body()
Como JSON
from lilya.requests import Request

pedido = Request(scope, send)

await request.json()
Como texto
from lilya.requests import Request

pedido = Request(scope, send)

await request.text()
Como dados de formulário ou formulário multipart
from lilya.requests import Request

pedido = Request(scope, send)

async with request.form() as form:
    ...
Como dados
from lilya.requests import Request

pedido = Request(scope, send)

await request.data()
Como um stream
from lilya.enums import ScopeType
from lilya.requests import Request
from lilya.responses import PlainText
from lilya.types import Receive, Scope, Send


async def app(scope: Scope, receive: Receive, send: Send):
    assert scope["type"] == ScopeType.HTTP

    request = Request(scope=scope, receive=receive, send=send)
    data = b""

    async for chunk in request.stream():
        data += chunk

    response = PlainText(content=data)
    await response(scope, receive, send)

Ao usar .stream(), os fragmentos de bytes são fornecidos sem a necessidade de guardar todo o corpo em memória. Chamadas subsequentes a .body(), .form() ou .json() resultarão em erro.

Em situações específicas, como resposta de longa duração ou streaming, torna-se crucial determinar se o cliente foi desligado.

Isso pode ser verificado utilizando o seguinte: desligado = await request.is_disconnected().

Ficheiros de Request

Normalmente, os ficheiros são transmitidos como dados de formulário multipart (multipart/form-data).

from lilya.requests import Request

request = Request(scope, receive)

request.form(max_files=1000, max_fields=1000)

Tem a flexibilidade de definir o número máximo de campos ou ficheiros usando os parâmetros max_files e max_fields:

async with request.form(max_files=1000, max_fields=1000):
    ...

Warning

Estas limitações servem para fins de segurança. Permitir um número ilimitado de campos ou ficheiros poderia representar um risco de ataque de denial of service, consumindo recursos excessivos de CPU e memória ao analisar inúmeros campos vazios.

Ao utilizar async with request.form() as form, obtém um lilya.datastructures.FormData, que é um multidict imutável contendo tanto uploads de ficheiros quanto input de texto.

Os itens de upload de ficheiros são representados como instâncias de lilya.datastructures.DataUpload.

DataUpload

DataUpload possui os seguintes atributos:

  • filename: Uma str com o nome original do ficheiro que foi enviado ou None se não estiver disponível (por exemplo, profile.png).
  • file: Um SpooledTemporaryFile (um objecto semelhante a um ficheiro). Este é o ficheiro Python real que pode passar diretamente para outras funções ou bibliotecas que esperam um objecto "semelhante a um ficheiro".
  • headers: Um objecto Header. Geralmente, isso será apenas o cabeçalho Content-Type, mas se houver cabeçalhos adicionais incluídos no campo multipart, serão incluídos aqui. Observe que esses cabeçalhos não têm relação com os cabeçalhos em Request.headers.
  • size: Um int com o tamanho do ficheiro enviado em bytes. Esse valor é calculado a partir do conteúdo do pedido, tornando-o uma escolha melhor para encontrar o tamanho do ficheiro enviado do que o cabeçalho Content-Length. Nenhum valor se não estiver definido.

A classe DataUpload fornece vários métodos assíncronos que invocam as operações de ficheiro correspondentes usando o SpooledTemporaryFile interno:

  • async write(dados): Grava os dados especificados (em bytes) no ficheiro.
  • async read(tamanho): Lê o número especificado de bytes (como um inteiro) do ficheiro.
  • async seek(deslocamento): Posiciona o cursor do ficheiro na posição de bytes especificado (como um inteiro). Por exemplo, usando await profile.seek(0) moveria o cursor para o início do ficheiro.
  • async close(): Fecha o ficheiro.

Como todos estes métodos são assíncronos, a palavra-chave await é necessária ao invocá-los.

Exemplo

async with request.form() as form:
    filename = form["upload_file"].filename
    contents = await form["upload_file"].read()

Aplicação

A aplicação Lilya.

from lilya.requests import Request

request = Request(scope)

request.app

Estado

Se você deseja incluir informações adicionais com o pedido, pode faze-lo usando o request.state.

from lilya.requests import Request

request = Request(scope)

request.state.admin = "example@lilya.dev"