From ae8d208845ee7aa88487edae0230ab2d128650b3 Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Tue, 16 Jun 2026 10:38:53 -0700 Subject: [PATCH 1/4] feat(docs): i18n config (pt-br) --- site/astro.config.mjs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/site/astro.config.mjs b/site/astro.config.mjs index d457e289..1a62e476 100644 --- a/site/astro.config.mjs +++ b/site/astro.config.mjs @@ -15,6 +15,7 @@ export default defineConfig({ root: { label: 'English', lang: 'en' }, 'zh-cn': { label: '简体中文', lang: 'zh-CN' }, ja: { label: '日本語', lang: 'ja' }, + 'pt-br': { label: 'Português', lang: 'pt-BR' }, }, logo: { light: './src/assets/logo-light.svg', @@ -103,9 +104,9 @@ export default defineConfig({ // Autogenerated from the content dirs — new pages appear automatically, // ordered by each page's `sidebar.order` frontmatter. sidebar: [ - { label: 'Guide', translations: { 'zh-CN': '指南', ja: 'ガイド' }, items: [{ autogenerate: { directory: 'guide' } }] }, - { label: 'Middleware', translations: { 'zh-CN': '中间件', ja: 'ミドルウェア' }, items: [{ autogenerate: { directory: 'middleware' } }] }, - { label: 'Cookbook', translations: { 'zh-CN': '示例', ja: 'クックブック' }, items: [{ autogenerate: { directory: 'cookbook' } }] }, + { label: 'Guide', translations: { 'zh-CN': '指南', ja: 'ガイド', 'pt-BR': 'Guia' }, items: [{ autogenerate: { directory: 'guide' } }] }, + { label: 'Middleware', translations: { 'zh-CN': '中间件', ja: 'ミドルウェア', 'pt-BR': 'Middleware' }, items: [{ autogenerate: { directory: 'middleware' } }] }, + { label: 'Cookbook', translations: { 'zh-CN': '示例', ja: 'クックブック', 'pt-BR': 'Receitas' }, items: [{ autogenerate: { directory: 'cookbook' } }] }, ], // tune the built-in code theme toward our terminal palette expressiveCode: { themes: ['github-dark', 'github-light'] }, From 7709bc19b6f548bb178d7a2c6c4dd1b64936e0ea Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Tue, 16 Jun 2026 11:01:42 -0700 Subject: [PATCH 2/4] docs(i18n): guide (pt-br) --- site/src/content/docs/pt-br/guide/binding.md | 151 ++++++++ site/src/content/docs/pt-br/guide/context.md | 78 +++++ site/src/content/docs/pt-br/guide/cookies.md | 72 ++++ .../content/docs/pt-br/guide/customization.md | 63 ++++ .../docs/pt-br/guide/error-handling.md | 82 +++++ .../content/docs/pt-br/guide/installation.md | 57 ++++ .../content/docs/pt-br/guide/ip-address.md | 117 +++++++ .../content/docs/pt-br/guide/quickstart.md | 76 +++++ site/src/content/docs/pt-br/guide/request.md | 170 +++++++++ site/src/content/docs/pt-br/guide/response.md | 322 ++++++++++++++++++ site/src/content/docs/pt-br/guide/routing.md | 75 ++++ .../content/docs/pt-br/guide/static-files.md | 89 +++++ .../src/content/docs/pt-br/guide/templates.md | 133 ++++++++ site/src/content/docs/pt-br/guide/testing.md | 264 ++++++++++++++ 14 files changed, 1749 insertions(+) create mode 100644 site/src/content/docs/pt-br/guide/binding.md create mode 100644 site/src/content/docs/pt-br/guide/context.md create mode 100644 site/src/content/docs/pt-br/guide/cookies.md create mode 100644 site/src/content/docs/pt-br/guide/customization.md create mode 100644 site/src/content/docs/pt-br/guide/error-handling.md create mode 100644 site/src/content/docs/pt-br/guide/installation.md create mode 100644 site/src/content/docs/pt-br/guide/ip-address.md create mode 100644 site/src/content/docs/pt-br/guide/quickstart.md create mode 100644 site/src/content/docs/pt-br/guide/request.md create mode 100644 site/src/content/docs/pt-br/guide/response.md create mode 100644 site/src/content/docs/pt-br/guide/routing.md create mode 100644 site/src/content/docs/pt-br/guide/static-files.md create mode 100644 site/src/content/docs/pt-br/guide/templates.md create mode 100644 site/src/content/docs/pt-br/guide/testing.md diff --git a/site/src/content/docs/pt-br/guide/binding.md b/site/src/content/docs/pt-br/guide/binding.md new file mode 100644 index 00000000..36db4b54 --- /dev/null +++ b/site/src/content/docs/pt-br/guide/binding.md @@ -0,0 +1,151 @@ +--- +title: Binding +description: Analise dados de request em structs Go tipadas a partir de path, query, header e body. +sidebar: + order: 5 +--- + +Analisar dados de request é uma parte crucial de uma aplicação web. No Echo isso se chama +_binding_, e ele pode ler de quatro partes de um request HTTP: + +- Parâmetros de caminho da URL +- Parâmetros de query da URL +- Headers +- Body do request + +## Binding com tags de struct + +Defina uma struct com tags que especificam a fonte e a chave dos dados, então chame `c.Bind()` +com um ponteiro para ela. Aqui o parâmetro de query `id` faz binding para o campo `ID`: + +```go +type User struct { + ID string `query:"id"` +} + +// handler for /users?id= +var user User +if err := c.Bind(&user); err != nil { + return c.String(http.StatusBadRequest, "bad request") +} +``` + +### Fontes de dados + +| Tag | Fonte | +| -------- | ----- | +| `query` | Parâmetro de query | +| `param` | Parâmetro de caminho | +| `header` | Valor de header | +| `form` | Dados de formulário (query + body) | +| `json` | Body do request (`encoding/json`) | +| `xml` | Body do request (`encoding/xml`) | + +Campos de path, query, header e form exigem uma **tag explícita**. JSON e XML usam +o nome do campo da struct quando a tag é omitida, igual à biblioteca padrão. + +### Tipos de conteúdo do body + +Ao decodificar o body do request, o header `Content-Type` seleciona o decoder: + +- `application/json` +- `application/xml` +- `application/x-www-form-urlencoded` + +### Múltiplas fontes e precedência + +Um campo pode declarar várias fontes. Os dados fazem binding nesta ordem, cada etapa +sobrescrevendo a anterior: + +1. Parâmetros de caminho +2. Parâmetros de query (somente GET / DELETE) +3. Body do request + +```go +type User struct { + ID string `param:"id" query:"id" form:"id" json:"id" xml:"id"` +} +``` + +### Binding direto de uma fonte + +```go +echo.BindBody(c, &payload) // request body +echo.BindQueryParams(c, &payload) // query parameters +echo.BindPathValues(c, &payload) // path parameters +echo.BindHeaders(c, &payload) // headers +``` + +:::note +Headers **não** são incluídos por `c.Bind()`. Faça binding deles diretamente com `echo.BindHeaders`. +::: + +:::caution[Segurança] +Não faça binding diretamente em structs de negócio. Se uma struct vinculada expuser um campo `IsAdmin bool`, +um body de request `{"IsAdmin": true}` o definiria. Use um DTO dedicado e faça o mapeamento +explicitamente: +::: + +```go +type UserDTO struct { + Name string `json:"name" form:"name" query:"name"` + Email string `json:"email" form:"email" query:"email"` +} + +e.POST("/users", func(c *echo.Context) error { + var dto UserDTO + if err := c.Bind(&dto); err != nil { + return c.String(http.StatusBadRequest, "bad request") + } + user := User{Name: dto.Name, Email: dto.Email, IsAdmin: false} + executeSomeBusinessLogic(user) + return c.JSON(http.StatusOK, user) +}) +``` + +## Binding fluente + +Para binding explícito e type-safe de uma única fonte, use os binders fluentes. Eles +encadeiam configuração e execução, coletando erros: + +```go +// /api/search?active=true&id=1&id=2&id=3&length=25 +var opts struct { + IDs []int64 + Active bool +} +length := int64(50) + +err := echo.QueryParamsBinder(c). + Int64("length", &length). + Int64s("id", &opts.IDs). + Bool("active", &opts.Active). + BindError() // first error, if any +``` + +Binders disponíveis: `echo.QueryParamsBinder(c)`, `echo.PathValuesBinder(c)`, +`echo.FormFieldBinder(c)`. Termine uma cadeia com `BindError()` (primeiro erro) ou +`BindErrors()` (todos os erros). `FailFast(false)` executa a cadeia inteira; ele vem ativado por padrão. + +Cada tipo suportado oferece métodos `Type(...)`, `MustType(...)`, `Types(...)` (slices) e +`MustTypes(...)` — por exemplo, `Int64`, `MustInt64`, `Int64s`. Use +`BindWithDelimiter("id", &dest, ",")` para separar valores unidos por vírgula. + +## Binder customizado + +Registre um binder customizado via `Echo#Binder`: + +```go +type CustomBinder struct{} + +func (cb *CustomBinder) Bind(c *echo.Context, i any) error { + db := new(echo.DefaultBinder) + if err := db.Bind(c, i); err != echo.ErrUnsupportedMediaType { + return err + } + // custom logic here + return nil +} + +e.Binder = &CustomBinder{} +``` diff --git a/site/src/content/docs/pt-br/guide/context.md b/site/src/content/docs/pt-br/guide/context.md new file mode 100644 index 00000000..4027be0d --- /dev/null +++ b/site/src/content/docs/pt-br/guide/context.md @@ -0,0 +1,78 @@ +--- +title: Context +description: O objeto por request que carrega request, response, parâmetros e helpers. +sidebar: + order: 4 +--- + +`echo.Context` representa o contexto do request HTTP atual. Um ponteiro para ele +(`*echo.Context`) é passado para todo handler e middleware, carregando o request e a +response, parâmetros de caminho, dados vinculados e helpers para criar responses. + +```go +func handler(c *echo.Context) error { + // ... + return nil +} +``` + +## Ler entrada + +```go +id := c.Param("id") // path parameter +q := c.QueryParam("q") // query string value +all := c.QueryParams() // url.Values of all query params +name := c.FormValue("name") // form field (URL + body) +ua := c.Request().Header.Get(echo.HeaderUserAgent) +``` + +Há helpers `*Or` correspondentes que retornam um padrão quando um valor está ausente — +`c.ParamOr("id", "0")`, `c.QueryParamOr("page", "1")`, `c.FormValueOr(...)`. + +## Escrever responses + +```go +c.String(http.StatusOK, "plain text") +c.JSON(http.StatusOK, payload) +c.JSONPretty(http.StatusOK, payload, " ") +c.HTML(http.StatusOK, "hi") +c.XML(http.StatusOK, payload) +c.Blob(http.StatusOK, "application/pdf", bytes) +c.Stream(http.StatusOK, "application/octet-stream", reader) +c.NoContent(http.StatusNoContent) +c.Redirect(http.StatusFound, "/elsewhere") +``` + +## Arquivos + +```go +c.File("public/report.pdf") // serve a file +c.Attachment("invoice.pdf", "inv.pdf") // prompt download +c.Inline("photo.png", "photo.png") // render inline +``` + +## Armazenamento por request + +Compartilhe dados entre middleware e handlers com `Get`/`Set`: + +```go +c.Set("user", u) +u, _ := c.Get("user").(*User) +``` + +Acesso tipado está disponível por meio dos helpers de generics: + +```go +u, err := echo.ContextGet[*User](c, "user") +``` + +## Binding e validação + +`c.Bind()` analisa dados do request em uma struct; veja [Binding](/pt-br/guide/binding/). + +```go +var dto CreateUser +if err := c.Bind(&dto); err != nil { + return echo.ErrBadRequest +} +``` diff --git a/site/src/content/docs/pt-br/guide/cookies.md b/site/src/content/docs/pt-br/guide/cookies.md new file mode 100644 index 00000000..d9309427 --- /dev/null +++ b/site/src/content/docs/pt-br/guide/cookies.md @@ -0,0 +1,72 @@ +--- +title: Cookies +description: Crie, leia e liste HTTP cookies usando o tipo padrão http.Cookie. +sidebar: + order: 11 +--- + +Um cookie é um pequeno pedaço de dados que um servidor envia ao navegador, que o navegador +armazena e envia de volta em requests subsequentes. Cookies permitem que sites lembrem +informações com estado, como carrinho de compras, estado de autenticação ou valores de formulário +inseridos anteriormente. + +Echo usa o tipo padrão `http.Cookie` do Go para adicionar e recuperar cookies do +`echo.Context` em um handler. + +## Atributos de cookie + +| Atributo | Opcional | +| ---------- | -------- | +| `Name` | Não | +| `Value` | Não | +| `Path` | Sim | +| `Domain` | Sim | +| `Expires` | Sim | +| `Secure` | Sim | +| `HttpOnly` | Sim | + +## Criar um cookie + +```go +func writeCookie(c *echo.Context) error { + cookie := new(http.Cookie) + cookie.Name = "username" + cookie.Value = "jon" + cookie.Expires = time.Now().Add(24 * time.Hour) + c.SetCookie(cookie) + return c.String(http.StatusOK, "write a cookie") +} +``` + +- Crie o cookie com `new(http.Cookie)`. +- Defina atributos nos campos de `http.Cookie`. +- Chame `c.SetCookie(cookie)` para adicionar um header `Set-Cookie` à response. + +## Ler um cookie + +```go +func readCookie(c *echo.Context) error { + cookie, err := c.Cookie("username") + if err != nil { + return err + } + fmt.Println(cookie.Name) + fmt.Println(cookie.Value) + return c.String(http.StatusOK, "read a cookie") +} +``` + +- Leia um cookie por nome com `c.Cookie("username")`. +- Acesse seus atributos por meio dos campos de `http.Cookie`. + +## Ler todos os cookies + +```go +func readAllCookies(c *echo.Context) error { + for _, cookie := range c.Cookies() { + fmt.Println(cookie.Name) + fmt.Println(cookie.Value) + } + return c.String(http.StatusOK, "read all the cookies") +} +``` diff --git a/site/src/content/docs/pt-br/guide/customization.md b/site/src/content/docs/pt-br/guide/customization.md new file mode 100644 index 00000000..5503b808 --- /dev/null +++ b/site/src/content/docs/pt-br/guide/customization.md @@ -0,0 +1,63 @@ +--- +title: Customização +description: Customize logger, validator, binder, renderer, serializer e tratamento de erros do Echo. +sidebar: + order: 12 +--- + +Echo expõe um conjunto de campos na instância `Echo` que permite substituir o comportamento +embutido pelas suas próprias implementações. + +## Logging + +`Echo#Logger` escreve logs estruturados. O handler padrão emite JSON para `os.Stdout`. + +### Logger customizado + +O logger é um `*slog.Logger`, então você pode registrar qualquer handler `slog`: + +```go +e.Logger = slog.New(slog.NewJSONHandler(os.Stdout, nil)) +``` + +## Validator + +`Echo#Validator` registra um validador para validação de payloads de request. + +[Saiba mais](/pt-br/guide/request/#validate-data) + +## Binder customizado + +`Echo#Binder` registra um binder customizado para binding de payloads de request. + +[Saiba mais](/pt-br/guide/binding/#custom-binder) + +## Serializer JSON customizado + +`Echo#JSONSerializer` registra um serializer JSON customizado. Veja `DefaultJSONSerializer` +em [json.go](https://github.com/labstack/echo/blob/master/json.go). + +## Renderer + +`Echo#Renderer` registra um renderer para renderização de templates. + +[Saiba mais](/pt-br/guide/templates/) + +## Handler de erro HTTP + +`Echo#HTTPErrorHandler` registra um handler de erro HTTP customizado. + +[Saiba mais](/pt-br/guide/error-handling/) + +## Callback de rota + +`Echo#OnAddRoute` registra um callback chamado sempre que uma nova rota é adicionada ao +router. + +## Extrator de IP + +`Echo#IPExtractor` controla como o endereço IP real do cliente é determinado. Para +recuperá-lo de forma confiável e segura, sua aplicação precisa conhecer toda a sua +infraestrutura. + +[Saiba mais](/pt-br/guide/ip-address/) diff --git a/site/src/content/docs/pt-br/guide/error-handling.md b/site/src/content/docs/pt-br/guide/error-handling.md new file mode 100644 index 00000000..c4b2fefb --- /dev/null +++ b/site/src/content/docs/pt-br/guide/error-handling.md @@ -0,0 +1,82 @@ +--- +title: Tratamento de erros +description: Tratamento centralizado de erros HTTP retornando erros de handlers e middleware. +sidebar: + order: 6 +--- + +Echo defende o tratamento de erros **centralizado**: handlers e middleware retornam um +`error`, e um único handler de erros o transforma em uma response HTTP. Isso mantém logs +e formatação de response em um só lugar. + +Retorne um `error` simples ou um `*echo.HTTPError`: + +```go +e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + if !authenticated(c) { + // invalid credentials → abort with 401 + return echo.NewHTTPError(http.StatusUnauthorized, "Please provide valid credentials") + } + return next(c) + } +}) +``` + +`echo.NewHTTPError(code)` sem mensagem usa o texto do status (por exemplo, `"Unauthorized"`). +Echo também fornece erros sentinela como `echo.ErrBadRequest`, `echo.ErrNotFound` e +`echo.ErrUnauthorized`. + +## Handler de erro padrão + +O handler padrão do Echo responde em JSON: + +```json +{ "message": "error connecting to redis" } +``` + +Um `error` simples vira `500 Internal Server Error` (a mensagem original é incluída +quando a aplicação roda com erros expostos). Um `*HTTPError` usa seu código de status e mensagem. + +## Handler de erro customizado + +Defina o seu via `e.HTTPErrorHandler` — útil para páginas de erro, notificações ou +envio de erros para um sistema centralizado. + +Verifique se a response já foi enviada com `echo.UnwrapResponse()`, e encontre um +código de status na cadeia de erros via `echo.HTTPStatusCoder`: + +```go +func customHTTPErrorHandler(c *echo.Context, err error) { + if resp, uErr := echo.UnwrapResponse(c.Response()); uErr == nil { + if resp.Committed { + return // already sent by a handler/middleware + } + } + + code := http.StatusInternalServerError + var sc echo.HTTPStatusCoder + if errors.As(err, &sc) { + if tmp := sc.StatusCode(); tmp != 0 { + code = tmp + } + } + + var cErr error + if c.Request().Method == http.MethodHead { + cErr = c.NoContent(code) + } else { + cErr = c.File(fmt.Sprintf("%d.html", code)) // e.g. 404.html, 500.html + } + if cErr != nil { + c.Logger().Error("failed to send error page", "error", errors.Join(err, cErr)) + } +} + +e.HTTPErrorHandler = customHTTPErrorHandler +``` + +:::tip +Em vez de (ou além de) usar o logger, encaminhe erros para um serviço externo como +Sentry, Elasticsearch ou Splunk a partir do handler central. +::: diff --git a/site/src/content/docs/pt-br/guide/installation.md b/site/src/content/docs/pt-br/guide/installation.md new file mode 100644 index 00000000..a84e4fae --- /dev/null +++ b/site/src/content/docs/pt-br/guide/installation.md @@ -0,0 +1,57 @@ +--- +title: Instalação +description: Adicione o Echo v5 ao seu módulo Go. +sidebar: + order: 2 +--- + +O Echo é distribuído como um módulo Go: `github.com/labstack/echo/v5`. + +## Requisitos + +O Echo v5 requer **Go 1.25 ou mais recente**. + +```bash +go version +``` + +## Adicionar a um projeto + +Dentro de um módulo existente: + +```bash +go get github.com/labstack/echo/v5 +``` + +Ou inicie um novo módulo: + +```bash +mkdir myapp && cd myapp +go mod init myapp +go get github.com/labstack/echo/v5 +``` + +Importe-o no seu código: + +```go +import "github.com/labstack/echo/v5" +``` + +## Versões + +| Versão | Caminho de importação | Status | +| ------ | ------------------------------ | ------ | +| **v5** | `github.com/labstack/echo/v5` | Atual | +| v4 | `github.com/labstack/echo/v4` | LTS (manutenção) | + +:::note +O Echo segue [semantic import versioning](https://go.dev/blog/v2-go-modules) — a +versão principal faz parte do caminho de importação, então v4 e v5 podem coexistir durante uma migração. +::: + +## Manter atualizado + +```bash +go get github.com/labstack/echo/v5 +go mod tidy +``` diff --git a/site/src/content/docs/pt-br/guide/ip-address.md b/site/src/content/docs/pt-br/guide/ip-address.md new file mode 100644 index 00000000..76c18740 --- /dev/null +++ b/site/src/content/docs/pt-br/guide/ip-address.md @@ -0,0 +1,117 @@ +--- +title: Endereço IP +description: Recupere o endereço IP real do cliente com segurança atrás de proxies. +sidebar: + order: 14 +--- + +O endereço IP tem um papel fundamental em HTTP — ele é usado para controle de acesso, +auditoria, análise baseada em geografia e mais. Echo expõe `Context#RealIP()` para recuperá-lo. + +Recuperar o IP _real_ do cliente não é trivial, especialmente quando proxies L7 ficam na +frente da sua aplicação. Nesse caso, o IP real precisa ser repassado por HTTP dos +proxies para sua app — mas você não deve confiar incondicionalmente em headers HTTP, ou +corre o risco de ser enganado. **Isso é um risco de segurança.** + +Para recuperar o IP de forma confiável e segura, sua aplicação precisa conhecer toda a sua +infraestrutura. No Echo, você configura isso por meio de `Echo#IPExtractor`. + +:::caution +Se você não definir `Echo#IPExtractor` explicitamente, Echo recorre ao comportamento legado, +que não é um padrão seguro. +::: + +Comece com duas perguntas para encontrar a abordagem correta: + +1. Você coloca algum proxy HTTP (L7) na frente da aplicação? Isso inclui load balancers + de nuvem (como AWS ALB ou GCP HTTP LB) e proxies open-source (como Nginx, Envoy ou + um gateway de ingresso Istio). +2. Se sim, qual header HTTP seus proxies usam para passar o IP do cliente para a + aplicação? + +## Caso 1: Sem proxy + +Se não há proxy (a app encara a internet diretamente), o único endereço em que você pode +confiar é o da camada de rede. Todo header HTTP não é confiável porque +clientes têm controle total sobre eles. + +Use `echo.ExtractIPDirect()`: + +```go +e.IPExtractor = echo.ExtractIPDirect() +``` + +## Caso 2: Proxies usando o header X-Forwarded-For + +[`X-Forwarded-For` (XFF)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) +é o header mais comum para retransmitir IPs de clientes. A cada salto, o proxy acrescenta o +IP do request ao final do header. + +```text + ┌──────────┐ ┌──────────┐ ┌──────────┐ +───────────>│ Proxy 1 │───────────>│ Proxy 2 │───────────>│ Your app │ + │ (IP: b) │ │ (IP: c) │ │ │ + └──────────┘ └──────────┘ └──────────┘ + +Case 1. +XFF: "" "a" "a, b" + ~~~~~~ +Case 2. +XFF: "x" "x, a" "x, a, b" + ~~~~~~~~~ + ↑ What your app will see +``` + +Nesse caso, pegue a **primeira leitura de IP não confiável a partir da direita**. Nunca pegue +a primeira da esquerda, pois o cliente a controla. Aqui "confiável" significa +que você tem certeza de que o IP pertence à sua infraestrutura. No exemplo acima, se `b` e +`c` são confiáveis, o IP do cliente é `a` nos dois casos — nunca `x`. + +Use `ExtractIPFromXFFHeader(...TrustOption)`: + +```go +e.IPExtractor = echo.ExtractIPFromXFFHeader() +``` + +Por padrão, ele confia em endereços IP internos — loopback, link-local unicast, +private-use e unique local addresses de +[RFC 6890](https://datatracker.ietf.org/doc/html/rfc6890), +[RFC 4291](https://datatracker.ietf.org/doc/html/rfc4291) e +[RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193). Controle isso com `TrustOption`s: + +```go +e.IPExtractor = echo.ExtractIPFromXFFHeader( + echo.TrustLoopback(false), // e.g. IPv4 starting with 127. + echo.TrustLinkLocal(false), // e.g. IPv4 starting with 169.254. + echo.TrustPrivateNet(false), // e.g. IPv4 starting with 10. or 192.168. + echo.TrustIPRange(lbIPRange), +) +``` + +## Caso 3: Proxies usando o header X-Real-IP + +`X-Real-IP` é outro header para retransmitir o IP do cliente, mas diferentemente de XFF ele carrega +apenas um único endereço. + +Se seus proxies definem este header, use `ExtractIPFromRealIPHeader(...TrustOption)`: + +```go +e.IPExtractor = echo.ExtractIPFromRealIPHeader() +``` + +Assim como com XFF, ele confia em endereços IP internos por padrão e aceita as mesmas +`TrustOption`s. + +:::danger +**Nunca esqueça** de configurar o proxy mais externo (na borda da sua +infraestrutura) para **não repassar headers recebidos**. Caso contrário, um cliente pode +forjá-los, abrindo espaço para fraude. +::: + +## Comportamento padrão + +Por padrão, Echo considera ao mesmo tempo o primeiro header XFF, o header X-Real-IP e o IP +da camada de rede. + +Como este artigo deve deixar claro, essa não é uma boa escolha. Ela continua sendo o padrão +apenas por compatibilidade retroativa. diff --git a/site/src/content/docs/pt-br/guide/quickstart.md b/site/src/content/docs/pt-br/guide/quickstart.md new file mode 100644 index 00000000..feef7f1d --- /dev/null +++ b/site/src/content/docs/pt-br/guide/quickstart.md @@ -0,0 +1,76 @@ +--- +title: Início rápido +description: Crie uma API Echo pronta para produção em menos de cinco minutos. +sidebar: + order: 1 +--- + +Echo é um framework web Go minimalista e de alta performance. Este guia coloca um +servidor em execução em menos de cinco minutos. + +## Requisitos + +Echo requer **Go 1.25 ou mais recente**. Verifique sua versão: + +```bash +go version +``` + +## Instalar + +Crie um módulo e adicione o Echo: + +```bash +go mod init myapp +go get github.com/labstack/echo/v5 +``` + +## Hello, World + +Crie `main.go`: + +```go +package main + +import ( + "net/http" + + "github.com/labstack/echo/v5" + "github.com/labstack/echo/v5/middleware" +) + +func main() { + e := echo.New() + + e.Use(middleware.RequestLogger()) + e.Use(middleware.Recover()) + + e.GET("/", func(c *echo.Context) error { + return c.JSON(http.StatusOK, map[string]string{"message": "Hello, World!"}) + }) + + if err := e.Start(":1323"); err != nil { + e.Logger.Error("failed to start server", "error", err) + } +} +``` + +Execute: + +```bash +go run main.go +``` + +Seu servidor está ativo em `http://localhost:1323`. O router do Echo despacha requests +com **zero alocação dinâmica de memória** por rota. + +:::tip[Ask Echo] +Travou? Pressione o botão **Ask Echo** (canto inferior direito) e pergunte +*"How do I add JWT auth?"* — as respostas vêm diretamente destes docs. +::: + +## Próximos passos + +- [Routing](/pt-br/guide/routing/) — rotas estáticas, parametrizadas e com wildcard. +- [Context](/pt-br/guide/context/) — o objeto request/response por request. +- [Binding](/pt-br/guide/binding/) — analise dados do request em structs tipadas. diff --git a/site/src/content/docs/pt-br/guide/request.md b/site/src/content/docs/pt-br/guide/request.md new file mode 100644 index 00000000..05a9c27b --- /dev/null +++ b/site/src/content/docs/pt-br/guide/request.md @@ -0,0 +1,170 @@ +--- +title: Request +description: Recupere dados de formulário, query e path de um request, e valide-os. +sidebar: + order: 7 +--- + +Um handler lê dados do request por meio do `echo.Context`. Echo pode recuperar valores +individualmente por nome, fazer binding deles em structs (veja [Binding](/pt-br/guide/binding/)) e +delegar a validação para um validador que você registra. + +## Recuperar dados + +### Dados de formulário + +Recupere um campo de formulário por nome com `Context#FormValue(name string)`: + +```go +e.POST("/form", func(c *echo.Context) error { + name := c.FormValue("name") + return c.String(http.StatusOK, name) +}) +``` + +Para tipos diferentes de `string`, use a função genérica `echo.FormValue[T]`: + +```go +age, err := echo.FormValue[int](c, "age") +if err != nil { + return err +} +``` + +Teste com: + +```sh +curl -X POST http://localhost:1323/form -d 'name=Joe&age=30' +``` + +Para fazer binding de um tipo de dado customizado, implemente a interface `echo.BindUnmarshaler`: + +```go +type Timestamp time.Time + +func (t *Timestamp) UnmarshalParam(src string) error { + ts, err := time.Parse(time.RFC3339, src) + if err != nil { + return err + } + *t = Timestamp(ts) + return nil +} +``` + +### Parâmetros de query + +Recupere um parâmetro de query por nome com `Context#QueryParam(name string)`: + +```go +func(c *echo.Context) error { + name := c.QueryParam("name") + return c.String(http.StatusOK, name) +} +``` + +Para tipos diferentes de `string`, use a função genérica `echo.QueryParam[T]`: + +```go +age, err := echo.QueryParam[int](c, "age") +if err != nil { + return err +} +``` + +```sh +curl -X GET "http://localhost:1323?name=Joe&age=30" +``` + +### Parâmetros de caminho + +Recupere um parâmetro de caminho registrado por nome com `Context#Param(name string)`: + +```go +e.GET("/users/:name", func(c *echo.Context) error { + name := c.Param("name") + return c.String(http.StatusOK, name) +}) +``` + +Para tipos diferentes de `string`, use a função genérica `echo.PathParam[T]`: + +```go +id, err := echo.PathParam[int](c, "id") +if err != nil { + return err +} +``` + +```sh +curl http://localhost:1323/users/Joe +curl http://localhost:1323/users/123 +``` + +### Binding de dados + +Echo também pode fazer binding de dados do request em structs e variáveis nativas do Go. Veja +[Binding](/pt-br/guide/binding/). + +## Validar dados + +Echo não tem validação de dados embutida. Você pode registrar um validador customizado via +`Echo#Validator` e usar uma biblioteca de terceiros como +[go-playground/validator](https://github.com/go-playground/validator). + +O exemplo abaixo valida uma struct vinculada: + +```go +package main + +import ( + "net/http" + + "github.com/go-playground/validator/v10" // go get github.com/go-playground/validator/v10 + "github.com/labstack/echo/v5" +) + +type CustomValidator struct { + validator *validator.Validate +} + +func (cv *CustomValidator) Validate(i any) error { + if err := cv.validator.Struct(i); err != nil { + // Optionally return the error to let each route control the status code. + return echo.ErrBadRequest.Wrap(err) + } + return nil +} + +type User struct { + Name string `json:"name" validate:"required"` + Email string `json:"email" validate:"required,email"` +} + +func main() { + e := echo.New() + e.Validator = &CustomValidator{validator: validator.New()} + + e.POST("/users", func(c *echo.Context) error { + u := new(User) + if err := c.Bind(u); err != nil { + return err + } + if err := c.Validate(u); err != nil { + return err + } + return c.JSON(http.StatusOK, u) + }) + + if err := e.Start(":1323"); err != nil { + e.Logger.Error("failed to start server", "error", err) + } +} +``` + +```sh +curl -X POST http://localhost:1323/users \ + -H 'Content-Type: application/json' \ + -d '{"name":"Joe","email":"joe@invalid-domain"}' +{"message":"Key: 'User.Email' Error:Field validation for 'Email' failed on the 'email' tag"} +``` diff --git a/site/src/content/docs/pt-br/guide/response.md b/site/src/content/docs/pt-br/guide/response.md new file mode 100644 index 00000000..eaeac053 --- /dev/null +++ b/site/src/content/docs/pt-br/guide/response.md @@ -0,0 +1,322 @@ +--- +title: Response +description: Envie strings, HTML, JSON, XML, arquivos, streams, redirects e hooks de response. +sidebar: + order: 8 +--- + +Um handler escreve sua response por meio do `echo.Context`. Cada helper define o +`Content-Type` e o código de status apropriados para você. + +## Enviar string + +`Context#String(code int, s string)` envia uma response de texto simples com um código de status. + +```go +func(c *echo.Context) error { + return c.String(http.StatusOK, "Hello, World!") +} +``` + +## Enviar HTML + +`Context#HTML(code int, html string)` envia uma response HTML simples com um código de +status. Para gerar HTML dinamicamente, veja [Templates](/pt-br/guide/templates/). + +```go +func(c *echo.Context) error { + return c.HTML(http.StatusOK, "Hello, World!") +} +``` + +### Enviar blob HTML + +`Context#HTMLBlob(code int, b []byte)` envia um blob HTML com um código de status. Ele é +útil com um mecanismo de templates que produz `[]byte`. + +```go +func handler(c *echo.Context) error { + blob := []byte("Hello, World!") + return c.HTMLBlob(http.StatusOK, blob) +} +``` + +## Renderizar template + +Veja [Templates](/pt-br/guide/templates/). + +## Enviar JSON + +`Context#JSON(code int, i any)` codifica um valor Go como JSON e o envia com um +código de status. + +```go +type User struct { + Name string `json:"name" xml:"name"` + Email string `json:"email" xml:"email"` +} + +func(c *echo.Context) error { + u := &User{ + Name: "Jon", + Email: "jon@labstack.com", + } + return c.JSON(http.StatusOK, u) +} +``` + +### Stream JSON + +`Context#JSON()` usa `json.Marshal` internamente, o que pode ser ineficiente para payloads grandes. +Nesse caso, envie o JSON diretamente como stream: + +```go +func(c *echo.Context) error { + u := &User{ + Name: "Jon", + Email: "jon@labstack.com", + } + c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) + c.Response().WriteHeader(http.StatusOK) + return json.NewEncoder(c.Response()).Encode(u) +} +``` + +### JSON pretty + +`Context#JSONPretty(code int, i any, indent string)` envia uma response JSON formatada. +O indent pode ser espaços ou tabs. + +```go +func(c *echo.Context) error { + u := &User{ + Name: "Jon", + Email: "jon@labstack.com", + } + return c.JSONPretty(http.StatusOK, u, " ") +} +``` + +```json +{ + "email": "jon@labstack.com", + "name": "Jon" +} +``` + +### Blob JSON + +`Context#JSONBlob(code int, b []byte)` envia um blob JSON pré-codificado diretamente, por +exemplo vindo de um banco de dados. + +```go +func(c *echo.Context) error { + encodedJSON := []byte{} // Encoded JSON from an external source. + return c.JSONBlob(http.StatusOK, encodedJSON) +} +``` + +## Enviar JSONP + +`Context#JSONP(code int, callback string, i any)` codifica um valor Go como JSON e +o envia como um payload JSONP envolvido no callback informado. + +```go +func handler(c *echo.Context) error { + callback := c.QueryParam("callback") + return c.JSONP(http.StatusOK, callback, &User{Name: "Jon", Email: "jon@labstack.com"}) +} +``` + +Veja a [receita de JSONP](/pt-br/cookbook/jsonp/). + +## Enviar XML + +`Context#XML(code int, i any)` codifica um valor Go como XML e o envia com um código +de status. + +```go +func(c *echo.Context) error { + u := &User{ + Name: "Jon", + Email: "jon@labstack.com", + } + return c.XML(http.StatusOK, u) +} +``` + +### Stream XML + +`Context#XML` usa `xml.Marshal` internamente, o que pode ser ineficiente para payloads grandes. +Nesse caso, envie o XML diretamente como stream: + +```go +func(c *echo.Context) error { + u := &User{ + Name: "Jon", + Email: "jon@labstack.com", + } + c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationXMLCharsetUTF8) + c.Response().WriteHeader(http.StatusOK) + return xml.NewEncoder(c.Response()).Encode(u) +} +``` + +### XML pretty + +`Context#XMLPretty(code int, i any, indent string)` envia uma response XML formatada. +O indent pode ser espaços ou tabs. + +```go +func(c *echo.Context) error { + u := &User{ + Name: "Jon", + Email: "jon@labstack.com", + } + return c.XMLPretty(http.StatusOK, u, " ") +} +``` + +```xml + + + Jon + jon@labstack.com + +``` + +:::tip +Você também pode fazer `Context#XML()` gerar XML formatado adicionando `pretty` +à query string da URL do request. + +```sh +curl http://localhost:1323/users/1?pretty +``` +::: + +### Blob XML + +`Context#XMLBlob(code int, b []byte)` envia um blob XML pré-codificado diretamente, por +exemplo vindo de um banco de dados. + +```go +func(c *echo.Context) error { + encodedXML := []byte{} // Encoded XML from an external source. + return c.XMLBlob(http.StatusOK, encodedXML) +} +``` + +## Enviar arquivo + +`Context#File(file string)` envia o conteúdo de um arquivo como a response. Ele define +o tipo de conteúdo correto e lida com cache automaticamente. + +```go +func(c *echo.Context) error { + return c.File("") +} +``` + +## Enviar attachment + +`Context#Attachment(file, name string)` é como `File()`, mas envia o arquivo com +`Content-Disposition: attachment` e o nome informado. + +```go +func(c *echo.Context) error { + return c.Attachment("", "") +} +``` + +## Enviar inline + +`Context#Inline(file, name string)` é como `File()`, mas envia o arquivo com +`Content-Disposition: inline` e o nome informado. + +```go +func(c *echo.Context) error { + return c.Inline("", "") +} +``` + +## Enviar blob + +`Context#Blob(code int, contentType string, b []byte)` envia dados arbitrários com um +tipo de conteúdo e código de status informados. + +```go +func(c *echo.Context) error { + data := []byte(`0306703,0035866,NO_ACTION,06/19/2006 +0086003,"0005866",UPDATED,06/19/2006`) + return c.Blob(http.StatusOK, "text/csv", data) +} +``` + +## Enviar stream + +`Context#Stream(code int, contentType string, r io.Reader)` envia um stream de dados +arbitrário com um tipo de conteúdo, `io.Reader` e código de status informados. + +```go +func(c *echo.Context) error { + f, err := os.Open("") + if err != nil { + return err + } + defer f.Close() + return c.Stream(http.StatusOK, "image/png", f) +} +``` + +## Enviar sem conteúdo + +`Context#NoContent(code int)` envia um body vazio com um código de status. + +```go +func(c *echo.Context) error { + return c.NoContent(http.StatusOK) +} +``` + +## Redirecionar request + +`Context#Redirect(code int, url string)` redireciona o request para a URL informada com +um código de status. + +```go +func(c *echo.Context) error { + return c.Redirect(http.StatusMovedPermanently, "") +} +``` + +## Hooks + +### Antes da response + +`Response#Before(func())` registra uma função que roda pouco antes de a response ser +escrita. + +### Depois da response + +`Response#After(func())` registra uma função que roda logo depois de a response ser +escrita. Se o `Content-Length` for desconhecido, nenhuma função after roda. + +```go +e.GET("/hooks", func(c *echo.Context) error { + resp, err := echo.UnwrapResponse(c.Response()) + if err != nil { + return err + } + resp.Before(func() { + println("before response") + }) + resp.After(func() { + println("after response") + }) + return c.String(http.StatusOK, "Hello, World!") +}) +``` + +:::tip +Você pode registrar várias funções `Before` e `After`. +::: diff --git a/site/src/content/docs/pt-br/guide/routing.md b/site/src/content/docs/pt-br/guide/routing.md new file mode 100644 index 00000000..2816286d --- /dev/null +++ b/site/src/content/docs/pt-br/guide/routing.md @@ -0,0 +1,75 @@ +--- +title: Routing +description: Associe URLs de request a handlers na árvore radix de alocação zero do Echo. +sidebar: + order: 3 +--- + +O router otimizado do Echo associa URLs de request a handlers usando uma árvore radix com +**zero alocação dinâmica de memória** e priorização inteligente de rotas. + +## Registrar rotas + +Use os helpers de método HTTP na instância `Echo`. Cada um recebe um padrão de caminho e um +`HandlerFunc` (`func(c *echo.Context) error`), com middleware opcional no nível da rota. + +```go +e := echo.New() + +e.GET("/users/:id", getUser) // named parameter +e.POST("/users", createUser) +e.PUT("/users/:id", updateUser) +e.DELETE("/users/:id", deleteUser) +e.GET("/static/*", serveFiles) // wildcard +``` + +`Any` registra um handler para todos os métodos suportados, e `Match` para um conjunto específico: + +```go +e.Any("/ping", pong) +e.Match([]string{http.MethodGet, http.MethodPost}, "/form", handleForm) +``` + +## Tipos de correspondência + +| Padrão | Tipo | Exemplo correspondente | +| ----------------- | -------- | -------------------------- | +| `/users/profile` | Static | `/users/profile` | +| `/users/:id` | Param | `/users/42` | +| `/static/*` | Wildcard | `/static/css/app.css` | + +:::note +A prioridade é **static → param → wildcard**, então `/users/profile` sempre vence +`/users/:id`, que vence `/users/*`. +::: + +## Parâmetros de caminho + +Leia parâmetros nomeados do contexto com `c.Param()` (ou `c.ParamOr()` para um valor padrão): + +```go +func getUser(c *echo.Context) error { + id := c.Param("id") + return c.String(http.StatusOK, id) +} +``` + +O segmento wildcard fica disponível como o parâmetro `*`: + +```go +e.GET("/files/*", func(c *echo.Context) error { + return c.String(http.StatusOK, c.Param("*")) +}) +``` + +## Grupos + +Agrupe rotas que compartilham prefixo e middleware com `e.Group()`: + +```go +admin := e.Group("/admin", middleware.BasicAuth(authFn)) +admin.GET("/metrics", metrics) // -> /admin/metrics +admin.GET("/users", listUsers) // -> /admin/users +``` + +Grupos podem ser aninhados para compor árvores de rotas maiores. diff --git a/site/src/content/docs/pt-br/guide/static-files.md b/site/src/content/docs/pt-br/guide/static-files.md new file mode 100644 index 00000000..434a3483 --- /dev/null +++ b/site/src/content/docs/pt-br/guide/static-files.md @@ -0,0 +1,89 @@ +--- +title: Servindo arquivos estáticos +description: Sirva imagens, JavaScript, CSS, fontes e outros assets com Echo. +sidebar: + order: 9 +--- + +Echo pode servir assets estáticos como imagens, JavaScript, CSS, PDFs e fontes a partir +do filesystem ou de um filesystem incorporado. + +## Filesystem padrão + +Echo usa `os.DirFS(".")` como seu filesystem padrão, com raiz no diretório de trabalho +atual. Para alterá-lo, defina o campo `Echo#Filesystem`: + +```go +e := echo.New() +e.Filesystem = os.DirFS("assets") +``` + +## Usando o middleware Static + +Veja [middleware Static](/pt-br/middleware/static/). + +## Usando Echo#Static() + +`Echo#Static(prefix, root string)` registra uma rota que serve arquivos estáticos sob +um prefixo de caminho a partir do diretório raiz informado. + +Sirva qualquer arquivo de `assets` sob `/static/*`. Um request para `/static/js/main.js` +serve `assets/js/main.js`: + +```go +e := echo.New() +e.Static("/static", "assets") +``` + +Sirva qualquer arquivo de `assets` sob `/*`. Um request para `/js/main.js` serve +`assets/js/main.js`: + +```go +e := echo.New() +e.Static("/", "assets") +``` + +## Usando Echo#StaticFS() + +Arquivos estáticos podem ser servidos a partir de qualquer `fs.FS`, incluindo um `embed.FS`. Use +`echo.MustSubFS` para que os arquivos servidos tenham raiz no subdiretório correto — um +`embed.FS` inclui seus subdiretórios como entradas próprias. + +```go +//go:embed "assets/images" +var images embed.FS + +func main() { + e := echo.New() + + e.StaticFS("/images", echo.MustSubFS(images, "assets/images")) + + sc := echo.StartConfig{Address: ":1323"} + if err := sc.Start(context.Background(), e); err != nil { + e.Logger.Error("failed to start server", "error", err) + } +} +``` + +## Usando Echo#File() + +`Echo#File(path, file string)` registra uma rota que serve um único arquivo estático. + +Sirva uma página index de `public/index.html`: + +```go +e.File("/", "public/index.html") +``` + +Sirva um favicon de `app/assets/favicon.ico`: + +```go +e := echo.New() +e.Filesystem = os.DirFS("/") +e.File("/favicon.ico", "app/assets/favicon.ico") // The file path must not have a leading slash. +``` + +:::caution +Um `/` inicial no caminho do arquivo não funciona com a maioria das implementações de `fs.FS`. Use +um caminho relativo. +::: diff --git a/site/src/content/docs/pt-br/guide/templates.md b/site/src/content/docs/pt-br/guide/templates.md new file mode 100644 index 00000000..f8618c85 --- /dev/null +++ b/site/src/content/docs/pt-br/guide/templates.md @@ -0,0 +1,133 @@ +--- +title: Templates +description: Renderize templates HTML com qualquer engine registrando um renderer. +sidebar: + order: 10 +--- + +`Context#Render(code int, name string, data any) error` renderiza um template com dados +e envia uma response `text/html` com um código de status. Registre um renderer definindo +`Echo#Renderer`, o que permite usar qualquer engine de templates. + +## Renderização + +O exemplo abaixo usa `html/template` do Go. + +Use o renderer de templates padrão: + +```go +e.Renderer = &echo.TemplateRenderer{ + Template: template.Must(template.New("hello").Parse("Hello, {{.}}!")), +} +``` + +Ou implemente você mesmo a interface `echo.Renderer`: + +```go +type Template struct { + templates *template.Template +} + +func (t *Template) Render(c *echo.Context, w io.Writer, name string, data any) error { + return t.templates.ExecuteTemplate(w, name, data) +} +``` + +1. Pré-compile os templates. + + `public/views/hello.html`: + + ```html + {{define "hello"}}Hello, {{.}}!{{end}} + ``` + + ```go + t := &Template{ + templates: template.Must(template.ParseGlob("public/views/*.html")), + } + ``` + +2. Registre o renderer. + + ```go + e := echo.New() + e.Renderer = t + e.GET("/hello", Hello) + ``` + +3. Renderize um template dentro do handler. + + ```go + func Hello(c *echo.Context) error { + return c.Render(http.StatusOK, "hello", "World") + } + ``` + +## Avançado: chamando Echo a partir de templates + +Às vezes é útil gerar URIs de dentro de um template chamando +`Echo#Reverse`. `html/template` do Go não é ideal para isso, mas é possível +fazer de duas formas: fornecendo um método comum em cada objeto passado para templates, +ou passando um `map[string]any` e ampliando-o no renderer customizado. A +segunda opção é mais flexível. Aqui está um exemplo completo. + +`template.html`: + +```html + + +

Hello {{index . "name"}}

+ +

{{ with $x := index . "reverse" }} + {{ call $x "foobar" }} + {{ end }} +

+ + +``` + +`server.go`: + +```go +package main + +import ( + "html/template" + "io" + "net/http" + + "github.com/labstack/echo/v5" +) + +// TemplateRenderer is a custom html/template renderer for Echo. +type TemplateRenderer struct { + templates *template.Template +} + +// Render renders a template document. +func (t *TemplateRenderer) Render(c *echo.Context, w io.Writer, name string, data any) error { + // Add global methods if the data is a map. + if viewContext, isMap := data.(map[string]any); isMap { + viewContext["reverse"] = c.RouteInfo().Reverse + } + + return t.templates.ExecuteTemplate(w, name, data) +} + +func main() { + e := echo.New() + e.Renderer = &TemplateRenderer{ + templates: template.Must(template.ParseGlob("main/*.html")), + } + + e.GET("/something/:name", func(c *echo.Context) error { + return c.Render(http.StatusOK, "template.html", map[string]any{ + "name": "Dolly!", + }) + }) + + if err := e.Start(":1323"); err != nil { + e.Logger.Error("shutting down the server", "error", err) + } +} +``` diff --git a/site/src/content/docs/pt-br/guide/testing.md b/site/src/content/docs/pt-br/guide/testing.md new file mode 100644 index 00000000..e08bcfce --- /dev/null +++ b/site/src/content/docs/pt-br/guide/testing.md @@ -0,0 +1,264 @@ +--- +title: Testes +description: Teste handlers e middleware com httptest e os helpers echotest. +sidebar: + order: 13 +--- + +Handlers e middleware do Echo são funções simples sobre um `echo.Context`, então são +diretos de testar com o pacote padrão `net/http/httptest`. O pacote +`echotest` fornece helpers que reduzem boilerplate. + +## Testando um handler + +Considere dois handlers: + +**CreateUser** — `POST /users` + +- Aceita um payload JSON. +- Retorna `201 Created` em caso de sucesso. +- Retorna `500 Internal Server Error` em caso de erro. + +**GetUser** — `GET /users/:email` + +- Retorna `200 OK` em caso de sucesso. +- Retorna `404 Not Found` se o usuário não existir, caso contrário `500 Internal Server Error`. + +`handler.go`: + +```go +package handler + +import ( + "net/http" + + "github.com/labstack/echo/v5" +) + +type ( + User struct { + Name string `json:"name" form:"name"` + Email string `json:"email" form:"email"` + } + handler struct { + db map[string]*User + } +) + +func (h *handler) createUser(c *echo.Context) error { + u := new(User) + if err := c.Bind(u); err != nil { + return err + } + return c.JSON(http.StatusCreated, u) +} + +func (h *handler) getUser(c *echo.Context) error { + email := c.Param("email") + user := h.db[email] + if user == nil { + return echo.NewHTTPError(http.StatusNotFound, "user not found") + } + return c.JSON(http.StatusOK, user) +} +``` + +`handler_test.go`: + +```go +package handler + +import ( + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/labstack/echo/v5" + "github.com/labstack/echo/v5/echotest" + "github.com/stretchr/testify/assert" +) + +var ( + mockDB = map[string]*User{ + "jon@labstack.com": {Name: "Jon Snow", Email: "jon@labstack.com"}, + } + userJSON = `{"name":"Jon Snow","email":"jon@labstack.com"}` +) + +func TestCreateUser(t *testing.T) { + // Setup + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON)) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + h := &handler{mockDB} + + // Assertions + if assert.NoError(t, h.createUser(c)) { + assert.Equal(t, http.StatusCreated, rec.Code) + assert.Equal(t, userJSON+"\n", rec.Body.String()) + } +} +``` + +### Usando os helpers echotest + +`echotest.ContextConfig` constrói um contexto (e recorder) a partir de uma descrição +declarativa do request: + +```go +// Same test as above, using echotest. +func TestCreateUserWithEchoTest(t *testing.T) { + c, rec := echotest.ContextConfig{ + Headers: map[string][]string{ + echo.HeaderContentType: {echo.MIMEApplicationJSON}, + }, + JSONBody: []byte(userJSON), + }.ToContextRecorder(t) + + h := &handler{mockDB} + + // Assertions + if assert.NoError(t, h.createUser(c)) { + assert.Equal(t, http.StatusCreated, rec.Code) + assert.Equal(t, userJSON+"\n", rec.Body.String()) + } +} + +// Even shorter, using ServeWithHandler. +func TestCreateUserWithServeHandler(t *testing.T) { + h := &handler{mockDB} + + rec := echotest.ContextConfig{ + Headers: map[string][]string{ + echo.HeaderContentType: {echo.MIMEApplicationJSON}, + }, + JSONBody: []byte(userJSON), + }.ServeWithHandler(t, h.createUser) + + assert.Equal(t, http.StatusCreated, rec.Code) + assert.Equal(t, userJSON+"\n", rec.Body.String()) +} + +func TestGetUser(t *testing.T) { + c, rec := echotest.ContextConfig{ + PathValues: echo.PathValues{ + {Name: "email", Value: "jon@labstack.com"}, + }, + Headers: map[string][]string{ + echo.HeaderContentType: {echo.MIMEApplicationJSON}, + }, + }.ToContextRecorder(t) + + h := &handler{mockDB} + + // Assertions + if assert.NoError(t, h.getUser(c)) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, userJSON+"\n", rec.Body.String()) + } +} +``` + +### Usando um payload de formulário + +```go +// import "net/url" +f := make(url.Values) +f.Set("name", "Jon Snow") +f.Set("email", "jon@labstack.com") +req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode())) +req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm) +``` + +Um payload de formulário multipart com `echotest`: + +```go +func TestContext_MultipartForm(t *testing.T) { + testConf := echotest.ContextConfig{ + MultipartForm: &echotest.MultipartForm{ + Fields: map[string]string{ + "key": "value", + }, + Files: []echotest.MultipartFormFile{ + { + Fieldname: "file", + Filename: "test.json", + Content: echotest.LoadBytes(t, "testdata/test.json"), + }, + }, + }, + } + c := testConf.ToContext(t) + + assert.Equal(t, "value", c.FormValue("key")) + assert.Equal(t, http.MethodPost, c.Request().Method) + assert.Equal(t, true, strings.HasPrefix(c.Request().Header.Get(echo.HeaderContentType), "multipart/form-data; boundary=")) + + fv, err := c.FormFile("file") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "test.json", fv.Filename) +} +``` + +### Definindo parâmetros de caminho + +```go +c.SetPathValues(echo.PathValues{ + {Name: "id", Value: "1"}, + {Name: "email", Value: "jon@labstack.com"}, +}) +``` + +### Definindo parâmetros de query + +```go +// import "net/url" +q := make(url.Values) +q.Set("email", "jon@labstack.com") +req := httptest.NewRequest(http.MethodGet, "/?"+q.Encode(), nil) +``` + +## Testando middleware + +```go +func TestMiddleware(t *testing.T) { + handler := func(c *echo.Context) error { + return c.JSON(http.StatusTeapot, fmt.Sprintf("email: %s", c.Param("email"))) + } + middleware := func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + c.Set("user_id", int64(1234)) + return next(c) + } + } + + c, rec := echotest.ContextConfig{ + PathValues: echo.PathValues{{Name: "email", Value: "jon@labstack.com"}}, + }.ToContextRecorder(t) + + if err := middleware(handler)(c); err != nil { + t.Fatal(err) + } + + // Check that the middleware set the value. + userID, err := echo.ContextGet[int64](c, "user_id") + assert.NoError(t, err) + assert.Equal(t, int64(1234), userID) + + // Check that the handler returned the correct response. + assert.Equal(t, http.StatusTeapot, rec.Code) +} +``` + +:::tip +Para mais exemplos, veja os [casos de teste de middleware](https://github.com/labstack/echo/tree/master/middleware) +no código-fonte do Echo. +::: From 053763dc7a381869ed3eee05ddc8e82350f7209b Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Tue, 16 Jun 2026 11:12:39 -0700 Subject: [PATCH 3/4] docs(i18n): middleware (pt-br) --- .../docs/pt-br/middleware/basic-auth.md | 74 +++++ .../docs/pt-br/middleware/body-dump.md | 72 +++++ .../docs/pt-br/middleware/body-limit.md | 48 +++ .../docs/pt-br/middleware/casbin-auth.md | 203 +++++++++++++ .../docs/pt-br/middleware/context-timeout.md | 47 +++ .../src/content/docs/pt-br/middleware/cors.md | 118 ++++++++ .../src/content/docs/pt-br/middleware/csrf.md | 177 +++++++++++ .../docs/pt-br/middleware/decompress.md | 59 ++++ .../src/content/docs/pt-br/middleware/gzip.md | 69 +++++ site/src/content/docs/pt-br/middleware/jwt.md | 126 ++++++++ .../content/docs/pt-br/middleware/key-auth.md | 91 ++++++ .../content/docs/pt-br/middleware/logger.md | 236 +++++++++++++++ .../docs/pt-br/middleware/method-override.md | 52 ++++ .../docs/pt-br/middleware/open-telemetry.md | 81 +++++ .../docs/pt-br/middleware/prometheus.md | 284 ++++++++++++++++++ .../content/docs/pt-br/middleware/proxy.md | 134 +++++++++ .../docs/pt-br/middleware/rate-limiter.md | 109 +++++++ .../content/docs/pt-br/middleware/recover.md | 61 ++++ .../content/docs/pt-br/middleware/redirect.md | 101 +++++++ .../docs/pt-br/middleware/request-id.md | 89 ++++++ .../content/docs/pt-br/middleware/rewrite.md | 87 ++++++ .../content/docs/pt-br/middleware/secure.md | 113 +++++++ .../content/docs/pt-br/middleware/session.md | 189 ++++++++++++ .../content/docs/pt-br/middleware/static.md | 142 +++++++++ .../docs/pt-br/middleware/trailing-slash.md | 65 ++++ 25 files changed, 2827 insertions(+) create mode 100644 site/src/content/docs/pt-br/middleware/basic-auth.md create mode 100644 site/src/content/docs/pt-br/middleware/body-dump.md create mode 100644 site/src/content/docs/pt-br/middleware/body-limit.md create mode 100644 site/src/content/docs/pt-br/middleware/casbin-auth.md create mode 100644 site/src/content/docs/pt-br/middleware/context-timeout.md create mode 100644 site/src/content/docs/pt-br/middleware/cors.md create mode 100644 site/src/content/docs/pt-br/middleware/csrf.md create mode 100644 site/src/content/docs/pt-br/middleware/decompress.md create mode 100644 site/src/content/docs/pt-br/middleware/gzip.md create mode 100644 site/src/content/docs/pt-br/middleware/jwt.md create mode 100644 site/src/content/docs/pt-br/middleware/key-auth.md create mode 100644 site/src/content/docs/pt-br/middleware/logger.md create mode 100644 site/src/content/docs/pt-br/middleware/method-override.md create mode 100644 site/src/content/docs/pt-br/middleware/open-telemetry.md create mode 100644 site/src/content/docs/pt-br/middleware/prometheus.md create mode 100644 site/src/content/docs/pt-br/middleware/proxy.md create mode 100644 site/src/content/docs/pt-br/middleware/rate-limiter.md create mode 100644 site/src/content/docs/pt-br/middleware/recover.md create mode 100644 site/src/content/docs/pt-br/middleware/redirect.md create mode 100644 site/src/content/docs/pt-br/middleware/request-id.md create mode 100644 site/src/content/docs/pt-br/middleware/rewrite.md create mode 100644 site/src/content/docs/pt-br/middleware/secure.md create mode 100644 site/src/content/docs/pt-br/middleware/session.md create mode 100644 site/src/content/docs/pt-br/middleware/static.md create mode 100644 site/src/content/docs/pt-br/middleware/trailing-slash.md diff --git a/site/src/content/docs/pt-br/middleware/basic-auth.md b/site/src/content/docs/pt-br/middleware/basic-auth.md new file mode 100644 index 00000000..83d8a7a4 --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/basic-auth.md @@ -0,0 +1,74 @@ +--- +title: Basic Auth +description: Middleware de autenticação HTTP Basic que valida credenciais de usuário e senha. +sidebar: + order: 1 +--- + +O middleware Basic Auth fornece autenticação básica HTTP. + +- Para credenciais válidas, ele chama o próximo handler. +- Para credenciais ausentes ou inválidas, ele envia uma response `401 Unauthorized`. + +## Uso + +```go +e.Use(middleware.BasicAuth(func(c *echo.Context, username, password string) (bool, error) { + // Use a constant time comparison to prevent timing attacks. + if subtle.ConstantTimeCompare([]byte(username), []byte("joe")) == 1 && + subtle.ConstantTimeCompare([]byte(password), []byte("secret")) == 1 { + return true, nil + } + return false, nil +})) +``` + +## Configuração customizada + +```go +e.Use(middleware.BasicAuthWithConfig(middleware.BasicAuthConfig{})) +``` + +## Configuração + +```go +type BasicAuthConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Validator validates the credentials. If the request contains multiple basic + // auth headers, it is called once for each header until the first valid result. + // Required. + Validator BasicAuthValidator + + // Realm is the realm attribute of the WWW-Authenticate header. + // Default value "Restricted". + Realm string + + // AllowedCheckLimit sets how many headers are allowed to be checked. This is + // useful in environments such as corporate test setups with application proxies + // restricting access with their own auth scheme. + // Default value 1. + AllowedCheckLimit uint +} +``` + +O `Validator` tem a assinatura: + +```go +type BasicAuthValidator func(c *echo.Context, user string, password string) (bool, error) +``` + +### Configuração padrão + +```go +// Effective defaults applied when fields are left unset. +BasicAuthConfig{ + Skipper: DefaultSkipper, + Realm: "Restricted", +} +``` + +:::caution[Segurança] +Sempre compare credenciais com `subtle.ConstantTimeCompare` para evitar timing attacks. +::: diff --git a/site/src/content/docs/pt-br/middleware/body-dump.md b/site/src/content/docs/pt-br/middleware/body-dump.md new file mode 100644 index 00000000..58eb3a5e --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/body-dump.md @@ -0,0 +1,72 @@ +--- +title: Body Dump +description: Capture payloads de request e response e passe-os para um handler para logging ou debugging. +sidebar: + order: 2 +--- + +O middleware Body Dump captura os payloads de request e response e os passa para um +handler registrado. Em geral, ele é usado para debugging ou logging. + +:::caution +Evite Body Dump para payloads grandes, como uploads ou downloads de arquivos. Se precisar usá-lo +nessas rotas, adicione uma exceção na função skipper. +::: + +## Uso + +```go +e := echo.New() +e.Use(middleware.BodyDump(func(c *echo.Context, reqBody, resBody []byte, err error) { + // Handle the request and response bodies. +})) +``` + +## Configuração customizada + +```go +e := echo.New() +e.Use(middleware.BodyDumpWithConfig(middleware.BodyDumpConfig{})) +``` + +## Configuração + +```go +type BodyDumpConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Handler receives the request and response payloads and the handler error, if any. + // Required. + Handler BodyDumpHandler + + // MaxRequestBytes limits how much of the request body to dump. If the request body + // exceeds this limit, only the first MaxRequestBytes are dumped and the handler + // receives truncated data. + // Default: 5 * MB (5,242,880 bytes). Set to -1 to disable limits (not recommended). + MaxRequestBytes int64 + + // MaxResponseBytes limits how much of the response body to dump. If the response body + // exceeds this limit, only the first MaxResponseBytes are dumped and the handler + // receives truncated data. + // Default: 5 * MB (5,242,880 bytes). Set to -1 to disable limits (not recommended). + MaxResponseBytes int64 +} +``` + +O `Handler` tem a assinatura: + +```go +type BodyDumpHandler func(c *echo.Context, reqBody []byte, resBody []byte, err error) +``` + +### Configuração padrão + +```go +// Effective defaults applied when fields are left unset (Handler is required). +BodyDumpConfig{ + Skipper: DefaultSkipper, + MaxRequestBytes: 5 * MB, + MaxResponseBytes: 5 * MB, +} +``` diff --git a/site/src/content/docs/pt-br/middleware/body-limit.md b/site/src/content/docs/pt-br/middleware/body-limit.md new file mode 100644 index 00000000..e78c533a --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/body-limit.md @@ -0,0 +1,48 @@ +--- +title: Body Limit +description: Rejeite requests cujo body exceda um tamanho máximo configurado. +sidebar: + order: 3 +--- + +O middleware Body Limit define o tamanho máximo permitido para o body de um request. Se o tamanho +exceder o limite configurado, ele envia uma response `413 Request Entity Too Large`. + +O limite é aplicado tanto ao header de request `Content-Length` quanto ao conteúdo real +lido, o que o torna resistente a headers falsificados. O limite é especificado +em bytes. + +## Uso + +```go +e := echo.New() +e.Use(middleware.BodyLimit(2_097_152)) // 2 MB +``` + +## Configuração customizada + +```go +e := echo.New() +e.Use(middleware.BodyLimitWithConfig(middleware.BodyLimitConfig{})) +``` + +## Configuração + +```go +type BodyLimitConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // LimitBytes is the maximum allowed size in bytes for a request body. + LimitBytes int64 +} +``` + +### Configuração padrão + +```go +// Effective defaults applied when fields are left unset (Limit is required). +BodyLimitConfig{ + Skipper: DefaultSkipper, +} +``` diff --git a/site/src/content/docs/pt-br/middleware/casbin-auth.md b/site/src/content/docs/pt-br/middleware/casbin-auth.md new file mode 100644 index 00000000..4d7ae7a0 --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/casbin-auth.md @@ -0,0 +1,203 @@ +--- +title: Casbin Auth +description: Autorize requests com a biblioteca de controle de acesso Casbin usando um pequeno middleware customizado. +sidebar: + order: 4 +--- + +[Casbin](https://github.com/casbin/casbin) é uma biblioteca de controle de acesso open-source +poderosa e eficiente para Go. Ela oferece suporte à aplicação de autorização em muitos modelos: + +- ACL (Access Control List) +- ACL com superusuário +- ACL sem usuários — útil para sistemas sem autenticação ou logins de usuário +- ACL sem recursos — mire um tipo de recurso (por exemplo `write-article`, `read-log`) em vez de um individual +- RBAC (Role-Based Access Control) +- RBAC com roles de recurso — usuários e recursos podem ter roles +- RBAC com domínios/tenants — usuários podem ter conjuntos de roles diferentes por domínio/tenant +- ABAC (Attribute-Based Access Control) +- RESTful +- Deny-override — regras de allow e deny têm suporte; deny sobrescreve allow + +Veja a [visão geral da API](https://casbin.org/docs/api-overview) e a +[documentação do Casbin](https://casbin.org/docs/) para detalhes. + +## Dependências + +```bash +go get github.com/casbin/casbin/v3 +``` + +```go +import ( + "github.com/casbin/casbin/v3" +) +``` + +## Implementação + +Echo não inclui um middleware Casbin; a integração é um pequeno wrapper em torno do +enforcer do Casbin: + +```go +// NewCasbinMiddleware returns middleware for Casbin (https://casbin.org/). +func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Context) (string, error)) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + username, err := userGetter(c) + if err != nil { + return echo.ErrUnauthorized.Wrap(err) + } + if pass, err := enforcer.Enforce(username, c.Request().URL.Path, c.Request().Method); err != nil { + return echo.ErrInternalServerError.Wrap(err) + } else if !pass { + return echo.NewHTTPError(http.StatusForbidden, "access denied") + } + return next(c) + } + } +} +``` + +## Exemplo + +Crie um arquivo de modelo Casbin `auth_model.conf`: + +```ini +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act + +[role_definition] +g = _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*") +``` + +Crie um arquivo de política Casbin `auth_policy.csv`: + +```csv +p, 1234567890, /dataset1/*, GET +p, alice, /dataset1/*, GET +p, alice, /dataset1/resource1, POST +p, bob, /dataset2/resource1, * +p, bob, /dataset2/resource2, GET +p, bob, /dataset2/folder1/*, POST +p, dataset1_admin, /dataset1/*, * +g, cathy, dataset1_admin +``` + +Autenticação e autorização são preocupações separadas. Autentique o usuário com +outro middleware (como JWT ou Basic Auth) e então forneça um `userGetter` para que Casbin +possa autorizar o request. + +### Com JWT + +```go +e.Use(echojwt.JWT([]byte("secret"))) // JWT middleware does authentication +jwtUser := func(c *echo.Context) (string, error) { // JWT user getter for Casbin authorization + token, err := echo.ContextGet[*jwt.Token](c, "user") + if err != nil { + return "", err + } + return token.Claims.GetSubject() +} +e.Use(NewCasbinMiddleware(ce, jwtUser)) // Casbin does authorization +``` + +Teste com: + +```bash +curl -v "http://localhost:8080/dataset1/any" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" +``` + +### Com Basic Auth + +```go +// BasicAuth middleware does authentication +e.Use(middleware.BasicAuth(func(c *echo.Context, user, password string) (bool, error) { + return subtle.ConstantTimeCompare([]byte(user), []byte("alice")) == 1 && + subtle.ConstantTimeCompare([]byte(password), []byte("password")) == 1, nil +})) +basicAuthUser := func(c *echo.Context) (string, error) { // Basic auth user getter for Casbin authorization + username, _, _ := c.Request().BasicAuth() // password is verified by the BasicAuth middleware above + return username, nil +} +e.Use(NewCasbinMiddleware(ce, basicAuthUser)) // Casbin does authorization +``` + +Teste com: + +```bash +# should pass +curl -v -u "alice:password" http://localhost:8080/dataset1/any +# should fail +curl -v -u "alice:password" http://localhost:8080/dataset2/resource2 +``` + +### Exemplo completo de Casbin + JWT + +```go +package main + +import ( + "log/slog" + "net/http" + + "github.com/casbin/casbin/v3" + "github.com/golang-jwt/jwt/v5" + echojwt "github.com/labstack/echo-jwt/v5" + "github.com/labstack/echo/v5" +) + +// NewCasbinMiddleware returns middleware for Casbin (https://casbin.org/). +func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Context) (string, error)) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + username, err := userGetter(c) + if err != nil { + return echo.ErrUnauthorized.Wrap(err) + } + if pass, err := enforcer.Enforce(username, c.Request().URL.Path, c.Request().Method); err != nil { + return echo.ErrInternalServerError.Wrap(err) + } else if !pass { + return echo.NewHTTPError(http.StatusForbidden, "access denied") + } + return next(c) + } + } +} + +func main() { + e := echo.New() + + ce, err := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv") + if err != nil { + slog.Error("failed to initialize Casbin enforcer", "error", err) + } + + e.Use(echojwt.JWT([]byte("secret"))) // JWT middleware does authentication + jwtUser := func(c *echo.Context) (string, error) { // JWT user getter for Casbin authorization + token, err := echo.ContextGet[*jwt.Token](c, "user") + if err != nil { + return "", err + } + return token.Claims.GetSubject() + } + e.Use(NewCasbinMiddleware(ce, jwtUser)) // Casbin does authorization + + e.GET("/*", func(c *echo.Context) error { + return c.String(http.StatusOK, "Hello, World!") + }) + + if err := e.Start(":8080"); err != nil { + e.Logger.Error("failed to start server", "error", err) + } +} +``` diff --git a/site/src/content/docs/pt-br/middleware/context-timeout.md b/site/src/content/docs/pt-br/middleware/context-timeout.md new file mode 100644 index 00000000..67c87c78 --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/context-timeout.md @@ -0,0 +1,47 @@ +--- +title: Context Timeout +description: Aplique timeout ao contexto do request para que operações context-aware retornem mais cedo. +sidebar: + order: 5 +--- + +O middleware Context Timeout aplica um timeout ao contexto do request dentro de um período +predefinido, para que métodos context-aware possam retornar mais cedo quando o prazo for excedido. + +## Uso + +```go +e.Use(middleware.ContextTimeout(60 * time.Second)) +``` + +## Configuração customizada + +```go +e.Use(middleware.ContextTimeoutWithConfig(middleware.ContextTimeoutConfig{ + Timeout: 60 * time.Second, +})) +``` + +## Configuração + +```go +type ContextTimeoutConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // ErrorHandler is a function invoked when an error arises during middleware execution. + ErrorHandler func(c *echo.Context, err error) error + + // Timeout configures the timeout for the middleware. + Timeout time.Duration +} +``` + +### Configuração padrão + +```go +// Effective defaults applied when fields are left unset (Timeout is required). +ContextTimeoutConfig{ + Skipper: DefaultSkipper, +} +``` diff --git a/site/src/content/docs/pt-br/middleware/cors.md b/site/src/content/docs/pt-br/middleware/cors.md new file mode 100644 index 00000000..4ecf8ae5 --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/cors.md @@ -0,0 +1,118 @@ +--- +title: CORS +description: Middleware Cross-Origin Resource Sharing para controle seguro de acesso entre domínios. +sidebar: + order: 6 +--- + +O middleware CORS implementa a especificação [CORS](https://fetch.spec.whatwg.org/#http-cors-protocol). CORS fornece +controles de acesso entre domínios para servidores web, permitindo transferências de dados seguras entre domínios. + +## Uso + +```go +e.Use(middleware.CORS("https://example.com", "https://subdomain.example.com")) +``` + +## Configuração customizada + +```go +e := echo.New() +e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ + AllowOrigins: []string{"https://labstack.com", "https://labstack.net"}, + AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept}, +})) +``` + +## Configuração + +```go +type CORSConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // AllowOrigins determines the value of the Access-Control-Allow-Origin response + // header, defining the list of origins that may access the resource. + // + // An origin consists of: scheme + "://" + host + optional ":" + port. + // A wildcard may be used, but it must be set explicitly as []string{"*"}. + // Example: `https://example.com`, `http://example.com:8080`, `*`. + // + // Security: use extreme caution when handling the origin and carefully validate any + // logic. Attackers may register hostile domain names. See + // https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html + // + // Mandatory. + AllowOrigins []string + + // UnsafeAllowOriginFunc is an optional custom function to validate the origin. It + // takes the origin and returns the allowed origin, whether it is allowed, and an + // error (returned immediately by the handler). If set, AllowOrigins is ignored. + // + // Security: use extreme caution when handling the origin. Attackers may register + // hostile (sub)domain names. + // + // Sub-domain check example: + // UnsafeAllowOriginFunc: func(c *echo.Context, origin string) (string, bool, error) { + // if strings.HasSuffix(origin, ".example.com") { + // return origin, true, nil + // } + // return "", false, nil + // } + // + // Optional. + UnsafeAllowOriginFunc func(c *echo.Context, origin string) (allowedOrigin string, allowed bool, err error) + + // AllowMethods determines the value of the Access-Control-Allow-Methods response + // header, used in response to a preflight request. + // + // Optional. Defaults to GET, HEAD, PUT, PATCH, POST, DELETE. If left empty, the + // middleware fills the preflight Access-Control-Allow-Methods header from the + // `Allow` header that the router set into the context. + AllowMethods []string + + // AllowHeaders determines the value of the Access-Control-Allow-Headers response + // header, indicating which HTTP headers can be used in the actual request. + // + // Optional. Defaults to an empty list. + AllowHeaders []string + + // AllowCredentials determines the value of the Access-Control-Allow-Credentials + // response header, indicating whether the response can be exposed when the + // credentials mode is true. + // + // Optional. Default value false, in which case the header is not set. + // + // Security: avoid using AllowCredentials = true together with AllowOrigins = *. + AllowCredentials bool + + // ExposeHeaders determines the value of Access-Control-Expose-Headers, the list of + // headers clients are allowed to access. + // + // Optional. Default value []string{}, in which case the header is not set. + ExposeHeaders []string + + // MaxAge determines the value of the Access-Control-Max-Age response header, how long + // (in seconds) the results of a preflight request can be cached. The header is set + // only if MaxAge != 0; a negative value sends "0", instructing browsers not to cache. + // + // Optional. Default value 0 — the header is not sent. + MaxAge int +} +``` + +### Configuração padrão + +```go +// Effective defaults applied when fields are left unset. +CORSConfig{ + Skipper: DefaultSkipper, + AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete}, +} +``` + +:::caution[Segurança] +Nunca combine `AllowCredentials = true` com um wildcard `AllowOrigins`. Quando precisar +de validação dinâmica de origem, use `UnsafeAllowOriginFunc` e valide cuidadosamente — +atacantes podem registrar nomes de (sub)domínio hostis. +::: diff --git a/site/src/content/docs/pt-br/middleware/csrf.md b/site/src/content/docs/pt-br/middleware/csrf.md new file mode 100644 index 00000000..1ca56e32 --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/csrf.md @@ -0,0 +1,177 @@ +--- +title: CSRF +description: Proteção Cross-Site Request Forgery usando metadados Sec-Fetch-Site e validação de token. +sidebar: + order: 7 +--- + +Cross-Site Request Forgery (CSRF, às vezes pronunciado "sea-surf", ou XSRF) é um tipo de +exploit malicioso em que comandos não autorizados são transmitidos a partir de um usuário em que um site +confia. + +## Uso + +```go +e.Use(middleware.CSRF()) +``` + +## Como funciona + +O middleware CSRF oferece suporte ao header +[`Sec-Fetch-Site`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site) +como uma abordagem moderna de defense-in-depth para +[proteção CSRF](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#fetch-metadata-headers), +implementando a Fetch Metadata API recomendada pela OWASP junto do mecanismo tradicional +baseado em token. + +Navegadores modernos enviam automaticamente o header `Sec-Fetch-Site` com cada request, +indicando a relação entre a origem do request e o destino. O middleware +usa isso para tomar uma decisão de segurança: + +- **`same-origin`** ou **`none`** — permitido (correspondência exata de origem ou navegação direta do usuário) +- **`same-site`** — recorre à validação de token (por exemplo, subdomínio para domínio principal) +- **`cross-site`** — bloqueado por padrão com um erro `403` para métodos inseguros (POST, PUT, DELETE, PATCH) + +Para navegadores que não enviam este header (navegadores mais antigos), o middleware recorre +automaticamente à proteção CSRF tradicional baseada em token. + +Duas opções ajustam o comportamento de `Sec-Fetch-Site`: + +- `TrustedOrigins []string` — allowlist de origens específicas para requests cross-site (útil para callbacks OAuth, webhooks) +- `AllowSecFetchSiteFunc func(c *echo.Context) (bool, error)` — lógica customizada para validação same-site/cross-site + +```go +e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{ + // Allow OAuth callbacks from a trusted provider. + TrustedOrigins: []string{"https://oauth-provider.com"}, + + // Custom validation for same-site/cross-site requests. + AllowSecFetchSiteFunc: func(c *echo.Context) (bool, error) { + // Your custom authorization logic here. + return validateCustomAuth(c), nil + // return true, err // blocks the request with an error + // return true, nil // allows the request through + // return false, nil // falls back to legacy token logic + }, +})) +``` + +## Proteção baseada em token + +```go +e := echo.New() +e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{ + TokenLookup: "header:X-XSRF-TOKEN", +})) +``` + +O exemplo acima extrai o token CSRF do header de request `X-XSRF-TOKEN`. + +Lendo o token de um cookie: + +```go +middleware.CSRFWithConfig(middleware.CSRFConfig{ + TokenLookup: "cookie:_csrf", + CookiePath: "/", + CookieDomain: "example.com", + CookieSecure: true, + CookieHTTPOnly: true, + CookieSameSite: http.SameSiteStrictMode, +}) +``` + +## Acessando o token CSRF + +- **Server-side** — o token fica disponível no contexto sob `ContextKey` e pode ser passado ao cliente via template. +- **Client-side** — o token pode ser lido do cookie CSRF. + +## Configuração + +```go +type CSRFConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // TrustedOrigins permits any request with a `Sec-Fetch-Site` header whose `Origin` + // header exactly matches one of the listed values. Values should be formatted as + // the Origin header: "scheme://host[:port]". + TrustedOrigins []string + + // AllowSecFetchSiteFunc allows custom behaviour for `Sec-Fetch-Site` requests that + // are about to fail with a CSRF error, to be allowed or replaced with a custom + // error. Applies to `same-site` and `cross-site` values. + AllowSecFetchSiteFunc func(c *echo.Context) (bool, error) + + // TokenLength is the length of the generated token. + // Optional. Default value 32. + TokenLength uint8 + + // TokenLookup is a string in the form ":" or + // ":,:" used to extract the token from the request. + // Optional. Default value "header:X-CSRF-Token". + // Possible values: + // - "header:" or "header::" + // - "query:" + // - "form:" + // Multiple sources example: "header:X-CSRF-Token,query:csrf". + TokenLookup string `yaml:"token_lookup"` + + // Generator defines a function to generate the token. + // Optional. Defaults to randomString(TokenLength). + Generator func() string + + // ContextKey is the key under which the generated CSRF token is stored in the context. + // Optional. Default value "csrf". + ContextKey string + + // CookieName is the name of the CSRF cookie that stores the token. + // Optional. Default value "_csrf". + CookieName string + + // CookieDomain is the domain of the CSRF cookie. + // Optional. Default value none. + CookieDomain string + + // CookiePath is the path of the CSRF cookie. + // Optional. Default value none. + CookiePath string + + // CookieMaxAge is the max age (in seconds) of the CSRF cookie. + // Optional. Default value 86400 (24h). + CookieMaxAge int + + // CookieSecure indicates whether the CSRF cookie is secure. + // Optional. Default value false. + CookieSecure bool + + // CookieHTTPOnly indicates whether the CSRF cookie is HTTP only. + // Optional. Default value false. + CookieHTTPOnly bool + + // CookieSameSite indicates the SameSite mode of the CSRF cookie. + // Optional. Default value SameSiteDefaultMode. + CookieSameSite http.SameSite + + // ErrorHandler defines a function that returns custom errors. + ErrorHandler func(c *echo.Context, err error) error +} +``` + +### Configuração padrão + +```go +var DefaultCSRFConfig = CSRFConfig{ + Skipper: DefaultSkipper, + TokenLength: 32, + TokenLookup: "header:" + echo.HeaderXCSRFToken, + ContextKey: "csrf", + CookieName: "_csrf", + CookieMaxAge: 86400, + CookieSameSite: http.SameSiteDefaultMode, +} +``` + +## Exemplo completo + +Um exemplo completo e executável está disponível no +[echox cookbook](https://github.com/labstack/echox/blob/master/cookbook/csrf/main.go). diff --git a/site/src/content/docs/pt-br/middleware/decompress.md b/site/src/content/docs/pt-br/middleware/decompress.md new file mode 100644 index 00000000..25eeb757 --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/decompress.md @@ -0,0 +1,59 @@ +--- +title: Decompress +description: Descomprima transparentemente bodies de request codificados com gzip. +sidebar: + order: 8 +--- + +O middleware Decompress descomprime o body do request HTTP quando o header `Content-Encoding` +está definido como `gzip`. + +:::note +O body é descomprimido em memória e mantido ali durante toda a vida do request (e +até a garbage collection). +::: + +## Uso + +```go +e.Use(middleware.Decompress()) +``` + +## Configuração customizada + +```go +e := echo.New() +e.Use(middleware.DecompressWithConfig(middleware.DecompressConfig{ + Skipper: middleware.DefaultSkipper, +})) +``` + +## Configuração + +```go +type DecompressConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // GzipDecompressPool provides the sync.Pool used to create and store gzip readers. + GzipDecompressPool Decompressor + + // MaxDecompressedSize limits the maximum size of the decompressed request body in + // bytes. If the decompressed body exceeds this limit, the middleware returns an + // HTTP 413 error. This prevents zip-bomb attacks where a small compressed payload + // decompresses to a huge size. + // Default: 100 * MB (104,857,600 bytes). Set to -1 to disable limits (not recommended). + MaxDecompressedSize int64 +} +``` + +### Configuração padrão + +```go +// Effective defaults applied when fields are left unset. +DecompressConfig{ + Skipper: DefaultSkipper, + GzipDecompressPool: &DefaultGzipDecompressPool{}, + MaxDecompressedSize: 100 * MB, +} +``` diff --git a/site/src/content/docs/pt-br/middleware/gzip.md b/site/src/content/docs/pt-br/middleware/gzip.md new file mode 100644 index 00000000..abf4f872 --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/gzip.md @@ -0,0 +1,69 @@ +--- +title: Gzip +description: Comprima responses HTTP com o esquema de compressão gzip. +sidebar: + order: 9 +--- + +O middleware Gzip comprime a response HTTP usando o esquema de compressão gzip. + +## Uso + +```go +e.Use(middleware.Gzip()) +``` + +## Configuração customizada + +```go +e := echo.New() +e.Use(middleware.GzipWithConfig(middleware.GzipConfig{ + Level: 5, +})) +``` + +:::tip +Passe um skipper para desabilitar gzip em certas URLs. +::: + +```go +e := echo.New() +e.Use(middleware.GzipWithConfig(middleware.GzipConfig{ + Skipper: func(c *echo.Context) bool { + return strings.Contains(c.Path(), "metrics") // change "metrics" to your own path + }, +})) +``` + +## Configuração + +```go +type GzipConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Level is the gzip compression level. + // Optional. Default value -1. + Level int + + // MinLength is the length threshold before gzip compression is applied. + // Optional. Default value 0. + // + // Most of the time the default is fine. Compressing a short response might increase + // the transmitted data because of gzip's format overhead, and compression consumes + // CPU and time on both server and client. Depending on your use case such a + // threshold can be useful. + MinLength int +} +``` + +### Configuração padrão + +```go +// Effective defaults applied when fields are left unset. +GzipConfig{ + Skipper: DefaultSkipper, + Level: -1, + MinLength: 0, +} +``` diff --git a/site/src/content/docs/pt-br/middleware/jwt.md b/site/src/content/docs/pt-br/middleware/jwt.md new file mode 100644 index 00000000..f7d0d2af --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/jwt.md @@ -0,0 +1,126 @@ +--- +title: JWT +description: Middleware de autenticação JSON Web Token fornecido pelo módulo echo-jwt. +sidebar: + order: 10 +--- + +O middleware JWT fornece autenticação JSON Web Token (JWT). Ele vive em um módulo separado: +[github.com/labstack/echo-jwt](https://github.com/labstack/echo-jwt). + +Comportamento: + +- Para um token válido, ele define o usuário no contexto e chama o próximo handler. +- Para um token inválido, ele envia uma response `401 Unauthorized`. +- Para um header `Authorization` ausente ou inválido, ele envia uma response `400 Bad Request`. + +## Dependências + +```go +import "github.com/labstack/echo-jwt/v5" +``` + +## Uso + +```go +e.Use(echojwt.JWT([]byte("secret"))) +``` + +## Configuração customizada + +```go +e.Use(echojwt.WithConfig(echojwt.Config{ + SigningKey: []byte("secret"), +})) +``` + +## Configuração + +```go +type Config struct { + // Skipper defines a function to skip middleware. + Skipper middleware.Skipper + + // BeforeFunc defines a function which is executed just before the middleware. + BeforeFunc middleware.BeforeFunc + + // SuccessHandler defines a function executed for a valid token. If it returns an + // error, the middleware stops the handler chain and returns that error. + SuccessHandler func(c *echo.Context) error + + // ErrorHandler defines a function executed when all lookups have been done and none + // passed the Validator. It runs with the last missing (ErrExtractionValueMissing) + // or invalid key, and may be used to define a custom JWT error. + // + // Note: when the error handler swallows the error (returns nil), the middleware + // continues the handler chain. This is useful when part of your site/api is public + // and offers extra features for authorized users; the handler can set a default + // public JWT token value in the request and continue. + ErrorHandler func(c *echo.Context, err error) error + + // ContinueOnIgnoredError allows the next middleware/handler to be called when the + // ErrorHandler ignores the error (returns nil). + ContinueOnIgnoredError bool + + // ContextKey is the key under which user information from the token is stored in the context. + // Optional. Default value "user". + ContextKey string + + // SigningKey is the signing key used to validate the token. One of the three options + // to provide a token validation key. Order of precedence: user-defined KeyFunc, + // SigningKeys, then SigningKey. + // Required if neither a user-defined KeyFunc nor SigningKeys is provided. + SigningKey any + + // SigningKeys is a map of signing keys to validate tokens using the kid field. One of + // the three options to provide a token validation key. + // Required if neither a user-defined KeyFunc nor SigningKey is provided. + SigningKeys map[string]any + + // SigningMethod is the signing method used to check the token's signing algorithm. + // Not checked when a user-defined KeyFunc is provided. + // Optional. Default value HS256. + SigningMethod string + + // KeyFunc supplies the public key for token validation. It must verify the signing + // algorithm and select the proper key. Useful when tokens are issued by an external + // party. When provided, SigningKey, SigningKeys and SigningMethod are ignored. + // One of the three options to provide a token validation key, and not used if a + // custom ParseTokenFunc is set. + KeyFunc jwt.Keyfunc + + // TokenLookup is a string in the form ":" or + // ":,:" used to extract the token from the request. + // Optional. Default value "header:Authorization". + // Possible values: + // - "header:" or "header::" + // trims a static prefix from the extracted value. For JWT tokens with + // `Authorization: Bearer `, the prefix to cut is `Bearer ` (note the space). + // If the prefix is empty, the whole value is returned. + // - "query:" + // - "param:" + // - "cookie:" + // - "form:" + // Multiple sources example: "header:Authorization:Bearer ,cookie:myowncookie". + TokenLookup string + + // TokenLookupFuncs is a list of user-defined functions that extract the JWT token + // from the context. One of two options to provide a token extractor. Order of + // precedence: TokenLookupFuncs, then TokenLookup. Both may be provided. + TokenLookupFuncs []middleware.ValuesExtractor + + // ParseTokenFunc parses the token from the given auth string, returning an error when + // parsing fails or the token is invalid. + // Defaults to an implementation using github.com/golang-jwt/jwt. + ParseTokenFunc func(c *echo.Context, auth string) (any, error) + + // NewClaimsFunc returns the extendable claims defining token content. Used by the + // default ParseTokenFunc; not used if a custom ParseTokenFunc is set. + // Optional. Defaults to a function returning jwt.MapClaims. + NewClaimsFunc func(c *echo.Context) jwt.Claims +} +``` + +## Exemplo + +Veja a [receita de JWT](/pt-br/cookbook/jwt/) para um exemplo completo. diff --git a/site/src/content/docs/pt-br/middleware/key-auth.md b/site/src/content/docs/pt-br/middleware/key-auth.md new file mode 100644 index 00000000..17922eb7 --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/key-auth.md @@ -0,0 +1,91 @@ +--- +title: Key Auth +description: Middleware de autenticação por chave que valida uma API key de header, query, form ou cookie. +sidebar: + order: 11 +--- + +O middleware Key Auth fornece autenticação baseada em chave. + +- Para uma chave válida, ele chama o próximo handler. +- Para uma chave inválida, ele envia uma response `401 Unauthorized`. +- Para uma chave ausente, ele envia uma response `400 Bad Request`. + +## Uso + +```go +e.Use(middleware.KeyAuth(func(c *echo.Context, key string, source middleware.ExtractorSource) (bool, error) { + return key == "valid-key", nil +})) +``` + +## Configuração customizada + +```go +e := echo.New() +e.Use(middleware.KeyAuthWithConfig(middleware.KeyAuthConfig{ + KeyLookup: "query:api-key", + Validator: func(c *echo.Context, key string, source middleware.ExtractorSource) (bool, error) { + return key == "valid-key", nil + }, +})) +``` + +## Configuração + +```go +type KeyAuthConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // KeyLookup is a string in the form ":" or + // ":,:" used to extract the key from the request. + // Optional. Default value "header:Authorization:Bearer ". + // Possible values: + // - "header:" or "header::" + // trims a static prefix from the extracted value. For + // `Authorization: Basic `, the prefix to remove is `Basic `. + // - "query:" + // - "form:" + // - "cookie:" + // Multiple sources example: "header:Authorization,header:X-Api-Key". + KeyLookup string + + // AllowedCheckLimit sets how many KeyLookup values are allowed to be checked. This is + // useful in environments such as corporate test setups with application proxies + // restricting access with their own auth scheme. + AllowedCheckLimit uint + + // Validator validates the key. + // Required. + Validator KeyAuthValidator + + // ErrorHandler defines a function executed when all lookups have been done and none + // passed the Validator. It runs with the last missing (ErrExtractionValueMissing) or + // invalid key, and may be used to define a custom error. + // + // Note: when the error handler swallows the error (returns nil), the middleware + // continues the handler chain. This is useful when part of your site/api is public + // and offers extra features for authorized users. + ErrorHandler KeyAuthErrorHandler + + // ContinueOnIgnoredError allows the next middleware/handler to be called when the + // ErrorHandler ignores the error (returns nil). + ContinueOnIgnoredError bool +} +``` + +O `Validator` tem a assinatura: + +```go +type KeyAuthValidator func(c *echo.Context, key string, source ExtractorSource) (bool, error) +``` + +### Configuração padrão + +```go +DefaultKeyAuthConfig = KeyAuthConfig{ + Skipper: DefaultSkipper, + KeyLookup: "header:" + echo.HeaderAuthorization + ":Bearer ", +} +``` diff --git a/site/src/content/docs/pt-br/middleware/logger.md b/site/src/content/docs/pt-br/middleware/logger.md new file mode 100644 index 00000000..6d343c8b --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/logger.md @@ -0,0 +1,236 @@ +--- +title: Request Logger +description: Logging de requests totalmente customizável que se integra a bibliotecas de logging estruturado. +sidebar: + order: 12 +--- + +O middleware `RequestLogger` registra informações sobre cada request HTTP. Ele permite customizar +totalmente o que é registrado e como, tornando-o adequado para uso com bibliotecas de terceiros +(logging estruturado). + +Os valores que o logger pode extrair são controlados pelos campos booleanos e slices de +`RequestLoggerConfig`. Habilite um campo (por exemplo `LogStatus: true`) para ter seu valor +preenchido em `RequestLoggerValues`, que é passado para seu `LogValuesFunc`. + +```go +type RequestLoggerConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // BeforeNextFunc is called before the next middleware or handler in the chain. + BeforeNextFunc func(c *echo.Context) + + // LogValuesFunc is called with the values extracted by the logger from the + // request/response. + // Mandatory. + LogValuesFunc func(c *echo.Context, v RequestLoggerValues) error + + // HandleError instructs the logger to call the global error handler when the next + // middleware/handler returns an error. A side effect is that the response is then + // committed and sent, so middlewares up the chain can no longer change the status + // code or body. + HandleError bool + + // LogLatency records the duration of the rest of the handler chain (the next(c) call). + LogLatency bool + // LogProtocol extracts the request protocol (for example HTTP/1.1 or HTTP/2). + LogProtocol bool + // LogRemoteIP extracts the request remote IP. See echo.Context.RealIP() for details. + LogRemoteIP bool + // LogHost extracts the request host value (for example example.com). + LogHost bool + // LogMethod extracts the request method (for example GET). + LogMethod bool + // LogURI extracts the request URI (for example /list?lang=en&page=1). + LogURI bool + // LogURIPath extracts the request URI path part (for example /list). + LogURIPath bool + // LogRoutePath extracts the route path the request matched (for example /user/:id). + LogRoutePath bool + // LogRequestID extracts the request ID from the X-Request-ID request header, or the + // response if the request did not have a value. + LogRequestID bool + // LogReferer extracts the request referer value. + LogReferer bool + // LogUserAgent extracts the request user agent value. + LogUserAgent bool + // LogStatus extracts the response status code. If the chain returns an echo.HTTPError, + // the status code is taken from it. + LogStatus bool + // LogError extracts the error returned from the handler chain. + LogError bool + // LogContentLength extracts the Content-Length header value. Note: this can differ + // from the actual request body size as it may be spoofed. + LogContentLength bool + // LogResponseSize extracts the response content length. Note: when used with Gzip + // middleware this value may not always be correct. + LogResponseSize bool + // LogHeaders extracts the given list of request headers. A slice of values is logged + // per header since a request can contain more than one. Names are canonicalized with + // http.CanonicalHeaderKey (for example "accept-encoding" becomes "Accept-Encoding"). + LogHeaders []string + // LogQueryParams extracts the given list of query parameters from the request URI. A + // slice of values is logged per name since a request can repeat a parameter. + LogQueryParams []string + // LogFormValues extracts the given list of form values from the request body and URI. + // A slice of values is logged per name since a request can repeat a value. + LogFormValues []string +} +``` + +## Exemplos + +### fmt.Printf + +```go +skipper := func(c *echo.Context) bool { + // Skip the health check endpoint. + return c.Request().URL.Path == "/health" +} +e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ + LogStatus: true, + LogURI: true, + Skipper: skipper, + BeforeNextFunc: func(c *echo.Context) { + c.Set("customValueFromContext", 42) + }, + LogValuesFunc: func(c *echo.Context, v middleware.RequestLoggerValues) error { + value, _ := c.Get("customValueFromContext").(int) + fmt.Printf("REQUEST: uri: %v, status: %v, custom-value: %v\n", v.URI, v.Status, value) + return nil + }, +})) +``` + +Saída de exemplo: + +```text +REQUEST: uri: /hello, status: 200, custom-value: 42 +``` + +### slog ([log/slog](https://pkg.go.dev/log/slog)) + +```go +logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) +e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ + LogStatus: true, + LogURI: true, + LogError: true, + HandleError: true, // forwards the error to the global error handler so it can pick the status code + LogValuesFunc: func(c *echo.Context, v middleware.RequestLoggerValues) error { + if v.Error == nil { + logger.LogAttrs(context.Background(), slog.LevelInfo, "REQUEST", + slog.String("uri", v.URI), + slog.Int("status", v.Status), + ) + } else { + logger.LogAttrs(context.Background(), slog.LevelError, "REQUEST_ERROR", + slog.String("uri", v.URI), + slog.Int("status", v.Status), + slog.String("err", v.Error.Error()), + ) + } + return nil + }, +})) +``` + +Saída de exemplo: + +```text +{"time":"2024-12-30T20:55:46.2399999+08:00","level":"INFO","msg":"REQUEST","uri":"/hello","status":200} +``` + +### Zerolog ([rs/zerolog](https://github.com/rs/zerolog)) + +```go +logger := zerolog.New(os.Stdout) +e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ + LogURI: true, + LogStatus: true, + LogValuesFunc: func(c *echo.Context, v middleware.RequestLoggerValues) error { + logger.Info(). + Str("URI", v.URI). + Int("status", v.Status). + Msg("request") + return nil + }, +})) +``` + +Saída de exemplo: + +```text +{"level":"info","URI":"/hello","status":200,"message":"request"} +``` + +### Zap ([uber-go/zap](https://github.com/uber-go/zap)) + +```go +logger, _ := zap.NewProduction() +e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ + LogURI: true, + LogStatus: true, + LogValuesFunc: func(c *echo.Context, v middleware.RequestLoggerValues) error { + logger.Info("request", + zap.String("URI", v.URI), + zap.Int("status", v.Status), + ) + return nil + }, +})) +``` + +Saída de exemplo: + +```text +{"level":"info","ts":1735564026.3197417,"caller":"cmd/main.go:20","msg":"request","URI":"/hello","status":200} +``` + +### Logrus ([sirupsen/logrus](https://github.com/sirupsen/logrus)) + +```go +log := logrus.New() +e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ + LogURI: true, + LogStatus: true, + LogValuesFunc: func(c *echo.Context, values middleware.RequestLoggerValues) error { + log.WithFields(logrus.Fields{ + "URI": values.URI, + "status": values.Status, + }).Info("request") + return nil + }, +})) +``` + +Saída de exemplo: + +```text +time="2024-12-30T21:08:49+08:00" level=info msg=request URI=/hello status=200 +``` + +## Solução de problemas + +### panic: missing LogValuesFunc callback function for request logger middleware + +Esse panic ocorre quando o callback obrigatório `LogValuesFunc` não é definido. Defina uma +função compatível com a assinatura `LogValuesFunc` e atribua-a na configuração: + +```go +func logValues(c *echo.Context, v middleware.RequestLoggerValues) error { + fmt.Printf("Request Method: %s, URI: %s\n", v.Method, v.URI) + return nil +} + +e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ + LogValuesFunc: logValues, +})) +``` + +### Parâmetros nos logs estão vazios + +Se valores como `v.URI` e `v.Status` estiverem vazios dentro de `LogValuesFunc`, verifique se as +flags de extração correspondentes (`LogStatus`, `LogURI` e assim por diante) estão definidas como `true` na +configuração. Cada valor só é preenchido quando sua flag está habilitada. diff --git a/site/src/content/docs/pt-br/middleware/method-override.md b/site/src/content/docs/pt-br/middleware/method-override.md new file mode 100644 index 00000000..04273750 --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/method-override.md @@ -0,0 +1,52 @@ +--- +title: Method Override +description: Substitua o método HTTP de um request POST via header, form ou valor de query. +sidebar: + order: 13 +--- + +O middleware Method Override lê o método sobrescrito do request e o usa +no lugar do método original. + +:::note +Por motivos de segurança, apenas o método `POST` pode ser sobrescrito. +::: + +## Uso + +```go +e.Pre(middleware.MethodOverride()) +``` + +## Configuração customizada + +```go +e := echo.New() +e.Pre(middleware.MethodOverrideWithConfig(middleware.MethodOverrideConfig{ + Getter: middleware.MethodFromForm("_method"), +})) +``` + +O método pode vir de `MethodFromHeader`, `MethodFromForm` ou `MethodFromQuery`. + +## Configuração + +```go +type MethodOverrideConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Getter is a function that gets the overridden method from the request. + // Optional. Default value MethodFromHeader(echo.HeaderXHTTPMethodOverride). + Getter MethodOverrideGetter +} +``` + +### Configuração padrão + +```go +DefaultMethodOverrideConfig = MethodOverrideConfig{ + Skipper: DefaultSkipper, + Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride), +} +``` diff --git a/site/src/content/docs/pt-br/middleware/open-telemetry.md b/site/src/content/docs/pt-br/middleware/open-telemetry.md new file mode 100644 index 00000000..792f11ad --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/open-telemetry.md @@ -0,0 +1,81 @@ +--- +title: OpenTelemetry +description: Instrumentação OpenTelemetry para requests HTTP no Echo. +sidebar: + order: 14 +--- + +[Echo OpenTelemetry](https://github.com/labstack/echo-opentelemetry) é um middleware que +fornece instrumentação OpenTelemetry para requests HTTP. + +OpenTelemetry é um conjunto de ferramentas open-source que fornecem instrumentação para aplicações +cloud-native. + +- [OpenTelemetry Exporters](https://opentelemetry.io/docs/languages/go/exporters/) +- [OpenTelemetry HTTP spec](https://opentelemetry.io/docs/specs/semconv/http/) +- [HTTP metrics spec](https://opentelemetry.io/docs/specs/semconv/http/http-metrics/) + +## Uso + +Adicione a dependência do middleware OpenTelemetry com Go modules: + +```bash +go get github.com/labstack/echo-opentelemetry +``` + +Importe o middleware e a API de trace do OpenTelemetry: + +```go +import ( + echootel "github.com/labstack/echo-opentelemetry" + "go.opentelemetry.io/otel/trace" +) +``` + +Registre-o com configuração completa: + +```go +e.Use(echootel.NewMiddlewareWithConfig(echootel.Config{ + ServerName: "my-server", + TracerProvider: tp, + + //Skipper: nil, + //OnNextError: nil, + //OnExtractionError: nil, + //MeterProvider: nil, + //Propagators: nil, + //SpanStartOptions: nil, + //SpanStartAttributes: nil, + //SpanEndAttributes: nil, + //MetricAttributes: nil, + //Metrics: nil, +})) +``` + +Para opções de configuração, veja a struct [`Config`](https://github.com/labstack/echo-opentelemetry/blob/main/otel.go#L28). + +Adicione o middleware de forma simplificada fornecendo apenas o nome do servidor: + +```go +e.Use(echootel.NewMiddleware("app.example.com")) +``` + +Adicione o middleware com opções de configuração: + +```go +e.Use(echootel.NewMiddlewareWithConfig(echootel.Config{ + TracerProvider: tp, +})) +``` + +Recupere o tracer a partir do contexto do Echo: + +```go +tracer, err := echo.ContextGet[trace.Tracer](c, echootel.TracerKey) +``` + +## Exemplo + +O [exemplo](https://github.com/labstack/echo-opentelemetry/blob/main/example/main.go) exporta +métricas e spans para stdout, mas você pode usar qualquer exporter (OTLP etc.). Veja a +documentação de [OpenTelemetry exporters](https://opentelemetry.io/docs/languages/go/exporters). diff --git a/site/src/content/docs/pt-br/middleware/prometheus.md b/site/src/content/docs/pt-br/middleware/prometheus.md new file mode 100644 index 00000000..e8693575 --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/prometheus.md @@ -0,0 +1,284 @@ +--- +title: Prometheus +description: Gere métricas Prometheus para requests HTTP no Echo. +sidebar: + order: 15 +--- + +O middleware [Echo Prometheus](https://github.com/labstack/echo-prometheus) gera métricas Prometheus +para requests HTTP. + +## Uso + +Adicione o módulo necessário: + +```bash +go get github.com/labstack/echo-prometheus +``` + +Adicione o middleware Prometheus e uma rota para servir as métricas coletadas: + +```go +e := echo.New() +e.Use(echoprometheus.NewMiddleware("myapp")) // adds middleware to gather metrics +e.GET("/metrics", echoprometheus.NewHandler()) // adds route to serve gathered metrics +``` + +## Exemplos + +Sirva métricas a partir do mesmo servidor que as coleta: + +```go +package main + +import ( + "net/http" + + echoprometheus "github.com/labstack/echo-prometheus" + "github.com/labstack/echo/v5" +) + +func main() { + e := echo.New() + + e.Use(echoprometheus.NewMiddleware("myapp")) // adds middleware to gather metrics + e.GET("/metrics", echoprometheus.NewHandler()) // adds route to serve gathered metrics + + e.GET("/hello", func(c *echo.Context) error { + return c.String(http.StatusOK, "hello") + }) + + if err := e.Start(":8080"); err != nil { + e.Logger.Error("failed to start server", "error", err) + } +} +``` + +Sirva métricas em uma porta separada: + +```go +func main() { + e := echo.New() // this Echo instance will serve routes on port 8080 + e.Use(echoprometheus.NewMiddleware("myapp")) // adds middleware to gather metrics + + go func() { + metrics := echo.New() // this Echo will run on separate port 8081 + metrics.GET("/metrics", echoprometheus.NewHandler()) // adds route to serve gathered metrics + if err := metrics.Start(":8081"); err != nil { + e.Logger.Error("failed to start metrics server", "error", err) + } + }() + + e.GET("/hello", func(c *echo.Context) error { + return c.String(http.StatusOK, "hello") + }) + + if err := e.Start(":8080"); err != nil { + e.Logger.Error("failed to start server", "error", err) + } +} +``` + +Saída de exemplo (para o primeiro exemplo): + +```bash +curl http://localhost:8080/metrics + +# HELP echo_request_duration_seconds The HTTP request latencies in seconds. +# TYPE echo_request_duration_seconds summary +echo_request_duration_seconds_sum 0.41086482 +echo_request_duration_seconds_count 1 +# HELP echo_request_size_bytes The HTTP request sizes in bytes. +# TYPE echo_request_size_bytes summary +echo_request_size_bytes_sum 56 +echo_request_size_bytes_count 1 +# HELP echo_requests_total How many HTTP requests processed, partitioned by status code and HTTP method. +# TYPE echo_requests_total counter +echo_requests_total{code="200",host="localhost:8080",method="GET",url="/"} 1 +# HELP echo_response_size_bytes The HTTP response sizes in bytes. +# TYPE echo_response_size_bytes summary +echo_response_size_bytes_sum 61 +echo_response_size_bytes_count 1 +... +``` + +## Configuração customizada + +### Servindo métricas Prometheus customizadas + +Use métricas customizadas com o registry padrão do Prometheus: + +```go +package main + +import ( + "log" + + echoprometheus "github.com/labstack/echo-prometheus" + "github.com/labstack/echo/v5" + "github.com/prometheus/client_golang/prometheus" +) + +func main() { + e := echo.New() // this Echo instance will serve routes on port 8080 + + customCounter := prometheus.NewCounter( // create a new counter metric + prometheus.CounterOpts{ + Name: "custom_requests_total", + Help: "How many HTTP requests processed, partitioned by status code and HTTP method.", + }, + ) + if err := prometheus.Register(customCounter); err != nil { // register the counter with the default registry + log.Fatal(err) + } + + e.Use(echoprometheus.NewMiddlewareWithConfig(echoprometheus.MiddlewareConfig{ + AfterNext: func(c *echo.Context, err error) { + customCounter.Inc() // increment the counter after every request + }, + })) + e.GET("/metrics", echoprometheus.NewHandler()) // register a route to serve gathered metrics + + if err := e.Start(":8080"); err != nil { + e.Logger.Error("failed to start server", "error", err) + } +} +``` + +Ou crie seu próprio registry e registre métricas customizadas nele: + +```go +package main + +import ( + "log" + + echoprometheus "github.com/labstack/echo-prometheus" + "github.com/labstack/echo/v5" + "github.com/prometheus/client_golang/prometheus" +) + +func main() { + e := echo.New() // this Echo instance will serve routes on port 8080 + + customRegistry := prometheus.NewRegistry() // create a custom registry for your custom metrics + customCounter := prometheus.NewCounter( // create a new counter metric + prometheus.CounterOpts{ + Name: "custom_requests_total", + Help: "How many HTTP requests processed, partitioned by status code and HTTP method.", + }, + ) + if err := customRegistry.Register(customCounter); err != nil { // register the counter with the custom registry + log.Fatal(err) + } + + e.Use(echoprometheus.NewMiddlewareWithConfig(echoprometheus.MiddlewareConfig{ + AfterNext: func(c *echo.Context, err error) { + customCounter.Inc() // increment the counter after every request + }, + Registerer: customRegistry, // use the custom registry instead of the default Prometheus registry + })) + e.GET("/metrics", echoprometheus.NewHandlerWithConfig(echoprometheus.HandlerConfig{Gatherer: customRegistry})) // serve metrics from the custom registry + + if err := e.Start(":8080"); err != nil { + e.Logger.Error("failed to start server", "error", err) + } +} +``` + +### Ignorando URLs + +Um skipper pode ser passado para evitar gerar métricas para certas URLs: + +```go +package main + +import ( + "net/http" + "strings" + + echoprometheus "github.com/labstack/echo-prometheus" + "github.com/labstack/echo/v5" +) + +func main() { + e := echo.New() // this Echo instance will serve routes on port 8080 + + mwConfig := echoprometheus.MiddlewareConfig{ + Skipper: func(c *echo.Context) bool { + return strings.HasPrefix(c.Path(), "/testurl") + }, // does not gather metrics on routes starting with `/testurl` + } + e.Use(echoprometheus.NewMiddlewareWithConfig(mwConfig)) // adds middleware to gather metrics + + e.GET("/metrics", echoprometheus.NewHandler()) // adds route to serve gathered metrics + + e.GET("/", func(c *echo.Context) error { + return c.String(http.StatusOK, "Hello, World!") + }) + + if err := e.Start(":8080"); err != nil { + e.Logger.Error("failed to start server", "error", err) + } +} +``` + +## Cenários complexos + +Modifique as definições padrão de métricas `echoprometheus`: + +```go +package main + +import ( + "net/http" + + echoprometheus "github.com/labstack/echo-prometheus" + "github.com/labstack/echo/v5" + "github.com/prometheus/client_golang/prometheus" +) + +func main() { + e := echo.New() // this Echo instance will serve routes on port 8080 + + e.Use(echoprometheus.NewMiddlewareWithConfig(echoprometheus.MiddlewareConfig{ + // Labels of default metrics can be modified or added with the `LabelFuncs` function. + LabelFuncs: map[string]echoprometheus.LabelValueFunc{ + "scheme": func(c *echo.Context, err error) string { // additional custom label + return c.Scheme() + }, + "host": func(c *echo.Context, err error) string { // overrides the default 'host' label value + return "y_" + c.Request().Host + }, + }, + // The `echoprometheus` middleware registers the following metrics by default: + // - Histogram: request_duration_seconds + // - Histogram: response_size_bytes + // - Histogram: request_size_bytes + // - Counter: requests_total + // which can be modified with the `HistogramOptsFunc` and `CounterOptsFunc` functions. + HistogramOptsFunc: func(opts prometheus.HistogramOpts) prometheus.HistogramOpts { + if opts.Name == "request_duration_seconds" { + opts.Buckets = []float64{1000.0, 10_000.0, 100_000.0, 1_000_000.0} // 1KB, 10KB, 100KB, 1MB + } + return opts + }, + CounterOptsFunc: func(opts prometheus.CounterOpts) prometheus.CounterOpts { + if opts.Name == "requests_total" { + opts.ConstLabels = prometheus.Labels{"my_const": "123"} + } + return opts + }, + })) // adds middleware to gather metrics + + e.GET("/metrics", echoprometheus.NewHandler()) // adds route to serve gathered metrics + + e.GET("/hello", func(c *echo.Context) error { + return c.String(http.StatusOK, "hello") + }) + + if err := e.Start(":8080"); err != nil { + e.Logger.Error("failed to start server", "error", err) + } +} +``` diff --git a/site/src/content/docs/pt-br/middleware/proxy.md b/site/src/content/docs/pt-br/middleware/proxy.md new file mode 100644 index 00000000..9acc87b2 --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/proxy.md @@ -0,0 +1,134 @@ +--- +title: Proxy +description: Middleware de reverse proxy HTTP e WebSocket com load balancing. +sidebar: + order: 16 +--- + +Proxy fornece um middleware de reverse proxy HTTP/WebSocket. Ele encaminha um request para um +servidor upstream usando uma técnica de load balancing configurada. + +## Uso + +```go +url1, err := url.Parse("http://localhost:8081") +if err != nil { + e.Logger.Error("failed to parse url", "error", err) +} +url2, err := url.Parse("http://localhost:8082") +if err != nil { + e.Logger.Error("failed to parse url", "error", err) +} +e.Use(middleware.Proxy(middleware.NewRoundRobinBalancer([]*middleware.ProxyTarget{ + { + URL: url1, + }, + { + URL: url2, + }, +}))) +``` + +## Configuração customizada + +```go +e := echo.New() +e.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{})) +``` + +## Configuração + +```go +type ProxyConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Balancer defines a load balancing technique. + // Required. + Balancer ProxyBalancer + + // RetryCount defines the number of times a failed proxied request should be retried + // using the next available ProxyTarget. Defaults to 0, meaning requests are never retried. + RetryCount int + + // RetryFilter defines a function used to determine if a failed request to a + // ProxyTarget should be retried. The RetryFilter will only be called when the number + // of previous retries is less than RetryCount. If the function returns true, the + // request will be retried. The provided error indicates the reason for the request + // failure. When the ProxyTarget is unavailable, the error will be an instance of + // echo.HTTPError with a code of http.StatusBadGateway. In all other cases, the error + // will indicate an internal error in the Proxy middleware. When a RetryFilter is not + // specified, all requests that fail with http.StatusBadGateway will be retried. A custom + // RetryFilter can be provided to only retry specific requests. Note that RetryFilter is + // only called when the request to the target fails, or an internal error in the Proxy + // middleware has occurred. Successful requests that return a non-200 response code cannot + // be retried. + RetryFilter func(c *echo.Context, e error) bool + + // ErrorHandler defines a function which can be used to return custom errors from + // the Proxy middleware. ErrorHandler is only invoked when there has been + // either an internal error in the Proxy middleware or the ProxyTarget is + // unavailable. Due to the way requests are proxied, ErrorHandler is not invoked + // when a ProxyTarget returns a non-200 response. In these cases, the response + // is already written so errors cannot be modified. ErrorHandler is only + // invoked after all retry attempts have been exhausted. + ErrorHandler func(c *echo.Context, err error) error + + // Rewrite defines URL path rewrite rules. The values captured in asterisk can be + // retrieved by index e.g. $1, $2 and so on. + // Examples: + // "/old": "/new", + // "/api/*": "/$1", + // "/js/*": "/public/javascripts/$1", + // "/users/*/orders/*": "/user/$1/order/$2", + Rewrite map[string]string + + // RegexRewrite defines rewrite rules using regexp.Regexp with captures. + // Every capture group in the values can be retrieved by index e.g. $1, $2 and so on. + // Example: + // "^/old/[0.9]+/": "/new", + // "^/api/.+?/(.*)": "/v2/$1", + RegexRewrite map[*regexp.Regexp]string + + // Context key to store selected ProxyTarget into context. + // Optional. Default value "target". + ContextKey string + + // To customize the transport to remote. + // Examples: If custom TLS certificates are required. + Transport http.RoundTripper + + // ModifyResponse defines function to modify response from ProxyTarget. + ModifyResponse func(*http.Response) error +} +``` + +### Configuração padrão + +| Nome | Valor | +| ---------- | -------------- | +| Skipper | DefaultSkipper | +| ContextKey | `target` | + +### Regras baseadas em regex + +Para reescrita avançada de requests de proxy, as regras também podem ser definidas usando expressões +regulares. Grupos de captura normais podem ser definidos com `()` e referenciados por índice +(`$1`, `$2`, ...) no caminho reescrito. + +Regras `RegexRewrite` e `Rewrite` normais podem ser combinadas. + +```go +e.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{ + Balancer: rrb, + Rewrite: map[string]string{ + "^/v1/*": "/v2/$1", + }, + RegexRewrite: map[*regexp.Regexp]string{ + regexp.MustCompile("^/foo/([0-9].*)"): "/num/$1", + regexp.MustCompile("^/bar/(.+?)/(.*)"): "/baz/$2/$1", + }, +})) +``` + +Veja a receita de [reverse proxy](/pt-br/cookbook/reverse-proxy/) para um exemplo completo. diff --git a/site/src/content/docs/pt-br/middleware/rate-limiter.md b/site/src/content/docs/pt-br/middleware/rate-limiter.md new file mode 100644 index 00000000..d4038d33 --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/rate-limiter.md @@ -0,0 +1,109 @@ +--- +title: Rate Limiter +description: Limite o número de requests de um IP ou identificador específico dentro de um período. +sidebar: + order: 17 +--- + +`RateLimiter` fornece um middleware de rate limiter que limita o número de requests enviados ao +servidor a partir de um IP ou identificador específico dentro de um período. + +Por padrão, um store em memória acompanha os requests. A implementação em memória padrão +é focada em correção e pode não ser a melhor opção para um alto número de requests +concorrentes ou um grande número de identificadores distintos (>16k). + +## Uso + +Para adicionar um limite de taxa à sua aplicação, adicione o middleware `RateLimiter`. O exemplo abaixo +limita a aplicação a 20 requests/segundo usando o store em memória padrão: + +```go +e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20.0))) +``` + +:::note +Se a taxa fornecida for um número float, `Burst` é tratado como o valor arredondado para baixo da taxa. +::: + +## Configuração customizada + +```go +config := middleware.RateLimiterConfig{ + Skipper: middleware.DefaultSkipper, + Store: middleware.NewRateLimiterMemoryStoreWithConfig( + middleware.RateLimiterMemoryStoreConfig{Rate: 10, Burst: 30, ExpiresIn: 3 * time.Minute}, + ), + IdentifierExtractor: func(c *echo.Context) (string, error) { + id := c.RealIP() + return id, nil + }, + ErrorHandler: func(c *echo.Context, err error) error { + return c.JSON(http.StatusForbidden, nil) + }, + DenyHandler: func(c *echo.Context, identifier string, err error) error { + return c.JSON(http.StatusTooManyRequests, nil) + }, +} + +e.Use(middleware.RateLimiterWithConfig(config)) +``` + +### Erros + +```go +var ( + // ErrRateLimitExceeded denotes an error raised when the rate limit is exceeded. + ErrRateLimitExceeded = echo.NewHTTPError(http.StatusTooManyRequests, "rate limit exceeded") + // ErrExtractorError denotes an error raised when the extractor function is unsuccessful. + ErrExtractorError = echo.NewHTTPError(http.StatusForbidden, "error while extracting identifier") +) +``` + +:::tip +Para implementar seu próprio store, satisfaça a interface `RateLimiterStore` e passe-o para +`RateLimiterConfig`. +::: + +## Configuração + +```go +type RateLimiterConfig struct { + Skipper Skipper + BeforeFunc BeforeFunc + // IdentifierExtractor uses echo.Context to extract the identifier for a visitor. + IdentifierExtractor Extractor + // Store defines a store for the rate limiter. + Store RateLimiterStore + // ErrorHandler provides a handler to be called when IdentifierExtractor returns a non-nil error. + ErrorHandler func(c *echo.Context, err error) error + // DenyHandler provides a handler to be called when RateLimiter denies access. + DenyHandler func(c *echo.Context, identifier string, err error) error +} +``` + +### Configuração padrão + +```go +// DefaultRateLimiterConfig defines default values for RateLimiterConfig. +var DefaultRateLimiterConfig = RateLimiterConfig{ + Skipper: DefaultSkipper, + IdentifierExtractor: func(c *echo.Context) (string, error) { + id := c.RealIP() + return id, nil + }, + ErrorHandler: func(c *echo.Context, err error) error { + return &echo.HTTPError{ + Code: ErrExtractorError.Code, + Message: ErrExtractorError.Message, + Internal: err, + } + }, + DenyHandler: func(c *echo.Context, identifier string, err error) error { + return &echo.HTTPError{ + Code: ErrRateLimitExceeded.Code, + Message: ErrRateLimitExceeded.Message, + Internal: err, + } + }, +} +``` diff --git a/site/src/content/docs/pt-br/middleware/recover.md b/site/src/content/docs/pt-br/middleware/recover.md new file mode 100644 index 00000000..ae03f386 --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/recover.md @@ -0,0 +1,61 @@ +--- +title: Recover +description: Recupere-se de panics em qualquer ponto da cadeia e delegue ao handler de erro centralizado. +sidebar: + order: 18 +--- + +O middleware Recover recupera panics em qualquer ponto da cadeia, imprime a stack trace e +passa o controle para o +[HTTPErrorHandler](/pt-br/guide/customization/#http-error-handler) centralizado. + +## Uso + +```go +e.Use(middleware.Recover()) +``` + +## Configuração customizada + +```go +e := echo.New() +e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{ + StackSize: 1 << 10, // 1 KB +})) +``` + +O exemplo acima usa um `StackSize` de 1 KB e valores padrão para `DisableStackAll` e +`DisablePrintStack`. + +## Configuração + +```go +type RecoverConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Size of the stack to be printed. + // Optional. Default value 4KB. + StackSize int + + // DisableStackAll disables formatting stack traces of all other goroutines + // into the buffer after the trace for the current goroutine. + // Optional. Default value false. + DisableStackAll bool + + // DisablePrintStack disables printing the stack trace. + // Optional. Default value false. + DisablePrintStack bool +} +``` + +### Configuração padrão + +```go +var DefaultRecoverConfig = RecoverConfig{ + Skipper: DefaultSkipper, + StackSize: 4 << 10, // 4 KB + DisableStackAll: false, + DisablePrintStack: false, +} +``` diff --git a/site/src/content/docs/pt-br/middleware/redirect.md b/site/src/content/docs/pt-br/middleware/redirect.md new file mode 100644 index 00000000..62e746e3 --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/redirect.md @@ -0,0 +1,101 @@ +--- +title: Redirect +description: Redirecione requests entre HTTP/HTTPS e variantes www/non-www. +sidebar: + order: 19 +--- + +## HTTPS Redirect + +O middleware HTTPS redirect redireciona requests HTTP para HTTPS. Por exemplo, +`http://labstack.com` é redirecionado para `https://labstack.com`. + +### Uso + +```go +e := echo.New() +e.Pre(middleware.HTTPSRedirect()) +``` + +## HTTPS WWW Redirect + +HTTPS WWW redirect redireciona requests HTTP para www HTTPS. Por exemplo, +`http://labstack.com` é redirecionado para `https://www.labstack.com`. + +### Uso + +```go +e := echo.New() +e.Pre(middleware.HTTPSWWWRedirect()) +``` + +## HTTPS NonWWW Redirect + +HTTPS NonWWW redirect redireciona requests HTTP para non-www HTTPS. Por exemplo, +`http://www.labstack.com` é redirecionado para `https://labstack.com`. + +### Uso + +```go +e := echo.New() +e.Pre(middleware.HTTPSNonWWWRedirect()) +``` + +## WWW Redirect + +WWW redirect redireciona requests non-www para www. Por exemplo, `http://labstack.com` é +redirecionado para `http://www.labstack.com`. + +### Uso + +```go +e := echo.New() +e.Pre(middleware.WWWRedirect()) +``` + +## NonWWW Redirect + +NonWWW redirect redireciona requests www para non-www. Por exemplo, `http://www.labstack.com` é +redirecionado para `http://labstack.com`. + +### Uso + +```go +e := echo.New() +e.Pre(middleware.NonWWWRedirect()) +``` + +## Configuração customizada + +```go +e := echo.New() +e.Use(middleware.HTTPSRedirectWithConfig(middleware.RedirectConfig{ + Code: http.StatusTemporaryRedirect, +})) +``` + +O exemplo acima redireciona requests HTTP para HTTPS com o código de status +`307 - StatusTemporaryRedirect`. + +## Configuração + +```go +type RedirectConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Status code to be used when redirecting the request. + // Optional. Default value http.StatusMovedPermanently. + Code int +} +``` + +### Configuração padrão + +```go +// Effective defaults applied when fields are left unset. +RedirectConfig{ + Skipper: DefaultSkipper, + Code: http.StatusMovedPermanently, +} +``` diff --git a/site/src/content/docs/pt-br/middleware/request-id.md b/site/src/content/docs/pt-br/middleware/request-id.md new file mode 100644 index 00000000..112ca44a --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/request-id.md @@ -0,0 +1,89 @@ +--- +title: Request ID +description: Gere um ID único para cada request. +sidebar: + order: 20 +--- + +O middleware Request ID gera um ID único para um request. + +## Uso + +```go +e.Use(middleware.RequestID()) +``` + +Exemplo: + +```go +func main() { + e := echo.New() + + e.Use(middleware.RequestID()) + + e.GET("/", func(c *echo.Context) error { + return c.String(http.StatusOK, c.Response().Header().Get(echo.HeaderXRequestID)) + }) + + if err := e.Start(":8080"); err != nil { + e.Logger.Error("failed to start server", "error", err) + } +} +``` + +## Configuração customizada + +```go +e.Use(middleware.RequestIDWithConfig(middleware.RequestIDConfig{ + Generator: func() string { + return customGenerator() + }, +})) +``` + +## Configuração + +```go +type RequestIDConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Generator defines a function to generate an ID. + // Optional. Default value random.String(32). + Generator func() string + + // RequestIDHandler defines a function which is executed for a request id. + RequestIDHandler func(c *echo.Context, requestID string) + + // TargetHeader defines what header to look for to populate the id. + // Optional. Default value is `X-Request-Id`. + TargetHeader string +} +``` + +### Configuração padrão + +```go +// Effective defaults applied when fields are left unset. +RequestIDConfig{ + Skipper: DefaultSkipper, + Generator: generator, // random 32-character string + TargetHeader: echo.HeaderXRequestID, +} +``` + +## Definir ID + +Você pode definir o ID a partir do requester com o header `X-Request-ID`. + +### Request + +```sh +curl -H "X-Request-ID: 3" --compressed -v "http://localhost:1323/?my=param" +``` + +### Log + +```js +{"time":"2017-11-13T20:26:28.6438003+01:00","id":"3","remote_ip":"::1","host":"localhost:1323","method":"GET","uri":"/?my=param","my":"param","status":200, "latency":0,"latency_human":"0s","bytes_in":0,"bytes_out":13} +``` diff --git a/site/src/content/docs/pt-br/middleware/rewrite.md b/site/src/content/docs/pt-br/middleware/rewrite.md new file mode 100644 index 00000000..77888bb1 --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/rewrite.md @@ -0,0 +1,87 @@ +--- +title: Rewrite +description: Reescreva o caminho da URL com base em regras configuradas. +sidebar: + order: 21 +--- + +O middleware Rewrite reescreve o caminho da URL com base nas regras fornecidas. Ele é útil para +compatibilidade retroativa ou para criar links mais limpos e descritivos. + +## Uso + +```go +e.Pre(middleware.Rewrite(map[string]string{ + "/old": "/new", + "/api/*": "/$1", + "/js/*": "/public/javascripts/$1", + "/users/*/orders/*": "/user/$1/order/$2", +})) +``` + +Os valores capturados em asteriscos podem ser recuperados por índice, por exemplo `$1`, `$2` e assim por diante. Cada +asterisco é non-greedy (traduzido para um grupo de captura `(.*?)`); ao usar vários asteriscos, +um `*` final corresponde ao restante do caminho. + +:::caution +O middleware Rewrite deve ser registrado via `Echo#Pre()` para rodar antes do router. +::: + +## Configuração customizada + +```go +e := echo.New() +e.Pre(middleware.RewriteWithConfig(middleware.RewriteConfig{})) +``` + +## Configuração + +```go +type RewriteConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Rules defines the URL path rewrite rules. The values captured in asterisk can be + // retrieved by index e.g. $1, $2 and so on. + // Example: + // "/old": "/new", + // "/api/*": "/$1", + // "/js/*": "/public/javascripts/$1", + // "/users/*/orders/*": "/user/$1/order/$2", + // Required. + Rules map[string]string + + // RegexRules defines the URL path rewrite rules using regexp.Regexp with captures. + // Every capture group in the values can be retrieved by index e.g. $1, $2 and so on. + // Example: + // "^/old/[0.9]+/": "/new", + // "^/api/.+?/(.*)": "/v2/$1", + RegexRules map[*regexp.Regexp]string +} +``` + +Configuração padrão: + +| Nome | Valor | +| ------- | -------------- | +| Skipper | DefaultSkipper | + +### Regras baseadas em regex + +Para reescrita avançada de caminhos, as regras também podem ser definidas usando expressões regulares. Grupos +de captura normais podem ser definidos com `()` e referenciados por índice (`$1`, `$2`, ...) no +caminho reescrito. + +`RegexRules` e `Rules` normais podem ser combinados. + +```go +e.Pre(middleware.RewriteWithConfig(middleware.RewriteConfig{ + Rules: map[string]string{ + "^/v1/*": "/v2/$1", + }, + RegexRules: map[*regexp.Regexp]string{ + regexp.MustCompile("^/foo/([0-9].*)"): "/num/$1", + regexp.MustCompile("^/bar/(.+?)/(.*)"): "/baz/$2/$1", + }, +})) +``` diff --git a/site/src/content/docs/pt-br/middleware/secure.md b/site/src/content/docs/pt-br/middleware/secure.md new file mode 100644 index 00000000..65c1790b --- /dev/null +++ b/site/src/content/docs/pt-br/middleware/secure.md @@ -0,0 +1,113 @@ +--- +title: Secure +description: Proteja contra XSS, content sniffing, clickjacking e outros ataques de injeção. +sidebar: + order: 22 +--- + +O middleware Secure fornece proteção contra cross-site scripting (XSS), content type +sniffing, clickjacking, conexões inseguras e outros ataques de injeção de código. + +## Uso + +```go +e.Use(middleware.Secure()) +``` + +## Configuração customizada + +```go +e := echo.New() +e.Use(middleware.SecureWithConfig(middleware.SecureConfig{ + XSSProtection: "", + ContentTypeNosniff: "", + XFrameOptions: "", + HSTSMaxAge: 3600, + ContentSecurityPolicy: "default-src 'self'", +})) +``` + +:::note +Passar `XSSProtection`, `ContentTypeNosniff`, `XFrameOptions` ou +`ContentSecurityPolicy` vazios desabilita essa proteção. +::: + +## Configuração + +```go +type SecureConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // XSSProtection provides protection against cross-site scripting attack (XSS) + // by setting the `X-XSS-Protection` header. + // Optional. Default value "1; mode=block". + XSSProtection string + + // ContentTypeNosniff provides protection against overriding Content-Type + // header by setting the `X-Content-Type-Options` header. + // Optional. Default value "nosniff". + ContentTypeNosniff string + + // XFrameOptions can be used to indicate whether or not a browser should + // be allowed to render a page in a ,