Ir para o conteúdo
Trilha
Capítulos

Referência

Ctx

Tudo que uma função de rota pode fazer com o contexto da requisição.

*trilha.Ctx embrulha a requisição e a resposta. É criado por requisição e não deve ser usado por outra goroutine depois que o handler devolve.

Requisição#

MétodoDescrição
Request() *http.Requesta requisição original
SetContext(ctx)troca o contexto da requisição: um middleware passa valores a código que só recebe *http.Request
SetRequest(*http.Request)troca a requisição (URL reescrita, corpo embrulhado)
Context() context.Contextcontexto da requisição (cancelamento)
Param(nome) stringparâmetro de rota (slug_"slug")
Pattern() stringo gabarito da rota que casou (/blog/{slug}), a forma agregável do caminho; "" para o que o fallback respondeu (estático, 404, redirecionamento de barra)
Query(nome) stringprimeiro valor do parâmetro de query
Form(nome) stringcampo do formulário (faz o parse sob demanda, com limite de tamanho)
FormErr() errorerro do parse do formulário: 400 inválido, 413 grande demais
BindJSON(&v) errordecodifica o corpo JSON; campos desconhecidos são erro (400); 413 acima do limite
Cookie(nome) (*http.Cookie, error)cookie da requisição
Accepts(ofertas...) stringa oferta que o cliente prefere (Accept, ranqueado por q), ou ""; cabeçalho ausente ou */* fica com a primeira oferta
RequestID() stringX-Request-ID recebido ou um id gerado
Env() trilha.Envtrilha.Dev ou trilha.Prod
Base() stringprefixo de URL (TRILHA_BASE_PATH), sem barra final
App() *trilha.Appa aplicação
Fragment() stringid que o cliente quer trocar (cabeçalho Trilha-Fragment), ou "" numa navegação normal (Interatividade)

Resposta#

MétodoDescrição
JSON(code, v) errorescreve JSON com Content-Type correto
Text(code, s) errorescreve texto simples
HTML(code, node) errorescreve um nó como documento inteiro, sem layouts
Redirect(url) errordevolve o erro de redirecionamento 303 (use com return)
Status(code)status que a próxima renderização de página vai usar
Header(k, v)define um cabeçalho de resposta
SetCookie(*http.Cookie)adiciona Set-Cookie
Flash(tipo, texto)guarda um aviso para a requisição seguinte, num cookie assinado: a notícia que o redirect comeria. O ui.Flashes(c) mostra. Numa resposta de fragmento ele vai no cabeçalho Trilha-Flash, e quem mostra é o ui.js. Sem TRILHA_SECRET nada é escrito e o app avisa uma vez no log
Flashes() []Flashos avisos deixados pela requisição anterior mais os que esta ainda não mandou; ler é gastar, e ler duas vezes dá a mesma lista
Render(code, node) errorescreve a página com os layouts da rota (como o GET): para um POST devolver o formulário com erros (422); num fragmento, sem os layouts
Stream() *Streamresposta em Server-Sent Events: Send(evento, dados), JSON(evento, v), Comment(s), Done(); desliga o write timeout (IA e agentes)
Writer() http.ResponseWriteracesso direto (downloads longos, WebSocket)
Written() boolse a resposta já começou

Cache HTTP#

MétodoDescrição
ETag(tag) boolescreve ETag (com aspas, se faltarem) e diz se a requisição já a tinha
LastModified(t) boolescreve Last-Modified e diz se a cópia está em dia
CacheControl(v)escreve Cache-Control como veio

true quer dizer que o 304 já foi escrito: devolva nil, nil e não escreva mais nada. Só GET e HEAD respondem 304; nos outros métodos os cabeçalhos são escritos e a resposta é sempre false. Etiqueta vazia ou data zerada não escrevem nada. Quando os dois são declarados, quem decide é o If-None-Match e a data fica como metadado, como pede a RFC 9110. Os arquivos em static/ já vêm com ETag: a impressão digital do conteúdo que vai no ?v=.

Entre página e layout#

MétodoDescrição
SetTitle(s) / Title() stringtítulo da página, lido pelos layouts
Set(chave, v) / Get(chave) anyvalores por requisição (middleware → página → layout)

Ilhas#

func (c *Ctx) Island(src string, props any, children ...h.Node) h.Node

Renderiza <div data-trilha-island="…" data-trilha-props="…"> com os filhos como conteúdo de origem, vindo do servidor. src é um módulo em public/ (endereçado pelo Asset, então leva o hash do conteúdo) cuja exportação padrão é a função de montagem, chamada uma vez com (el, props). props é qualquer coisa que o encoding/json serialize, ou nil; viaja como atributo escapado e volta pelo JSON.parse, então é dado, nunca marcação. Props que não serializam avisam uma vez e deixam o conteúdo de origem em paz. O carregador é um único script inline com o nonce da requisição, emitido junto da primeira ilha da resposta (Interatividade).

Conexão longa e corpo grande#

MétodoDescrição
AllowBody(n int64)limite de corpo desta requisição, no lugar do Config.MaxBodyBytes
NoReadDeadline() errortira o prazo de leitura desta requisição (upload lento não é erro)
NoWriteDeadline() errortira o prazo de escrita (download longo, SSE)
Hijack() (net.Conn, *bufio.ReadWriter, error)assume a conexão: prazos removidos, e o Trilha não escreve mais nada nela

O limite padrão é do app; a exceção é da rota. Levante no middleware.go da rota, não no handler — o CSRF de formulário lê o corpo antes do handler rodar, então a decisão tem de vir antes:

// app/anexos/middleware.go
func Middleware(c *trilha.Ctx, next trilha.Next) error {
	if c.Request().Method == "POST" {
		c.AllowBody(8 << 20) // só esta requisição; o resto do app segue no limite do app
		c.NoReadDeadline()
	}
	return next()
}

Estourar o limite continua sendo 413 com a mensagem de sempre, pelo FormErr, pelos Bind* ou na leitura direta do Request().Body.

WebSocket#

O Trilha não tem WebSocket próprio, e isso é decisão. O protocolo é transporte: não encosta em rota, em layout nem em render. O que ele exige — frames de fragmentação e continuação, frame de controle no meio de uma mensagem, aperto de mão de fechamento com prazo, validação de UTF-8, máscara, limite de tamanho, escrita concorrente, contrapressão, permessage-deflate — são algumas centenas de linhas que a suíte Autobahn cobra em mais de 500 casos. A assimetria decide: o seu app pode pôr coder/websocket no go.mod dele (o princípio II obriga o framework, não o app), mas não consegue tirar essas linhas do framework.

O que faltava era a porta, e ela é o Hijack:

func WS(c *trilha.Ctx) error {
	conn, _, err := c.Hijack() // prazos de leitura e escrita já removidos
	if err != nil {
		return err
	}
	defer conn.Close()
	return meuWebsocket.Serve(conn) // coder/websocket, gorilla, o que você escolher
}

Depois do Hijack a conexão é sua: o framework não escreve cabeçalho, página de erro nem corpo nela, e o log de acesso registra 101.

Segurança#

MétodoDescrição
CSRFToken() stringtoken da requisição; cria o cookie na primeira chamada
trilha.CSRFInput(c) h.Node<input type="hidden" name="_csrf"> para formulários
trilha.CSRFTokenFrom(r) stringo mesmo token, para quem só recebe o *http.Request (html/template, templ, um handler seu); "" fora de uma requisição da Trilha
trilha.NonceFrom(r) stringo nonce da CSP daquela requisição, mesmo motivo e mesma regra (Segurança)

O token é verificado automaticamente em POST, PUT, PATCH e DELETE de page.go (e de route.go se Config.CSRFForAPI estiver ligado), pelo campo _csrf ou pelo cabeçalho X-CSRF-Token.

Bind#

Bind(v any) error preenche uma struct a partir do formulário (ou do JSON, quando o Content-Type é application/json). Campos casam pela tag form:"nome" (ou pelo nome do campo); tipos: string, []string, bool (on/true/1), int, int64, float64 (vírgula ou ponto), time.Time (2006-01-02 ou 2006-01-02T15:04) e ponteiros (nil quando ausente). Struct aninhada é achatada, com a tag como prefixo (Cobranca Endereco +"form:\"cob_\""+cob_cep…). Valores que não convertem viram FieldErrors (mensagem trilha.BindInvalid, ajustável) depois de todos os campos serem tentados. A tag validate:"..."` de cada campo é aplicada logo em seguida, na mesma passada: veja Validação.

File#

File(campo string, regras FileRules) (*Upload, error) lê um arquivo do formulário multipart e só o devolve se ele passar pelas regras.

SímboloPapel
FileRules.MaxSize int64limite deste arquivo, à parte do Config.MaxBodyBytes; 0 deixa o limite do corpo trabalhar
FileRules.Accept []stringtipos aceitos, comparados com o tipo detectado: "image/png", "image/*", "*/*"; vazio aceita qualquer um
FileRules.Optional boolcampo ausente devolve (nil, nil) em vez de erro
Upload.Namenome sanitizado: sem diretório, sem separador, sem caractere de controle, no máximo 100 caracteres, nunca vazio
Upload.MIME / Upload.Exttipo detectado nos primeiros 512 bytes, e a extensão correspondente
Upload.Size / Upload.Filetamanho em bytes, e o arquivo posicionado no começo
up.Save(dir) (string, error)grava dentro de dir (modo 0600) com um nome livre e devolve o caminho
up.Close() errorfecha o arquivo

Regra que falha vira FieldErrors no nome do campo, como no Bind; qualquer outra coisa (corpo quebrado, disco cheio) volta como está. As mensagens saem do ValidationMessages (required, filemax, filetype) — veja Validação.