diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 8268945a..53be1e80 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -31,6 +31,10 @@ jobs: steps: - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 + with: + go-version: '1.27' + - uses: actions/setup-node@v6 with: node-version: 24 @@ -66,4 +70,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 \ No newline at end of file + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..867a5589 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,35 @@ +name: Documentation checks + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 + with: + go-version: '1.27' + cache-dependency-path: | + go.sum + reference/go.sum + - uses: actions/setup-node@v6 + with: + node-version: '24' + cache: npm + cache-dependency-path: site/package-lock.json + - name: Test cookbook + run: go test ./... + - name: Install site dependencies + run: npm ci --ignore-scripts + working-directory: site + - name: Check source reference and build site + run: npm run build + working-directory: site + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index ae35effc..5f595aa0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ vendor .superpowers/ .serena/ .playwright-mcp/ +.cache/ +site/src/generated/ +site/dist/ +site/dist-next/ +site/node_modules/ diff --git a/README.md b/README.md index 1d16acd4..cb6d5e0a 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,20 @@ the runnable cookbook recipes the docs reference. | ----------- | --------------------------------------------------------------------------------------------------- | | `site/` | The docs site — [Astro](https://astro.build) + [Starlight](https://starlight.astro.build). Content lives in `site/src/content/docs/`. | | `cookbook/` | Standalone, runnable Go example apps referenced from the docs. | +| `reference/` | Source-owned middleware examples and the config-field extractor used by the site build. | | `docs/` | Internal design specs. | ## Documentation site -Requires [Node.js](https://nodejs.org) (LTS). +Requires [Node.js](https://nodejs.org) (LTS), Go 1.27, and Git. The build +publishes the stable docs at `/` and a next preview at `/next/`, each with its +own search index and source revision. Those revisions are pinned in +`site/echo-source.json` and `site/next-source.json`. The build compiles the +`reference/` examples against both, extracts middleware fields and function +signatures, and checks stable against `site/reference-baseline.json` and next +against `site/next-reference-baseline.json`. JWT, +Prometheus, and OpenTelemetry are extracted from their own pinned modules in +`site/external-sources.json` and checked against `site/external-baseline.json`. ```bash cd site @@ -24,6 +33,33 @@ npm run build # production build to site/dist npm run preview # preview the production build ``` +The first build fetches pinned source into `.cache/`. To test a proposed next +Echo checkout, set `ECHO_SOURCE_DIR` to its absolute path when running +`npm run build`. The build reports changed API facts and stops. Review the +affected pages and behavior, then prepare and accept the **next** baseline: + +```bash +DOCS_CHANNEL=next ECHO_SOURCE_DIR=/absolute/path/to/echo npm run source:prepare +DOCS_CHANNEL=next ECHO_SOURCE_DIR=/absolute/path/to/echo npm run source:accept +``` + +Review the baseline diff, update `site/next-source.json` to the proposed +revision, and rerun the full build. The release baseline remains independent. +The generated files in `site/src/generated/` are never edited or committed. +`npm run site:check` checks routes, local links and fragments, image text, +search assets, and locale coverage. `npm run performance:check` catches large +HTML or first-load asset growth on representative stable and next pages. + +Generated field descriptions come from the pinned Go source comments and remain +in English on localized pages; the surrounding task guidance is authored per +locale. `npm run translations:status` identifies changed sections in the Spanish, +Japanese, Portuguese, and Chinese Request Logger, Static, and middleware task +pages. Translate and review the affected section, then record that page with +`npm run translations:accept -- es logger` (replace locale and page) and review +the baseline diff. This tracks edits to both English and localized text; it +does not judge translation quality. The API tables are generated from source, +so translators focus on explanations, task guidance, examples, and safety notes. + Content is Markdown/MDX under `site/src/content/docs/` (`guide/`, `middleware/`, `cookbook/`). To add a page, drop a file in the right folder — the sidebar is generated from each page's `sidebar.order` frontmatter. Every page needs a diff --git a/docs/api-reference-tool-trial.md b/docs/api-reference-tool-trial.md new file mode 100644 index 00000000..c1edaf0e --- /dev/null +++ b/docs/api-reference-tool-trial.md @@ -0,0 +1,24 @@ +# API reference tool trial + +The first trial used `gomarkdoc` v1.1.0 against the pinned Echo checkout's +`middleware` package, which includes CORS, Request Logger, and Static, and +against the external `github.com/labstack/echo-jwt/v5` package from the current +`echox` module. The package output was 2,422 lines for core middleware and 389 +lines for JWT. `gomarkdoc --check --output ...` successfully detected that the +unmodified generated core file matched its source. The output included +`EnablePathUnescaping` and excluded the removed `RequestLoggerConfig.LogError`. + +`--embed` supports marked regions within authored Markdown. A check against an +unmarked, fully generated file failed because embed mode would append a second +generated block. This confirms that an existing page must add embed markers +before using that mode. Templates can change generated Markdown, but this +package-wide output is too broad for individual middleware pages and is not a +machine-readable field manifest. Those two requirements motivate the small +`config-fields` extractor in `reference/`. It extracts only fields, types, +deprecation markers, and source positions; it does not infer defaults or +rewrite authored pages. + +The site currently renders field tables from that extractor. A targeted +`gomarkdoc` template and marked embedding on a real page remain to be evaluated +for signatures and other reference sections. This trial does not justify +generating behavior explanations. diff --git a/reference/cmd/config-fields/main.go b/reference/cmd/config-fields/main.go new file mode 100644 index 00000000..da2d0995 --- /dev/null +++ b/reference/cmd/config-fields/main.go @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: MIT + +// config-fields emits source-backed middleware API facts for the website. +// It does not claim to determine runtime defaults or behavior. +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "os" + "path/filepath" + "sort" + "strings" +) + +type field struct { + Name string `json:"name"` + Type string `json:"type"` + Doc string `json:"doc,omitempty"` + Deprecated bool `json:"deprecated,omitempty"` + Line int `json:"line"` +} + +type config struct { + Name string `json:"name"` + File string `json:"file"` + Line int `json:"line"` + Fields []field `json:"fields"` +} + +type function struct { + Name string `json:"name"` + Signature string `json:"signature"` + File string `json:"file"` + Line int `json:"line"` +} + +type manifest struct { + Module string `json:"module"` + Revision string `json:"revision,omitempty"` + Configs []config `json:"configs"` + Functions []function `json:"functions"` +} + +func main() { + root := flag.String("root", "", "Echo repository root") + revision := flag.String("revision", "", "exact Echo commit or tag used for this build") + module := flag.String("module", "github.com/labstack/echo/v5", "module path containing the source") + directory := flag.String("directory", "middleware", "package directory relative to the module root") + packageName := flag.String("package", "middleware", "Go package name") + flag.Parse() + if *root == "" { + fmt.Fprintln(os.Stderr, "-root is required") + os.Exit(2) + } + + result, err := extractPackage(*root, *revision, *module, *directory, *packageName) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + encoder := json.NewEncoder(os.Stdout) + encoder.SetIndent("", " ") + if err := encoder.Encode(result); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func extract(root, revision string) (manifest, error) { + return extractPackage(root, revision, "github.com/labstack/echo/v5", "middleware", "middleware") +} + +func extractPackage(root, revision, module, directory, packageName string) (manifest, error) { + root, err := filepath.Abs(root) + if err != nil { + return manifest{}, err + } + fs := token.NewFileSet() + packageDir := filepath.Join(root, directory) + packages, err := parser.ParseDir(fs, packageDir, func(info os.FileInfo) bool { + return strings.HasSuffix(info.Name(), ".go") && !strings.HasSuffix(info.Name(), "_test.go") + }, parser.ParseComments) + if err != nil { + return manifest{}, err + } + pkg, ok := packages[packageName] + if !ok { + return manifest{}, fmt.Errorf("package %s not found in %s", packageName, packageDir) + } + + result := manifest{Module: module, Revision: revision, Configs: []config{}, Functions: []function{}} + for filename, source := range pkg.Files { + for _, declaration := range source.Decls { + if fn, ok := declaration.(*ast.FuncDecl); ok && fn.Recv == nil && ast.IsExported(fn.Name.Name) { + var rendered bytes.Buffer + if err := format.Node(&rendered, fs, fn.Type); err != nil { + return manifest{}, err + } + result.Functions = append(result.Functions, function{ + Name: fn.Name.Name, + Signature: strings.Replace(rendered.String(), "func(", "func "+fn.Name.Name+"(", 1), + File: filepath.ToSlash(strings.TrimPrefix(filename, root+string(filepath.Separator))), + Line: fs.Position(fn.Pos()).Line, + }) + continue + } + group, ok := declaration.(*ast.GenDecl) + if !ok || group.Tok != token.TYPE { + continue + } + for _, item := range group.Specs { + typeSpec := item.(*ast.TypeSpec) + structure, ok := typeSpec.Type.(*ast.StructType) + if !ok || !ast.IsExported(typeSpec.Name.Name) || !strings.HasSuffix(typeSpec.Name.Name, "Config") { + continue + } + entry := config{ + Name: typeSpec.Name.Name, + File: filepath.ToSlash(strings.TrimPrefix(filename, root+string(filepath.Separator))), + Line: fs.Position(typeSpec.Pos()).Line, + Fields: []field{}, + } + for _, sourceField := range structure.Fields.List { + var rendered bytes.Buffer + if err := format.Node(&rendered, fs, sourceField.Type); err != nil { + return manifest{}, err + } + fieldDoc := comment(sourceField.Doc) + for _, name := range sourceField.Names { + if !ast.IsExported(name.Name) { + continue + } + entry.Fields = append(entry.Fields, field{ + Name: name.Name, + Type: rendered.String(), + Doc: fieldDoc, + Deprecated: strings.Contains(fieldDoc, "Deprecated:"), + Line: fs.Position(name.Pos()).Line, + }) + } + } + result.Configs = append(result.Configs, entry) + } + } + } + sort.Slice(result.Configs, func(i, j int) bool { return result.Configs[i].Name < result.Configs[j].Name }) + sort.Slice(result.Functions, func(i, j int) bool { return result.Functions[i].Name < result.Functions[j].Name }) + return result, nil +} + +func comment(group *ast.CommentGroup) string { + if group == nil { + return "" + } + return strings.TrimSpace(group.Text()) +} diff --git a/reference/cmd/config-fields/main_test.go b/reference/cmd/config-fields/main_test.go new file mode 100644 index 00000000..ea1b70f1 --- /dev/null +++ b/reference/cmd/config-fields/main_test.go @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT + +package main + +import ( + "os" + "path/filepath" + "testing" +) + +func TestExtractOnlyExportedConfigFields(t *testing.T) { + root := t.TempDir() + dir := filepath.Join(root, "middleware") + if err := os.Mkdir(dir, 0755); err != nil { + t.Fatal(err) + } + source := `package middleware +type ExampleConfig struct { + // Deprecated: use New instead. + Old bool + New string + private int +} +type hiddenConfig struct { Visible bool } +type OtherType struct { Visible bool } +func ExampleWithConfig(c ExampleConfig) bool { return c.New != "" } +func privateFunction() {} +` + if err := os.WriteFile(filepath.Join(dir, "example.go"), []byte(source), 0644); err != nil { + t.Fatal(err) + } + + got, err := extract(root, "abc123") + if err != nil { + t.Fatal(err) + } + if got.Revision != "abc123" || len(got.Configs) != 1 { + t.Fatalf("unexpected manifest: %#v", got) + } + fields := got.Configs[0].Fields + if got.Configs[0].File != "middleware/example.go" || len(fields) != 2 { + t.Fatalf("unexpected config: %#v", got.Configs[0]) + } + if fields[0].Name != "Old" || fields[0].Type != "bool" || fields[0].Doc != "Deprecated: use New instead." || !fields[0].Deprecated || fields[1].Name != "New" { + t.Fatalf("unexpected fields: %#v", fields) + } + if len(got.Functions) != 1 || got.Functions[0].Name != "ExampleWithConfig" || got.Functions[0].Signature != "func ExampleWithConfig(c ExampleConfig) bool" || got.Functions[0].File != "middleware/example.go" { + t.Fatalf("unexpected functions: %#v", got.Functions) + } +} diff --git a/reference/go.mod b/reference/go.mod new file mode 100644 index 00000000..f390045d --- /dev/null +++ b/reference/go.mod @@ -0,0 +1,7 @@ +module github.com/labstack/echox/reference + +go 1.25.0 + +require github.com/labstack/echo/v5 v5.3.0 + +require golang.org/x/time v0.15.0 // indirect diff --git a/reference/go.sum b/reference/go.sum new file mode 100644 index 00000000..2929860c --- /dev/null +++ b/reference/go.sum @@ -0,0 +1,16 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/labstack/echo/v5 v5.3.0 h1:KT74Mprk053PQEHwSZdeCDIz1BigTZOZhavMD0c9Fjs= +github.com/labstack/echo/v5 v5.3.0/go.mod h1:Q3j2+clBRgJr0O3DDONQeXNsM7RHgSwUhcuo47unqm8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/reference/request-logger/main.go b/reference/request-logger/main.go new file mode 100644 index 00000000..edbcbe57 --- /dev/null +++ b/reference/request-logger/main.go @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT + +// This complete example is the source for the Request Logger documentation. +package main + +import ( + "log/slog" + "net/http" + "os" + + "github.com/labstack/echo/v5" + "github.com/labstack/echo/v5/middleware" +) + +func main() { + e := echo.New() + logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) + + e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ + LogURI: true, + LogStatus: true, + HandleError: true, + LogValuesFunc: func(c *echo.Context, v middleware.RequestLoggerValues) error { + if v.Error != nil { + logger.Error("request failed", "uri", v.URI, "status", v.Status, "error", v.Error) + return nil + } + logger.Info("request", "uri", v.URI, "status", v.Status) + return nil + }, + })) + + e.GET("/", func(c *echo.Context) error { + return c.String(http.StatusOK, "hello") + }) + + if err := e.Start(":1323"); err != nil { + e.Logger.Error("server stopped", "error", err) + } +} diff --git a/reference/static/main.go b/reference/static/main.go new file mode 100644 index 00000000..7c066599 --- /dev/null +++ b/reference/static/main.go @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +// This complete example is the source for the Static middleware documentation. +package main + +import ( + "github.com/labstack/echo/v5" + "github.com/labstack/echo/v5/middleware" +) + +func main() { + e := echo.New() + e.Use(middleware.StaticWithConfig(middleware.StaticConfig{ + Root: "public", + EnablePathUnescaping: false, // Keep encoded slashes encoded when route guards protect files. + })) + + if err := e.Start(":1323"); err != nil { + e.Logger.Error("server stopped", "error", err) + } +} diff --git a/reference/static/public/index.html b/reference/static/public/index.html new file mode 100644 index 00000000..5fbf861c --- /dev/null +++ b/reference/static/public/index.html @@ -0,0 +1,10 @@ + + + + + Echo Static example + + +

Hello from Echo Static middleware

+ + diff --git a/site/astro.config.mjs b/site/astro.config.mjs index 5603037f..fb8d42da 100644 --- a/site/astro.config.mjs +++ b/site/astro.config.mjs @@ -2,11 +2,15 @@ import { defineConfig } from 'astro/config'; import starlight from '@astrojs/starlight'; import { redirects } from './src/redirects.mjs'; +const next = process.env.DOCS_CHANNEL === 'next'; + // https://astro.build/config export default defineConfig({ site: 'https://echo.labstack.com', + base: next ? '/next/' : '/', + outDir: next ? './dist-next' : './dist', // Preserve every live Docusaurus /docs/* URL at cutover (generated — see ./src/redirects.mjs). - redirects, + redirects: next ? {} : redirects, integrations: [ starlight({ title: 'Echo', @@ -36,6 +40,7 @@ export default defineConfig({ // Keep Starlight's built-in Pagefind ⌘K search; Search override adds the // empty-state launchpad. "Ask AI" is the kapa.ai widget (see head). components: { + Banner: './src/components/VersionBanner.astro', Footer: './src/components/Footer.astro', Search: './src/components/Search.astro', }, @@ -51,7 +56,7 @@ export default defineConfig({ // We hide kapa's default floating launcher and open the modal from our // own "Ask AI" pill in the header (see Search.astro) so the trigger // matches the Terminal chrome instead of kapa's stock button. - { + ...(!next ? [{ tag: 'script', attrs: { async: true, @@ -70,7 +75,7 @@ export default defineConfig({ 'data-button-hide': 'true', 'data-modal-override-open-selector': '#echo-ask-ai', }, - }, + }] : []), // Dark-first: default new visitors to dark unless they've chosen otherwise. { tag: 'script', diff --git a/site/echo-source.json b/site/echo-source.json new file mode 100644 index 00000000..bb5affe7 --- /dev/null +++ b/site/echo-source.json @@ -0,0 +1,5 @@ +{ + "repository": "https://github.com/labstack/echo.git", + "revision": "caa018246b840091e71a1d61ea9ee1176ab81333", + "release": "v5.3.0" +} diff --git a/site/external-baseline.json b/site/external-baseline.json new file mode 100644 index 00000000..a24133d5 --- /dev/null +++ b/site/external-baseline.json @@ -0,0 +1,218 @@ +{ + "jwt": { + "module": "github.com/labstack/echo-jwt/v5", + "version": "v5.0.2", + "configs": { + "Config": { + "Skipper": { + "type": "middleware.Skipper", + "deprecated": false + }, + "BeforeFunc": { + "type": "middleware.BeforeFunc", + "deprecated": false + }, + "SuccessHandler": { + "type": "func(c *echo.Context) error", + "deprecated": false + }, + "ErrorHandler": { + "type": "func(c *echo.Context, err error) error", + "deprecated": false + }, + "ContinueOnIgnoredError": { + "type": "bool", + "deprecated": false + }, + "ContextKey": { + "type": "string", + "deprecated": false + }, + "SigningKey": { + "type": "interface{}", + "deprecated": false + }, + "SigningKeys": { + "type": "map[string]interface{}", + "deprecated": false + }, + "SigningMethod": { + "type": "string", + "deprecated": false + }, + "KeyFunc": { + "type": "jwt.Keyfunc", + "deprecated": false + }, + "TokenLookup": { + "type": "string", + "deprecated": false + }, + "TokenLookupFuncs": { + "type": "[]middleware.ValuesExtractor", + "deprecated": false + }, + "ParseTokenFunc": { + "type": "func(c *echo.Context, auth string) (interface{}, error)", + "deprecated": false + }, + "NewClaimsFunc": { + "type": "func(c *echo.Context) jwt.Claims", + "deprecated": false + } + } + }, + "functions": { + "JWT": "func JWT(signingKey interface{}) echo.MiddlewareFunc", + "WithConfig": "func WithConfig(config Config) echo.MiddlewareFunc" + } + }, + "prometheus": { + "module": "github.com/labstack/echo-prometheus", + "version": "v0.0.1", + "configs": { + "HandlerConfig": { + "Gatherer": { + "type": "prometheus.Gatherer", + "deprecated": false + } + }, + "MiddlewareConfig": { + "Skipper": { + "type": "middleware.Skipper", + "deprecated": false + }, + "Namespace": { + "type": "string", + "deprecated": false + }, + "Subsystem": { + "type": "string", + "deprecated": false + }, + "LabelFuncs": { + "type": "map[string]LabelValueFunc", + "deprecated": false + }, + "HistogramOptsFunc": { + "type": "func(opts prometheus.HistogramOpts) prometheus.HistogramOpts", + "deprecated": false + }, + "CounterOptsFunc": { + "type": "func(opts prometheus.CounterOpts) prometheus.CounterOpts", + "deprecated": false + }, + "Registerer": { + "type": "prometheus.Registerer", + "deprecated": false + }, + "BeforeNext": { + "type": "func(c *echo.Context)", + "deprecated": false + }, + "AfterNext": { + "type": "func(c *echo.Context, err error)", + "deprecated": false + }, + "DoNotUseRequestPathFor404": { + "type": "bool", + "deprecated": false + } + }, + "PushGatewayConfig": { + "PushGatewayURL": { + "type": "string", + "deprecated": false + }, + "PushInterval": { + "type": "time.Duration", + "deprecated": false + }, + "Gatherer": { + "type": "prometheus.Gatherer", + "deprecated": false + }, + "ErrorHandler": { + "type": "func(err error) error", + "deprecated": false + }, + "ClientTransport": { + "type": "http.RoundTripper", + "deprecated": false + } + } + }, + "functions": { + "NewHandler": "func NewHandler() echo.HandlerFunc", + "NewHandlerWithConfig": "func NewHandlerWithConfig(config HandlerConfig) echo.HandlerFunc", + "NewMiddleware": "func NewMiddleware(subsystem string) echo.MiddlewareFunc", + "NewMiddlewareWithConfig": "func NewMiddlewareWithConfig(config MiddlewareConfig) echo.MiddlewareFunc", + "RunPushGatewayGatherer": "func RunPushGatewayGatherer(ctx context.Context, config PushGatewayConfig) error", + "WriteGatheredMetrics": "func WriteGatheredMetrics(writer io.Writer, gatherer prometheus.Gatherer) error" + } + }, + "open-telemetry": { + "module": "github.com/labstack/echo-opentelemetry", + "version": "v0.0.3", + "configs": { + "Config": { + "ServerName": { + "type": "string", + "deprecated": false + }, + "Skipper": { + "type": "middleware.Skipper", + "deprecated": false + }, + "OnNextError": { + "type": "OnErrorFunc", + "deprecated": false + }, + "OnExtractionError": { + "type": "OnErrorFunc", + "deprecated": false + }, + "TracerProvider": { + "type": "oteltrace.TracerProvider", + "deprecated": false + }, + "MeterProvider": { + "type": "metric.MeterProvider", + "deprecated": false + }, + "Propagators": { + "type": "propagation.TextMapPropagator", + "deprecated": false + }, + "SpanStartOptions": { + "type": "[]oteltrace.SpanStartOption", + "deprecated": false + }, + "SpanStartAttributes": { + "type": "AttributesFunc", + "deprecated": false + }, + "SpanEndAttributes": { + "type": "AttributesFunc", + "deprecated": false + }, + "MetricAttributes": { + "type": "MetricAttributesFunc", + "deprecated": false + }, + "Metrics": { + "type": "MetricsRecorder", + "deprecated": false + } + } + }, + "functions": { + "NewMetrics": "func NewMetrics(meter metric.Meter) (*Metrics, error)", + "NewMiddleware": "func NewMiddleware(serverName string) echo.MiddlewareFunc", + "NewMiddlewareWithConfig": "func NewMiddlewareWithConfig(config Config) echo.MiddlewareFunc", + "SpanNameFormatter": "func SpanNameFormatter(v Values) string", + "SpanStatus": "func SpanStatus(code int, err error) (codes.Code, string)", + "SplitAddress": "func SplitAddress(address string) (host string, port int, err error)" + } + } +} diff --git a/site/external-sources.json b/site/external-sources.json new file mode 100644 index 00000000..735a60ce --- /dev/null +++ b/site/external-sources.json @@ -0,0 +1,20 @@ +{ + "jwt": { + "module": "github.com/labstack/echo-jwt/v5", + "version": "v5.0.2", + "repository": "https://github.com/labstack/echo-jwt", + "package": "echojwt" + }, + "prometheus": { + "module": "github.com/labstack/echo-prometheus", + "version": "v0.0.1", + "repository": "https://github.com/labstack/echo-prometheus", + "package": "echoprometheus" + }, + "open-telemetry": { + "module": "github.com/labstack/echo-opentelemetry", + "version": "v0.0.3", + "repository": "https://github.com/labstack/echo-opentelemetry", + "package": "echootel" + } +} diff --git a/site/next-reference-baseline.json b/site/next-reference-baseline.json new file mode 100644 index 00000000..3230fc2d --- /dev/null +++ b/site/next-reference-baseline.json @@ -0,0 +1,613 @@ +{ + "configs": { + "AddTrailingSlashConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "RedirectCode": { + "type": "int", + "deprecated": false + } + }, + "BasicAuthConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "Validator": { + "type": "BasicAuthValidator", + "deprecated": false + }, + "Realm": { + "type": "string", + "deprecated": false + }, + "AllowedCheckLimit": { + "type": "uint", + "deprecated": false + } + }, + "BodyDumpConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "Handler": { + "type": "BodyDumpHandler", + "deprecated": false + }, + "MaxRequestBytes": { + "type": "int64", + "deprecated": false + }, + "MaxResponseBytes": { + "type": "int64", + "deprecated": false + } + }, + "BodyLimitConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "LimitBytes": { + "type": "int64", + "deprecated": false + } + }, + "CORSConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "AllowOrigins": { + "type": "[]string", + "deprecated": false + }, + "UnsafeAllowOriginFunc": { + "type": "func(c *echo.Context, origin string) (allowedOrigin string, allowed bool, err error)", + "deprecated": false + }, + "AllowMethods": { + "type": "[]string", + "deprecated": false + }, + "AllowHeaders": { + "type": "[]string", + "deprecated": false + }, + "AllowCredentials": { + "type": "bool", + "deprecated": false + }, + "ExposeHeaders": { + "type": "[]string", + "deprecated": false + }, + "MaxAge": { + "type": "int", + "deprecated": false + } + }, + "CSRFConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "TrustedOrigins": { + "type": "[]string", + "deprecated": false + }, + "AllowSecFetchSiteFunc": { + "type": "func(c *echo.Context) (bool, error)", + "deprecated": false + }, + "TokenLength": { + "type": "uint8", + "deprecated": false + }, + "TokenLookup": { + "type": "string", + "deprecated": false + }, + "Generator": { + "type": "func() string", + "deprecated": false + }, + "ContextKey": { + "type": "string", + "deprecated": false + }, + "CookieName": { + "type": "string", + "deprecated": false + }, + "CookieDomain": { + "type": "string", + "deprecated": false + }, + "CookiePath": { + "type": "string", + "deprecated": false + }, + "CookieMaxAge": { + "type": "int", + "deprecated": false + }, + "CookieSecure": { + "type": "bool", + "deprecated": false + }, + "CookieHTTPOnly": { + "type": "bool", + "deprecated": false + }, + "CookieSameSite": { + "type": "http.SameSite", + "deprecated": false + }, + "ErrorHandler": { + "type": "func(c *echo.Context, err error) error", + "deprecated": false + } + }, + "ContextTimeoutConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "ErrorHandler": { + "type": "func(c *echo.Context, err error) error", + "deprecated": false + }, + "Timeout": { + "type": "time.Duration", + "deprecated": false + } + }, + "DecompressConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "GzipDecompressPool": { + "type": "Decompressor", + "deprecated": false + }, + "MaxDecompressedSize": { + "type": "int64", + "deprecated": false + } + }, + "GzipConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "Level": { + "type": "int", + "deprecated": false + }, + "MinLength": { + "type": "int", + "deprecated": false + } + }, + "KeyAuthConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "KeyLookup": { + "type": "string", + "deprecated": false + }, + "AllowedCheckLimit": { + "type": "uint", + "deprecated": false + }, + "Validator": { + "type": "KeyAuthValidator", + "deprecated": false + }, + "ErrorHandler": { + "type": "KeyAuthErrorHandler", + "deprecated": false + }, + "ContinueOnIgnoredError": { + "type": "bool", + "deprecated": false + } + }, + "MethodOverrideConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "Getter": { + "type": "MethodOverrideGetter", + "deprecated": false + } + }, + "ProxyConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "Balancer": { + "type": "ProxyBalancer", + "deprecated": false + }, + "RetryCount": { + "type": "int", + "deprecated": false + }, + "RetryFilter": { + "type": "func(c *echo.Context, e error) bool", + "deprecated": false + }, + "ErrorHandler": { + "type": "func(c *echo.Context, err error) error", + "deprecated": false + }, + "Rewrite": { + "type": "map[string]string", + "deprecated": false + }, + "RegexRewrite": { + "type": "map[*regexp.Regexp]string", + "deprecated": false + }, + "ContextKey": { + "type": "string", + "deprecated": false + }, + "Transport": { + "type": "http.RoundTripper", + "deprecated": false + }, + "ModifyResponse": { + "type": "func(*http.Response) error", + "deprecated": false + } + }, + "RateLimiterConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "BeforeFunc": { + "type": "BeforeFunc", + "deprecated": false + }, + "IdentifierExtractor": { + "type": "Extractor", + "deprecated": false + }, + "Store": { + "type": "RateLimiterStore", + "deprecated": false + }, + "ErrorHandler": { + "type": "func(c *echo.Context, err error) error", + "deprecated": false + }, + "DenyHandler": { + "type": "func(c *echo.Context, identifier string, err error) error", + "deprecated": false + } + }, + "RateLimiterMemoryStoreConfig": { + "Rate": { + "type": "float64", + "deprecated": false + }, + "Burst": { + "type": "int", + "deprecated": false + }, + "ExpiresIn": { + "type": "time.Duration", + "deprecated": false + } + }, + "RecoverConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "StackSize": { + "type": "int", + "deprecated": false + }, + "DisableStackAll": { + "type": "bool", + "deprecated": false + }, + "DisablePrintStack": { + "type": "bool", + "deprecated": false + } + }, + "RedirectConfig": { + "Code": { + "type": "int", + "deprecated": false + } + }, + "RemoveTrailingSlashConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "RedirectCode": { + "type": "int", + "deprecated": false + } + }, + "RequestIDConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "Generator": { + "type": "func() string", + "deprecated": false + }, + "RequestIDHandler": { + "type": "func(c *echo.Context, requestID string)", + "deprecated": false + }, + "TargetHeader": { + "type": "string", + "deprecated": false + } + }, + "RequestLoggerConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "BeforeNextFunc": { + "type": "func(c *echo.Context)", + "deprecated": false + }, + "LogValuesFunc": { + "type": "func(c *echo.Context, v RequestLoggerValues) error", + "deprecated": false + }, + "HandleError": { + "type": "bool", + "deprecated": false + }, + "LogLatency": { + "type": "bool", + "deprecated": false + }, + "LogProtocol": { + "type": "bool", + "deprecated": false + }, + "LogRemoteIP": { + "type": "bool", + "deprecated": false + }, + "LogHost": { + "type": "bool", + "deprecated": false + }, + "LogMethod": { + "type": "bool", + "deprecated": false + }, + "LogURI": { + "type": "bool", + "deprecated": false + }, + "LogURIPath": { + "type": "bool", + "deprecated": false + }, + "LogRoutePath": { + "type": "bool", + "deprecated": false + }, + "LogRequestID": { + "type": "bool", + "deprecated": false + }, + "LogReferer": { + "type": "bool", + "deprecated": false + }, + "LogUserAgent": { + "type": "bool", + "deprecated": false + }, + "LogStatus": { + "type": "bool", + "deprecated": false + }, + "LogContentLength": { + "type": "bool", + "deprecated": false + }, + "LogResponseSize": { + "type": "bool", + "deprecated": false + }, + "LogHeaders": { + "type": "[]string", + "deprecated": false + }, + "LogQueryParams": { + "type": "[]string", + "deprecated": false + }, + "LogFormValues": { + "type": "[]string", + "deprecated": false + } + }, + "RewriteConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "Rules": { + "type": "map[string]string", + "deprecated": false + }, + "RegexRules": { + "type": "map[*regexp.Regexp]string", + "deprecated": false + } + }, + "SecureConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "XSSProtection": { + "type": "string", + "deprecated": false + }, + "ContentTypeNosniff": { + "type": "string", + "deprecated": false + }, + "XFrameOptions": { + "type": "string", + "deprecated": false + }, + "HSTSMaxAge": { + "type": "int", + "deprecated": false + }, + "HSTSExcludeSubdomains": { + "type": "bool", + "deprecated": false + }, + "ContentSecurityPolicy": { + "type": "string", + "deprecated": false + }, + "CSPReportOnly": { + "type": "bool", + "deprecated": false + }, + "HSTSPreloadEnabled": { + "type": "bool", + "deprecated": false + }, + "ReferrerPolicy": { + "type": "string", + "deprecated": false + } + }, + "StaticConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "Root": { + "type": "string", + "deprecated": false + }, + "Filesystem": { + "type": "fs.FS", + "deprecated": false + }, + "Index": { + "type": "string", + "deprecated": false + }, + "HTML5": { + "type": "bool", + "deprecated": false + }, + "Browse": { + "type": "bool", + "deprecated": false + }, + "IgnoreBase": { + "type": "bool", + "deprecated": false + }, + "DisablePathUnescaping": { + "type": "bool", + "deprecated": true + }, + "EnablePathUnescaping": { + "type": "bool", + "deprecated": false + }, + "DirectoryListTemplate": { + "type": "string", + "deprecated": false + } + } + }, + "functions": { + "AddTrailingSlash": "func AddTrailingSlash() echo.MiddlewareFunc", + "AddTrailingSlashWithConfig": "func AddTrailingSlashWithConfig(config AddTrailingSlashConfig) echo.MiddlewareFunc", + "BasicAuth": "func BasicAuth(fn BasicAuthValidator) echo.MiddlewareFunc", + "BasicAuthWithConfig": "func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc", + "BodyDump": "func BodyDump(handler BodyDumpHandler) echo.MiddlewareFunc", + "BodyDumpWithConfig": "func BodyDumpWithConfig(config BodyDumpConfig) echo.MiddlewareFunc", + "BodyLimit": "func BodyLimit(limitBytes int64) echo.MiddlewareFunc", + "BodyLimitWithConfig": "func BodyLimitWithConfig(config BodyLimitConfig) echo.MiddlewareFunc", + "CORS": "func CORS(allowOrigins ...string) echo.MiddlewareFunc", + "CORSWithConfig": "func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc", + "CSRF": "func CSRF() echo.MiddlewareFunc", + "CSRFWithConfig": "func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc", + "ContextTimeout": "func ContextTimeout(timeout time.Duration) echo.MiddlewareFunc", + "ContextTimeoutWithConfig": "func ContextTimeoutWithConfig(config ContextTimeoutConfig) echo.MiddlewareFunc", + "CreateExtractors": "func CreateExtractors(lookups string, limit uint) ([]ValuesExtractor, error)", + "Decompress": "func Decompress() echo.MiddlewareFunc", + "DecompressWithConfig": "func DecompressWithConfig(config DecompressConfig) echo.MiddlewareFunc", + "DefaultSkipper": "func DefaultSkipper(c *echo.Context) bool", + "Gzip": "func Gzip() echo.MiddlewareFunc", + "GzipWithConfig": "func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc", + "HTTPSNonWWWRedirect": "func HTTPSNonWWWRedirect() echo.MiddlewareFunc", + "HTTPSNonWWWRedirectWithConfig": "func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc", + "HTTPSRedirect": "func HTTPSRedirect() echo.MiddlewareFunc", + "HTTPSRedirectWithConfig": "func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc", + "HTTPSWWWRedirect": "func HTTPSWWWRedirect() echo.MiddlewareFunc", + "HTTPSWWWRedirectWithConfig": "func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc", + "KeyAuth": "func KeyAuth(fn KeyAuthValidator) echo.MiddlewareFunc", + "KeyAuthWithConfig": "func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc", + "MethodFromForm": "func MethodFromForm(param string) MethodOverrideGetter", + "MethodFromHeader": "func MethodFromHeader(header string) MethodOverrideGetter", + "MethodFromQuery": "func MethodFromQuery(param string) MethodOverrideGetter", + "MethodOverride": "func MethodOverride() echo.MiddlewareFunc", + "MethodOverrideWithConfig": "func MethodOverrideWithConfig(config MethodOverrideConfig) echo.MiddlewareFunc", + "NewRandomBalancer": "func NewRandomBalancer(targets []*ProxyTarget) ProxyBalancer", + "NewRateLimiterMemoryStore": "func NewRateLimiterMemoryStore(rateLimit float64) (store *RateLimiterMemoryStore)", + "NewRateLimiterMemoryStoreWithConfig": "func NewRateLimiterMemoryStoreWithConfig(config RateLimiterMemoryStoreConfig) (store *RateLimiterMemoryStore)", + "NewRoundRobinBalancer": "func NewRoundRobinBalancer(targets []*ProxyTarget) ProxyBalancer", + "NonWWWRedirect": "func NonWWWRedirect() echo.MiddlewareFunc", + "NonWWWRedirectWithConfig": "func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc", + "Proxy": "func Proxy(balancer ProxyBalancer) echo.MiddlewareFunc", + "ProxyWithConfig": "func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc", + "RateLimiter": "func RateLimiter(store RateLimiterStore) echo.MiddlewareFunc", + "RateLimiterWithConfig": "func RateLimiterWithConfig(config RateLimiterConfig) echo.MiddlewareFunc", + "Recover": "func Recover() echo.MiddlewareFunc", + "RecoverWithConfig": "func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc", + "RemoveTrailingSlash": "func RemoveTrailingSlash() echo.MiddlewareFunc", + "RemoveTrailingSlashWithConfig": "func RemoveTrailingSlashWithConfig(config RemoveTrailingSlashConfig) echo.MiddlewareFunc", + "RequestID": "func RequestID() echo.MiddlewareFunc", + "RequestIDWithConfig": "func RequestIDWithConfig(config RequestIDConfig) echo.MiddlewareFunc", + "RequestLogger": "func RequestLogger() echo.MiddlewareFunc", + "RequestLoggerWithConfig": "func RequestLoggerWithConfig(config RequestLoggerConfig) echo.MiddlewareFunc", + "Rewrite": "func Rewrite(rules map[string]string) echo.MiddlewareFunc", + "RewriteWithConfig": "func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc", + "Secure": "func Secure() echo.MiddlewareFunc", + "SecureWithConfig": "func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc", + "Static": "func Static(root string) echo.MiddlewareFunc", + "StaticWithConfig": "func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc", + "WWWRedirect": "func WWWRedirect() echo.MiddlewareFunc", + "WWWRedirectWithConfig": "func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc" + } +} diff --git a/site/next-source.json b/site/next-source.json new file mode 100644 index 00000000..d90df8ac --- /dev/null +++ b/site/next-source.json @@ -0,0 +1,5 @@ +{ + "repository": "https://github.com/labstack/echo.git", + "revision": "e804f422d2b0546df620b7f73b7563f509368972", + "release": "next" +} diff --git a/site/package.json b/site/package.json index 3c03aec5..085a1414 100644 --- a/site/package.json +++ b/site/package.json @@ -3,10 +3,19 @@ "type": "module", "version": "0.1.0", "scripts": { - "dev": "astro dev", - "build": "astro build", + "dev": "npm run source:prepare && astro dev", + "build": "node scripts/build-site.mjs", "preview": "astro preview", - "astro": "astro" + "astro": "astro", + "source:prepare": "node scripts/prepare-echo-source.mjs", + "source:check": "npm run source:prepare && node scripts/check-source.mjs", + "source:accept": "node scripts/accept-source.mjs", + "translations:status": "node scripts/translation-status.mjs", + "translations:accept": "node scripts/accept-translations.mjs", + "site:check": "node scripts/check-site.mjs", + "site:accept": "node scripts/check-site.mjs --accept", + "performance:check": "node scripts/check-performance.mjs", + "performance:accept": "node scripts/check-performance.mjs --accept" }, "dependencies": { "@astrojs/starlight": "^0.40.0", diff --git a/site/performance-baseline.json b/site/performance-baseline.json new file mode 100644 index 00000000..06dc8866 --- /dev/null +++ b/site/performance-baseline.json @@ -0,0 +1,80 @@ +{ + "/": { + "html": { + "raw": 27523, + "gzip": 8601 + }, + "firstLoadAssets": { + "raw": 94854, + "gzip": 19546 + }, + "externalDomains": [ + "encore.dev", + "fonts.googleapis.com", + "fonts.gstatic.com", + "github.com", + "pkg.go.dev", + "unpkg.com", + "widget.kapa.ai", + "x.com" + ] + }, + "/middleware/cors/": { + "html": { + "raw": 60522, + "gzip": 11390 + }, + "firstLoadAssets": { + "raw": 118355, + "gzip": 26052 + }, + "externalDomains": [ + "blog.portswigger.net", + "fonts.googleapis.com", + "fonts.gstatic.com", + "github.com", + "unpkg.com", + "widget.kapa.ai", + "x.com" + ] + }, + "/next/": { + "html": { + "raw": 27645, + "gzip": 8615 + }, + "firstLoadAssets": { + "raw": 94869, + "gzip": 19549 + }, + "externalDomains": [ + "encore.dev", + "fonts.googleapis.com", + "fonts.gstatic.com", + "github.com", + "pkg.go.dev", + "unpkg.com", + "widget.kapa.ai", + "x.com" + ] + }, + "/next/middleware/cors/": { + "html": { + "raw": 60976, + "gzip": 11405 + }, + "firstLoadAssets": { + "raw": 118370, + "gzip": 26055 + }, + "externalDomains": [ + "blog.portswigger.net", + "fonts.googleapis.com", + "fonts.gstatic.com", + "github.com", + "unpkg.com", + "widget.kapa.ai", + "x.com" + ] + } +} diff --git a/site/reference-baseline.json b/site/reference-baseline.json new file mode 100644 index 00000000..3230fc2d --- /dev/null +++ b/site/reference-baseline.json @@ -0,0 +1,613 @@ +{ + "configs": { + "AddTrailingSlashConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "RedirectCode": { + "type": "int", + "deprecated": false + } + }, + "BasicAuthConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "Validator": { + "type": "BasicAuthValidator", + "deprecated": false + }, + "Realm": { + "type": "string", + "deprecated": false + }, + "AllowedCheckLimit": { + "type": "uint", + "deprecated": false + } + }, + "BodyDumpConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "Handler": { + "type": "BodyDumpHandler", + "deprecated": false + }, + "MaxRequestBytes": { + "type": "int64", + "deprecated": false + }, + "MaxResponseBytes": { + "type": "int64", + "deprecated": false + } + }, + "BodyLimitConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "LimitBytes": { + "type": "int64", + "deprecated": false + } + }, + "CORSConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "AllowOrigins": { + "type": "[]string", + "deprecated": false + }, + "UnsafeAllowOriginFunc": { + "type": "func(c *echo.Context, origin string) (allowedOrigin string, allowed bool, err error)", + "deprecated": false + }, + "AllowMethods": { + "type": "[]string", + "deprecated": false + }, + "AllowHeaders": { + "type": "[]string", + "deprecated": false + }, + "AllowCredentials": { + "type": "bool", + "deprecated": false + }, + "ExposeHeaders": { + "type": "[]string", + "deprecated": false + }, + "MaxAge": { + "type": "int", + "deprecated": false + } + }, + "CSRFConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "TrustedOrigins": { + "type": "[]string", + "deprecated": false + }, + "AllowSecFetchSiteFunc": { + "type": "func(c *echo.Context) (bool, error)", + "deprecated": false + }, + "TokenLength": { + "type": "uint8", + "deprecated": false + }, + "TokenLookup": { + "type": "string", + "deprecated": false + }, + "Generator": { + "type": "func() string", + "deprecated": false + }, + "ContextKey": { + "type": "string", + "deprecated": false + }, + "CookieName": { + "type": "string", + "deprecated": false + }, + "CookieDomain": { + "type": "string", + "deprecated": false + }, + "CookiePath": { + "type": "string", + "deprecated": false + }, + "CookieMaxAge": { + "type": "int", + "deprecated": false + }, + "CookieSecure": { + "type": "bool", + "deprecated": false + }, + "CookieHTTPOnly": { + "type": "bool", + "deprecated": false + }, + "CookieSameSite": { + "type": "http.SameSite", + "deprecated": false + }, + "ErrorHandler": { + "type": "func(c *echo.Context, err error) error", + "deprecated": false + } + }, + "ContextTimeoutConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "ErrorHandler": { + "type": "func(c *echo.Context, err error) error", + "deprecated": false + }, + "Timeout": { + "type": "time.Duration", + "deprecated": false + } + }, + "DecompressConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "GzipDecompressPool": { + "type": "Decompressor", + "deprecated": false + }, + "MaxDecompressedSize": { + "type": "int64", + "deprecated": false + } + }, + "GzipConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "Level": { + "type": "int", + "deprecated": false + }, + "MinLength": { + "type": "int", + "deprecated": false + } + }, + "KeyAuthConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "KeyLookup": { + "type": "string", + "deprecated": false + }, + "AllowedCheckLimit": { + "type": "uint", + "deprecated": false + }, + "Validator": { + "type": "KeyAuthValidator", + "deprecated": false + }, + "ErrorHandler": { + "type": "KeyAuthErrorHandler", + "deprecated": false + }, + "ContinueOnIgnoredError": { + "type": "bool", + "deprecated": false + } + }, + "MethodOverrideConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "Getter": { + "type": "MethodOverrideGetter", + "deprecated": false + } + }, + "ProxyConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "Balancer": { + "type": "ProxyBalancer", + "deprecated": false + }, + "RetryCount": { + "type": "int", + "deprecated": false + }, + "RetryFilter": { + "type": "func(c *echo.Context, e error) bool", + "deprecated": false + }, + "ErrorHandler": { + "type": "func(c *echo.Context, err error) error", + "deprecated": false + }, + "Rewrite": { + "type": "map[string]string", + "deprecated": false + }, + "RegexRewrite": { + "type": "map[*regexp.Regexp]string", + "deprecated": false + }, + "ContextKey": { + "type": "string", + "deprecated": false + }, + "Transport": { + "type": "http.RoundTripper", + "deprecated": false + }, + "ModifyResponse": { + "type": "func(*http.Response) error", + "deprecated": false + } + }, + "RateLimiterConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "BeforeFunc": { + "type": "BeforeFunc", + "deprecated": false + }, + "IdentifierExtractor": { + "type": "Extractor", + "deprecated": false + }, + "Store": { + "type": "RateLimiterStore", + "deprecated": false + }, + "ErrorHandler": { + "type": "func(c *echo.Context, err error) error", + "deprecated": false + }, + "DenyHandler": { + "type": "func(c *echo.Context, identifier string, err error) error", + "deprecated": false + } + }, + "RateLimiterMemoryStoreConfig": { + "Rate": { + "type": "float64", + "deprecated": false + }, + "Burst": { + "type": "int", + "deprecated": false + }, + "ExpiresIn": { + "type": "time.Duration", + "deprecated": false + } + }, + "RecoverConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "StackSize": { + "type": "int", + "deprecated": false + }, + "DisableStackAll": { + "type": "bool", + "deprecated": false + }, + "DisablePrintStack": { + "type": "bool", + "deprecated": false + } + }, + "RedirectConfig": { + "Code": { + "type": "int", + "deprecated": false + } + }, + "RemoveTrailingSlashConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "RedirectCode": { + "type": "int", + "deprecated": false + } + }, + "RequestIDConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "Generator": { + "type": "func() string", + "deprecated": false + }, + "RequestIDHandler": { + "type": "func(c *echo.Context, requestID string)", + "deprecated": false + }, + "TargetHeader": { + "type": "string", + "deprecated": false + } + }, + "RequestLoggerConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "BeforeNextFunc": { + "type": "func(c *echo.Context)", + "deprecated": false + }, + "LogValuesFunc": { + "type": "func(c *echo.Context, v RequestLoggerValues) error", + "deprecated": false + }, + "HandleError": { + "type": "bool", + "deprecated": false + }, + "LogLatency": { + "type": "bool", + "deprecated": false + }, + "LogProtocol": { + "type": "bool", + "deprecated": false + }, + "LogRemoteIP": { + "type": "bool", + "deprecated": false + }, + "LogHost": { + "type": "bool", + "deprecated": false + }, + "LogMethod": { + "type": "bool", + "deprecated": false + }, + "LogURI": { + "type": "bool", + "deprecated": false + }, + "LogURIPath": { + "type": "bool", + "deprecated": false + }, + "LogRoutePath": { + "type": "bool", + "deprecated": false + }, + "LogRequestID": { + "type": "bool", + "deprecated": false + }, + "LogReferer": { + "type": "bool", + "deprecated": false + }, + "LogUserAgent": { + "type": "bool", + "deprecated": false + }, + "LogStatus": { + "type": "bool", + "deprecated": false + }, + "LogContentLength": { + "type": "bool", + "deprecated": false + }, + "LogResponseSize": { + "type": "bool", + "deprecated": false + }, + "LogHeaders": { + "type": "[]string", + "deprecated": false + }, + "LogQueryParams": { + "type": "[]string", + "deprecated": false + }, + "LogFormValues": { + "type": "[]string", + "deprecated": false + } + }, + "RewriteConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "Rules": { + "type": "map[string]string", + "deprecated": false + }, + "RegexRules": { + "type": "map[*regexp.Regexp]string", + "deprecated": false + } + }, + "SecureConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "XSSProtection": { + "type": "string", + "deprecated": false + }, + "ContentTypeNosniff": { + "type": "string", + "deprecated": false + }, + "XFrameOptions": { + "type": "string", + "deprecated": false + }, + "HSTSMaxAge": { + "type": "int", + "deprecated": false + }, + "HSTSExcludeSubdomains": { + "type": "bool", + "deprecated": false + }, + "ContentSecurityPolicy": { + "type": "string", + "deprecated": false + }, + "CSPReportOnly": { + "type": "bool", + "deprecated": false + }, + "HSTSPreloadEnabled": { + "type": "bool", + "deprecated": false + }, + "ReferrerPolicy": { + "type": "string", + "deprecated": false + } + }, + "StaticConfig": { + "Skipper": { + "type": "Skipper", + "deprecated": false + }, + "Root": { + "type": "string", + "deprecated": false + }, + "Filesystem": { + "type": "fs.FS", + "deprecated": false + }, + "Index": { + "type": "string", + "deprecated": false + }, + "HTML5": { + "type": "bool", + "deprecated": false + }, + "Browse": { + "type": "bool", + "deprecated": false + }, + "IgnoreBase": { + "type": "bool", + "deprecated": false + }, + "DisablePathUnescaping": { + "type": "bool", + "deprecated": true + }, + "EnablePathUnescaping": { + "type": "bool", + "deprecated": false + }, + "DirectoryListTemplate": { + "type": "string", + "deprecated": false + } + } + }, + "functions": { + "AddTrailingSlash": "func AddTrailingSlash() echo.MiddlewareFunc", + "AddTrailingSlashWithConfig": "func AddTrailingSlashWithConfig(config AddTrailingSlashConfig) echo.MiddlewareFunc", + "BasicAuth": "func BasicAuth(fn BasicAuthValidator) echo.MiddlewareFunc", + "BasicAuthWithConfig": "func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc", + "BodyDump": "func BodyDump(handler BodyDumpHandler) echo.MiddlewareFunc", + "BodyDumpWithConfig": "func BodyDumpWithConfig(config BodyDumpConfig) echo.MiddlewareFunc", + "BodyLimit": "func BodyLimit(limitBytes int64) echo.MiddlewareFunc", + "BodyLimitWithConfig": "func BodyLimitWithConfig(config BodyLimitConfig) echo.MiddlewareFunc", + "CORS": "func CORS(allowOrigins ...string) echo.MiddlewareFunc", + "CORSWithConfig": "func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc", + "CSRF": "func CSRF() echo.MiddlewareFunc", + "CSRFWithConfig": "func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc", + "ContextTimeout": "func ContextTimeout(timeout time.Duration) echo.MiddlewareFunc", + "ContextTimeoutWithConfig": "func ContextTimeoutWithConfig(config ContextTimeoutConfig) echo.MiddlewareFunc", + "CreateExtractors": "func CreateExtractors(lookups string, limit uint) ([]ValuesExtractor, error)", + "Decompress": "func Decompress() echo.MiddlewareFunc", + "DecompressWithConfig": "func DecompressWithConfig(config DecompressConfig) echo.MiddlewareFunc", + "DefaultSkipper": "func DefaultSkipper(c *echo.Context) bool", + "Gzip": "func Gzip() echo.MiddlewareFunc", + "GzipWithConfig": "func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc", + "HTTPSNonWWWRedirect": "func HTTPSNonWWWRedirect() echo.MiddlewareFunc", + "HTTPSNonWWWRedirectWithConfig": "func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc", + "HTTPSRedirect": "func HTTPSRedirect() echo.MiddlewareFunc", + "HTTPSRedirectWithConfig": "func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc", + "HTTPSWWWRedirect": "func HTTPSWWWRedirect() echo.MiddlewareFunc", + "HTTPSWWWRedirectWithConfig": "func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc", + "KeyAuth": "func KeyAuth(fn KeyAuthValidator) echo.MiddlewareFunc", + "KeyAuthWithConfig": "func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc", + "MethodFromForm": "func MethodFromForm(param string) MethodOverrideGetter", + "MethodFromHeader": "func MethodFromHeader(header string) MethodOverrideGetter", + "MethodFromQuery": "func MethodFromQuery(param string) MethodOverrideGetter", + "MethodOverride": "func MethodOverride() echo.MiddlewareFunc", + "MethodOverrideWithConfig": "func MethodOverrideWithConfig(config MethodOverrideConfig) echo.MiddlewareFunc", + "NewRandomBalancer": "func NewRandomBalancer(targets []*ProxyTarget) ProxyBalancer", + "NewRateLimiterMemoryStore": "func NewRateLimiterMemoryStore(rateLimit float64) (store *RateLimiterMemoryStore)", + "NewRateLimiterMemoryStoreWithConfig": "func NewRateLimiterMemoryStoreWithConfig(config RateLimiterMemoryStoreConfig) (store *RateLimiterMemoryStore)", + "NewRoundRobinBalancer": "func NewRoundRobinBalancer(targets []*ProxyTarget) ProxyBalancer", + "NonWWWRedirect": "func NonWWWRedirect() echo.MiddlewareFunc", + "NonWWWRedirectWithConfig": "func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc", + "Proxy": "func Proxy(balancer ProxyBalancer) echo.MiddlewareFunc", + "ProxyWithConfig": "func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc", + "RateLimiter": "func RateLimiter(store RateLimiterStore) echo.MiddlewareFunc", + "RateLimiterWithConfig": "func RateLimiterWithConfig(config RateLimiterConfig) echo.MiddlewareFunc", + "Recover": "func Recover() echo.MiddlewareFunc", + "RecoverWithConfig": "func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc", + "RemoveTrailingSlash": "func RemoveTrailingSlash() echo.MiddlewareFunc", + "RemoveTrailingSlashWithConfig": "func RemoveTrailingSlashWithConfig(config RemoveTrailingSlashConfig) echo.MiddlewareFunc", + "RequestID": "func RequestID() echo.MiddlewareFunc", + "RequestIDWithConfig": "func RequestIDWithConfig(config RequestIDConfig) echo.MiddlewareFunc", + "RequestLogger": "func RequestLogger() echo.MiddlewareFunc", + "RequestLoggerWithConfig": "func RequestLoggerWithConfig(config RequestLoggerConfig) echo.MiddlewareFunc", + "Rewrite": "func Rewrite(rules map[string]string) echo.MiddlewareFunc", + "RewriteWithConfig": "func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc", + "Secure": "func Secure() echo.MiddlewareFunc", + "SecureWithConfig": "func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc", + "Static": "func Static(root string) echo.MiddlewareFunc", + "StaticWithConfig": "func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc", + "WWWRedirect": "func WWWRedirect() echo.MiddlewareFunc", + "WWWRedirectWithConfig": "func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc" + } +} diff --git a/site/reference-pages.json b/site/reference-pages.json new file mode 100644 index 00000000..5e6380dc --- /dev/null +++ b/site/reference-pages.json @@ -0,0 +1,22 @@ +{ + "basic-auth": ["BasicAuthConfig"], + "body-dump": ["BodyDumpConfig"], + "body-limit": ["BodyLimitConfig"], + "context-timeout": ["ContextTimeoutConfig"], + "cors": ["CORSConfig"], + "csrf": ["CSRFConfig"], + "decompress": ["DecompressConfig"], + "gzip": ["GzipConfig"], + "key-auth": ["KeyAuthConfig"], + "logger": ["RequestLoggerConfig"], + "method-override": ["MethodOverrideConfig"], + "proxy": ["ProxyConfig"], + "rate-limiter": ["RateLimiterConfig", "RateLimiterMemoryStoreConfig"], + "recover": ["RecoverConfig"], + "redirect": ["RedirectConfig"], + "request-id": ["RequestIDConfig"], + "rewrite": ["RewriteConfig"], + "secure": ["SecureConfig"], + "static": ["StaticConfig"], + "trailing-slash": ["AddTrailingSlashConfig", "RemoveTrailingSlashConfig"] +} diff --git a/site/route-baseline.json b/site/route-baseline.json new file mode 100644 index 00000000..6e48a8e9 --- /dev/null +++ b/site/route-baseline.json @@ -0,0 +1,680 @@ +{ + "routes": [ + "/", + "/404.html", + "/cookbook/auto-tls/", + "/cookbook/cors/", + "/cookbook/crud/", + "/cookbook/embed-resources/", + "/cookbook/file-download/", + "/cookbook/file-upload/", + "/cookbook/graceful-shutdown/", + "/cookbook/hello-world/", + "/cookbook/http2-server-push/", + "/cookbook/http2/", + "/cookbook/jsonp/", + "/cookbook/jwt/", + "/cookbook/load-balancing/", + "/cookbook/middleware/", + "/cookbook/reverse-proxy/", + "/cookbook/sse/", + "/cookbook/streaming-response/", + "/cookbook/subdomain/", + "/cookbook/timeout/", + "/cookbook/websocket/", + "/docs/", + "/docs/binding/", + "/docs/category/cookbook/", + "/docs/category/guide/", + "/docs/category/middleware/", + "/docs/cookbook/auto-tls/", + "/docs/cookbook/cors/", + "/docs/cookbook/crud/", + "/docs/cookbook/embed-resources/", + "/docs/cookbook/file-download/", + "/docs/cookbook/file-upload/", + "/docs/cookbook/graceful-shutdown/", + "/docs/cookbook/hello-world/", + "/docs/cookbook/http2-server-push/", + "/docs/cookbook/http2/", + "/docs/cookbook/jsonp/", + "/docs/cookbook/jwt/", + "/docs/cookbook/load-balancing/", + "/docs/cookbook/middleware/", + "/docs/cookbook/reverse-proxy/", + "/docs/cookbook/sse/", + "/docs/cookbook/streaming-response/", + "/docs/cookbook/subdomain/", + "/docs/cookbook/timeout/", + "/docs/cookbook/websocket/", + "/docs/cookies/", + "/docs/customization/", + "/docs/error-handling/", + "/docs/ip-address/", + "/docs/middleware/basic-auth/", + "/docs/middleware/body-dump/", + "/docs/middleware/body-limit/", + "/docs/middleware/casbin-auth/", + "/docs/middleware/context-timeout/", + "/docs/middleware/cors/", + "/docs/middleware/csrf/", + "/docs/middleware/decompress/", + "/docs/middleware/gzip/", + "/docs/middleware/index/", + "/docs/middleware/jwt/", + "/docs/middleware/key-auth/", + "/docs/middleware/logger/", + "/docs/middleware/method-override/", + "/docs/middleware/open-telemetry/", + "/docs/middleware/prometheus/", + "/docs/middleware/proxy/", + "/docs/middleware/rate-limiter/", + "/docs/middleware/recover/", + "/docs/middleware/redirect/", + "/docs/middleware/request-id/", + "/docs/middleware/rewrite/", + "/docs/middleware/secure/", + "/docs/middleware/session/", + "/docs/middleware/static/", + "/docs/middleware/trailing-slash/", + "/docs/quick-start/", + "/docs/request/", + "/docs/response/", + "/docs/routing/", + "/docs/start-server/", + "/docs/static-files/", + "/docs/templates/", + "/docs/testing/", + "/es/", + "/es/cookbook/auto-tls/", + "/es/cookbook/cors/", + "/es/cookbook/crud/", + "/es/cookbook/embed-resources/", + "/es/cookbook/file-download/", + "/es/cookbook/file-upload/", + "/es/cookbook/graceful-shutdown/", + "/es/cookbook/hello-world/", + "/es/cookbook/http2-server-push/", + "/es/cookbook/http2/", + "/es/cookbook/jsonp/", + "/es/cookbook/jwt/", + "/es/cookbook/load-balancing/", + "/es/cookbook/middleware/", + "/es/cookbook/reverse-proxy/", + "/es/cookbook/sse/", + "/es/cookbook/streaming-response/", + "/es/cookbook/subdomain/", + "/es/cookbook/timeout/", + "/es/cookbook/websocket/", + "/es/guide/binding/", + "/es/guide/context/", + "/es/guide/cookies/", + "/es/guide/customization/", + "/es/guide/error-handling/", + "/es/guide/installation/", + "/es/guide/ip-address/", + "/es/guide/quickstart/", + "/es/guide/request/", + "/es/guide/response/", + "/es/guide/routing/", + "/es/guide/static-files/", + "/es/guide/templates/", + "/es/guide/testing/", + "/es/middleware/", + "/es/middleware/basic-auth/", + "/es/middleware/body-dump/", + "/es/middleware/body-limit/", + "/es/middleware/casbin-auth/", + "/es/middleware/context-timeout/", + "/es/middleware/cors/", + "/es/middleware/csrf/", + "/es/middleware/decompress/", + "/es/middleware/gzip/", + "/es/middleware/jwt/", + "/es/middleware/key-auth/", + "/es/middleware/logger/", + "/es/middleware/method-override/", + "/es/middleware/open-telemetry/", + "/es/middleware/prometheus/", + "/es/middleware/proxy/", + "/es/middleware/rate-limiter/", + "/es/middleware/recover/", + "/es/middleware/redirect/", + "/es/middleware/request-id/", + "/es/middleware/rewrite/", + "/es/middleware/secure/", + "/es/middleware/session/", + "/es/middleware/static/", + "/es/middleware/trailing-slash/", + "/guide/binding/", + "/guide/context/", + "/guide/cookies/", + "/guide/customization/", + "/guide/error-handling/", + "/guide/installation/", + "/guide/ip-address/", + "/guide/quickstart/", + "/guide/request/", + "/guide/response/", + "/guide/routing/", + "/guide/static-files/", + "/guide/templates/", + "/guide/testing/", + "/ja/", + "/ja/cookbook/auto-tls/", + "/ja/cookbook/cors/", + "/ja/cookbook/crud/", + "/ja/cookbook/embed-resources/", + "/ja/cookbook/file-download/", + "/ja/cookbook/file-upload/", + "/ja/cookbook/graceful-shutdown/", + "/ja/cookbook/hello-world/", + "/ja/cookbook/http2-server-push/", + "/ja/cookbook/http2/", + "/ja/cookbook/jsonp/", + "/ja/cookbook/jwt/", + "/ja/cookbook/load-balancing/", + "/ja/cookbook/middleware/", + "/ja/cookbook/reverse-proxy/", + "/ja/cookbook/sse/", + "/ja/cookbook/streaming-response/", + "/ja/cookbook/subdomain/", + "/ja/cookbook/timeout/", + "/ja/cookbook/websocket/", + "/ja/guide/binding/", + "/ja/guide/context/", + "/ja/guide/cookies/", + "/ja/guide/customization/", + "/ja/guide/error-handling/", + "/ja/guide/installation/", + "/ja/guide/ip-address/", + "/ja/guide/quickstart/", + "/ja/guide/request/", + "/ja/guide/response/", + "/ja/guide/routing/", + "/ja/guide/static-files/", + "/ja/guide/templates/", + "/ja/guide/testing/", + "/ja/middleware/", + "/ja/middleware/basic-auth/", + "/ja/middleware/body-dump/", + "/ja/middleware/body-limit/", + "/ja/middleware/casbin-auth/", + "/ja/middleware/context-timeout/", + "/ja/middleware/cors/", + "/ja/middleware/csrf/", + "/ja/middleware/decompress/", + "/ja/middleware/gzip/", + "/ja/middleware/jwt/", + "/ja/middleware/key-auth/", + "/ja/middleware/logger/", + "/ja/middleware/method-override/", + "/ja/middleware/open-telemetry/", + "/ja/middleware/prometheus/", + "/ja/middleware/proxy/", + "/ja/middleware/rate-limiter/", + "/ja/middleware/recover/", + "/ja/middleware/redirect/", + "/ja/middleware/request-id/", + "/ja/middleware/rewrite/", + "/ja/middleware/secure/", + "/ja/middleware/session/", + "/ja/middleware/static/", + "/ja/middleware/trailing-slash/", + "/middleware/", + "/middleware/basic-auth/", + "/middleware/body-dump/", + "/middleware/body-limit/", + "/middleware/casbin-auth/", + "/middleware/context-timeout/", + "/middleware/cors/", + "/middleware/csrf/", + "/middleware/decompress/", + "/middleware/gzip/", + "/middleware/jwt/", + "/middleware/key-auth/", + "/middleware/logger/", + "/middleware/method-override/", + "/middleware/open-telemetry/", + "/middleware/prometheus/", + "/middleware/proxy/", + "/middleware/rate-limiter/", + "/middleware/recover/", + "/middleware/redirect/", + "/middleware/request-id/", + "/middleware/rewrite/", + "/middleware/secure/", + "/middleware/session/", + "/middleware/static/", + "/middleware/trailing-slash/", + "/next/", + "/next/404.html", + "/next/cookbook/auto-tls/", + "/next/cookbook/cors/", + "/next/cookbook/crud/", + "/next/cookbook/embed-resources/", + "/next/cookbook/file-download/", + "/next/cookbook/file-upload/", + "/next/cookbook/graceful-shutdown/", + "/next/cookbook/hello-world/", + "/next/cookbook/http2-server-push/", + "/next/cookbook/http2/", + "/next/cookbook/jsonp/", + "/next/cookbook/jwt/", + "/next/cookbook/load-balancing/", + "/next/cookbook/middleware/", + "/next/cookbook/reverse-proxy/", + "/next/cookbook/sse/", + "/next/cookbook/streaming-response/", + "/next/cookbook/subdomain/", + "/next/cookbook/timeout/", + "/next/cookbook/websocket/", + "/next/es/", + "/next/es/cookbook/auto-tls/", + "/next/es/cookbook/cors/", + "/next/es/cookbook/crud/", + "/next/es/cookbook/embed-resources/", + "/next/es/cookbook/file-download/", + "/next/es/cookbook/file-upload/", + "/next/es/cookbook/graceful-shutdown/", + "/next/es/cookbook/hello-world/", + "/next/es/cookbook/http2-server-push/", + "/next/es/cookbook/http2/", + "/next/es/cookbook/jsonp/", + "/next/es/cookbook/jwt/", + "/next/es/cookbook/load-balancing/", + "/next/es/cookbook/middleware/", + "/next/es/cookbook/reverse-proxy/", + "/next/es/cookbook/sse/", + "/next/es/cookbook/streaming-response/", + "/next/es/cookbook/subdomain/", + "/next/es/cookbook/timeout/", + "/next/es/cookbook/websocket/", + "/next/es/guide/binding/", + "/next/es/guide/context/", + "/next/es/guide/cookies/", + "/next/es/guide/customization/", + "/next/es/guide/error-handling/", + "/next/es/guide/installation/", + "/next/es/guide/ip-address/", + "/next/es/guide/quickstart/", + "/next/es/guide/request/", + "/next/es/guide/response/", + "/next/es/guide/routing/", + "/next/es/guide/static-files/", + "/next/es/guide/templates/", + "/next/es/guide/testing/", + "/next/es/middleware/", + "/next/es/middleware/basic-auth/", + "/next/es/middleware/body-dump/", + "/next/es/middleware/body-limit/", + "/next/es/middleware/casbin-auth/", + "/next/es/middleware/context-timeout/", + "/next/es/middleware/cors/", + "/next/es/middleware/csrf/", + "/next/es/middleware/decompress/", + "/next/es/middleware/gzip/", + "/next/es/middleware/jwt/", + "/next/es/middleware/key-auth/", + "/next/es/middleware/logger/", + "/next/es/middleware/method-override/", + "/next/es/middleware/open-telemetry/", + "/next/es/middleware/prometheus/", + "/next/es/middleware/proxy/", + "/next/es/middleware/rate-limiter/", + "/next/es/middleware/recover/", + "/next/es/middleware/redirect/", + "/next/es/middleware/request-id/", + "/next/es/middleware/rewrite/", + "/next/es/middleware/secure/", + "/next/es/middleware/session/", + "/next/es/middleware/static/", + "/next/es/middleware/trailing-slash/", + "/next/guide/binding/", + "/next/guide/context/", + "/next/guide/cookies/", + "/next/guide/customization/", + "/next/guide/error-handling/", + "/next/guide/installation/", + "/next/guide/ip-address/", + "/next/guide/quickstart/", + "/next/guide/request/", + "/next/guide/response/", + "/next/guide/routing/", + "/next/guide/static-files/", + "/next/guide/templates/", + "/next/guide/testing/", + "/next/ja/", + "/next/ja/cookbook/auto-tls/", + "/next/ja/cookbook/cors/", + "/next/ja/cookbook/crud/", + "/next/ja/cookbook/embed-resources/", + "/next/ja/cookbook/file-download/", + "/next/ja/cookbook/file-upload/", + "/next/ja/cookbook/graceful-shutdown/", + "/next/ja/cookbook/hello-world/", + "/next/ja/cookbook/http2-server-push/", + "/next/ja/cookbook/http2/", + "/next/ja/cookbook/jsonp/", + "/next/ja/cookbook/jwt/", + "/next/ja/cookbook/load-balancing/", + "/next/ja/cookbook/middleware/", + "/next/ja/cookbook/reverse-proxy/", + "/next/ja/cookbook/sse/", + "/next/ja/cookbook/streaming-response/", + "/next/ja/cookbook/subdomain/", + "/next/ja/cookbook/timeout/", + "/next/ja/cookbook/websocket/", + "/next/ja/guide/binding/", + "/next/ja/guide/context/", + "/next/ja/guide/cookies/", + "/next/ja/guide/customization/", + "/next/ja/guide/error-handling/", + "/next/ja/guide/installation/", + "/next/ja/guide/ip-address/", + "/next/ja/guide/quickstart/", + "/next/ja/guide/request/", + "/next/ja/guide/response/", + "/next/ja/guide/routing/", + "/next/ja/guide/static-files/", + "/next/ja/guide/templates/", + "/next/ja/guide/testing/", + "/next/ja/middleware/", + "/next/ja/middleware/basic-auth/", + "/next/ja/middleware/body-dump/", + "/next/ja/middleware/body-limit/", + "/next/ja/middleware/casbin-auth/", + "/next/ja/middleware/context-timeout/", + "/next/ja/middleware/cors/", + "/next/ja/middleware/csrf/", + "/next/ja/middleware/decompress/", + "/next/ja/middleware/gzip/", + "/next/ja/middleware/jwt/", + "/next/ja/middleware/key-auth/", + "/next/ja/middleware/logger/", + "/next/ja/middleware/method-override/", + "/next/ja/middleware/open-telemetry/", + "/next/ja/middleware/prometheus/", + "/next/ja/middleware/proxy/", + "/next/ja/middleware/rate-limiter/", + "/next/ja/middleware/recover/", + "/next/ja/middleware/redirect/", + "/next/ja/middleware/request-id/", + "/next/ja/middleware/rewrite/", + "/next/ja/middleware/secure/", + "/next/ja/middleware/session/", + "/next/ja/middleware/static/", + "/next/ja/middleware/trailing-slash/", + "/next/middleware/", + "/next/middleware/basic-auth/", + "/next/middleware/body-dump/", + "/next/middleware/body-limit/", + "/next/middleware/casbin-auth/", + "/next/middleware/context-timeout/", + "/next/middleware/cors/", + "/next/middleware/csrf/", + "/next/middleware/decompress/", + "/next/middleware/gzip/", + "/next/middleware/jwt/", + "/next/middleware/key-auth/", + "/next/middleware/logger/", + "/next/middleware/method-override/", + "/next/middleware/open-telemetry/", + "/next/middleware/prometheus/", + "/next/middleware/proxy/", + "/next/middleware/rate-limiter/", + "/next/middleware/recover/", + "/next/middleware/redirect/", + "/next/middleware/request-id/", + "/next/middleware/rewrite/", + "/next/middleware/secure/", + "/next/middleware/session/", + "/next/middleware/static/", + "/next/middleware/trailing-slash/", + "/next/pt-br/", + "/next/pt-br/cookbook/auto-tls/", + "/next/pt-br/cookbook/cors/", + "/next/pt-br/cookbook/crud/", + "/next/pt-br/cookbook/embed-resources/", + "/next/pt-br/cookbook/file-download/", + "/next/pt-br/cookbook/file-upload/", + "/next/pt-br/cookbook/graceful-shutdown/", + "/next/pt-br/cookbook/hello-world/", + "/next/pt-br/cookbook/http2-server-push/", + "/next/pt-br/cookbook/http2/", + "/next/pt-br/cookbook/jsonp/", + "/next/pt-br/cookbook/jwt/", + "/next/pt-br/cookbook/load-balancing/", + "/next/pt-br/cookbook/middleware/", + "/next/pt-br/cookbook/reverse-proxy/", + "/next/pt-br/cookbook/sse/", + "/next/pt-br/cookbook/streaming-response/", + "/next/pt-br/cookbook/subdomain/", + "/next/pt-br/cookbook/timeout/", + "/next/pt-br/cookbook/websocket/", + "/next/pt-br/guide/binding/", + "/next/pt-br/guide/context/", + "/next/pt-br/guide/cookies/", + "/next/pt-br/guide/customization/", + "/next/pt-br/guide/error-handling/", + "/next/pt-br/guide/installation/", + "/next/pt-br/guide/ip-address/", + "/next/pt-br/guide/quickstart/", + "/next/pt-br/guide/request/", + "/next/pt-br/guide/response/", + "/next/pt-br/guide/routing/", + "/next/pt-br/guide/static-files/", + "/next/pt-br/guide/templates/", + "/next/pt-br/guide/testing/", + "/next/pt-br/middleware/", + "/next/pt-br/middleware/basic-auth/", + "/next/pt-br/middleware/body-dump/", + "/next/pt-br/middleware/body-limit/", + "/next/pt-br/middleware/casbin-auth/", + "/next/pt-br/middleware/context-timeout/", + "/next/pt-br/middleware/cors/", + "/next/pt-br/middleware/csrf/", + "/next/pt-br/middleware/decompress/", + "/next/pt-br/middleware/gzip/", + "/next/pt-br/middleware/jwt/", + "/next/pt-br/middleware/key-auth/", + "/next/pt-br/middleware/logger/", + "/next/pt-br/middleware/method-override/", + "/next/pt-br/middleware/open-telemetry/", + "/next/pt-br/middleware/prometheus/", + "/next/pt-br/middleware/proxy/", + "/next/pt-br/middleware/rate-limiter/", + "/next/pt-br/middleware/recover/", + "/next/pt-br/middleware/redirect/", + "/next/pt-br/middleware/request-id/", + "/next/pt-br/middleware/rewrite/", + "/next/pt-br/middleware/secure/", + "/next/pt-br/middleware/session/", + "/next/pt-br/middleware/static/", + "/next/pt-br/middleware/trailing-slash/", + "/next/zh-cn/", + "/next/zh-cn/cookbook/auto-tls/", + "/next/zh-cn/cookbook/cors/", + "/next/zh-cn/cookbook/crud/", + "/next/zh-cn/cookbook/embed-resources/", + "/next/zh-cn/cookbook/file-download/", + "/next/zh-cn/cookbook/file-upload/", + "/next/zh-cn/cookbook/graceful-shutdown/", + "/next/zh-cn/cookbook/hello-world/", + "/next/zh-cn/cookbook/http2-server-push/", + "/next/zh-cn/cookbook/http2/", + "/next/zh-cn/cookbook/jsonp/", + "/next/zh-cn/cookbook/jwt/", + "/next/zh-cn/cookbook/load-balancing/", + "/next/zh-cn/cookbook/middleware/", + "/next/zh-cn/cookbook/reverse-proxy/", + "/next/zh-cn/cookbook/sse/", + "/next/zh-cn/cookbook/streaming-response/", + "/next/zh-cn/cookbook/subdomain/", + "/next/zh-cn/cookbook/timeout/", + "/next/zh-cn/cookbook/websocket/", + "/next/zh-cn/guide/binding/", + "/next/zh-cn/guide/context/", + "/next/zh-cn/guide/cookies/", + "/next/zh-cn/guide/customization/", + "/next/zh-cn/guide/error-handling/", + "/next/zh-cn/guide/installation/", + "/next/zh-cn/guide/ip-address/", + "/next/zh-cn/guide/quickstart/", + "/next/zh-cn/guide/request/", + "/next/zh-cn/guide/response/", + "/next/zh-cn/guide/routing/", + "/next/zh-cn/guide/static-files/", + "/next/zh-cn/guide/templates/", + "/next/zh-cn/guide/testing/", + "/next/zh-cn/middleware/", + "/next/zh-cn/middleware/basic-auth/", + "/next/zh-cn/middleware/body-dump/", + "/next/zh-cn/middleware/body-limit/", + "/next/zh-cn/middleware/casbin-auth/", + "/next/zh-cn/middleware/context-timeout/", + "/next/zh-cn/middleware/cors/", + "/next/zh-cn/middleware/csrf/", + "/next/zh-cn/middleware/decompress/", + "/next/zh-cn/middleware/gzip/", + "/next/zh-cn/middleware/jwt/", + "/next/zh-cn/middleware/key-auth/", + "/next/zh-cn/middleware/logger/", + "/next/zh-cn/middleware/method-override/", + "/next/zh-cn/middleware/open-telemetry/", + "/next/zh-cn/middleware/prometheus/", + "/next/zh-cn/middleware/proxy/", + "/next/zh-cn/middleware/rate-limiter/", + "/next/zh-cn/middleware/recover/", + "/next/zh-cn/middleware/redirect/", + "/next/zh-cn/middleware/request-id/", + "/next/zh-cn/middleware/rewrite/", + "/next/zh-cn/middleware/secure/", + "/next/zh-cn/middleware/session/", + "/next/zh-cn/middleware/static/", + "/next/zh-cn/middleware/trailing-slash/", + "/pt-br/", + "/pt-br/cookbook/auto-tls/", + "/pt-br/cookbook/cors/", + "/pt-br/cookbook/crud/", + "/pt-br/cookbook/embed-resources/", + "/pt-br/cookbook/file-download/", + "/pt-br/cookbook/file-upload/", + "/pt-br/cookbook/graceful-shutdown/", + "/pt-br/cookbook/hello-world/", + "/pt-br/cookbook/http2-server-push/", + "/pt-br/cookbook/http2/", + "/pt-br/cookbook/jsonp/", + "/pt-br/cookbook/jwt/", + "/pt-br/cookbook/load-balancing/", + "/pt-br/cookbook/middleware/", + "/pt-br/cookbook/reverse-proxy/", + "/pt-br/cookbook/sse/", + "/pt-br/cookbook/streaming-response/", + "/pt-br/cookbook/subdomain/", + "/pt-br/cookbook/timeout/", + "/pt-br/cookbook/websocket/", + "/pt-br/guide/binding/", + "/pt-br/guide/context/", + "/pt-br/guide/cookies/", + "/pt-br/guide/customization/", + "/pt-br/guide/error-handling/", + "/pt-br/guide/installation/", + "/pt-br/guide/ip-address/", + "/pt-br/guide/quickstart/", + "/pt-br/guide/request/", + "/pt-br/guide/response/", + "/pt-br/guide/routing/", + "/pt-br/guide/static-files/", + "/pt-br/guide/templates/", + "/pt-br/guide/testing/", + "/pt-br/middleware/", + "/pt-br/middleware/basic-auth/", + "/pt-br/middleware/body-dump/", + "/pt-br/middleware/body-limit/", + "/pt-br/middleware/casbin-auth/", + "/pt-br/middleware/context-timeout/", + "/pt-br/middleware/cors/", + "/pt-br/middleware/csrf/", + "/pt-br/middleware/decompress/", + "/pt-br/middleware/gzip/", + "/pt-br/middleware/jwt/", + "/pt-br/middleware/key-auth/", + "/pt-br/middleware/logger/", + "/pt-br/middleware/method-override/", + "/pt-br/middleware/open-telemetry/", + "/pt-br/middleware/prometheus/", + "/pt-br/middleware/proxy/", + "/pt-br/middleware/rate-limiter/", + "/pt-br/middleware/recover/", + "/pt-br/middleware/redirect/", + "/pt-br/middleware/request-id/", + "/pt-br/middleware/rewrite/", + "/pt-br/middleware/secure/", + "/pt-br/middleware/session/", + "/pt-br/middleware/static/", + "/pt-br/middleware/trailing-slash/", + "/search/", + "/zh-cn/", + "/zh-cn/cookbook/auto-tls/", + "/zh-cn/cookbook/cors/", + "/zh-cn/cookbook/crud/", + "/zh-cn/cookbook/embed-resources/", + "/zh-cn/cookbook/file-download/", + "/zh-cn/cookbook/file-upload/", + "/zh-cn/cookbook/graceful-shutdown/", + "/zh-cn/cookbook/hello-world/", + "/zh-cn/cookbook/http2-server-push/", + "/zh-cn/cookbook/http2/", + "/zh-cn/cookbook/jsonp/", + "/zh-cn/cookbook/jwt/", + "/zh-cn/cookbook/load-balancing/", + "/zh-cn/cookbook/middleware/", + "/zh-cn/cookbook/reverse-proxy/", + "/zh-cn/cookbook/sse/", + "/zh-cn/cookbook/streaming-response/", + "/zh-cn/cookbook/subdomain/", + "/zh-cn/cookbook/timeout/", + "/zh-cn/cookbook/websocket/", + "/zh-cn/guide/binding/", + "/zh-cn/guide/context/", + "/zh-cn/guide/cookies/", + "/zh-cn/guide/customization/", + "/zh-cn/guide/error-handling/", + "/zh-cn/guide/installation/", + "/zh-cn/guide/ip-address/", + "/zh-cn/guide/quickstart/", + "/zh-cn/guide/request/", + "/zh-cn/guide/response/", + "/zh-cn/guide/routing/", + "/zh-cn/guide/static-files/", + "/zh-cn/guide/templates/", + "/zh-cn/guide/testing/", + "/zh-cn/middleware/", + "/zh-cn/middleware/basic-auth/", + "/zh-cn/middleware/body-dump/", + "/zh-cn/middleware/body-limit/", + "/zh-cn/middleware/casbin-auth/", + "/zh-cn/middleware/context-timeout/", + "/zh-cn/middleware/cors/", + "/zh-cn/middleware/csrf/", + "/zh-cn/middleware/decompress/", + "/zh-cn/middleware/gzip/", + "/zh-cn/middleware/jwt/", + "/zh-cn/middleware/key-auth/", + "/zh-cn/middleware/logger/", + "/zh-cn/middleware/method-override/", + "/zh-cn/middleware/open-telemetry/", + "/zh-cn/middleware/prometheus/", + "/zh-cn/middleware/proxy/", + "/zh-cn/middleware/rate-limiter/", + "/zh-cn/middleware/recover/", + "/zh-cn/middleware/redirect/", + "/zh-cn/middleware/request-id/", + "/zh-cn/middleware/rewrite/", + "/zh-cn/middleware/secure/", + "/zh-cn/middleware/session/", + "/zh-cn/middleware/static/", + "/zh-cn/middleware/trailing-slash/" + ] +} diff --git a/site/scripts/accept-source.mjs b/site/scripts/accept-source.mjs new file mode 100644 index 00000000..d3d4423b --- /dev/null +++ b/site/scripts/accept-source.mjs @@ -0,0 +1,27 @@ +import { readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const siteDir = fileURLToPath(new URL('..', import.meta.url)); +const channel = process.env.DOCS_CHANNEL === 'next' ? 'next' : 'stable'; +const baselineFile = channel === 'next' ? 'next-reference-baseline.json' : 'reference-baseline.json'; +const manifest = JSON.parse(readFileSync(join(siteDir, 'src/generated/config-fields.json'), 'utf8')); +const pin = JSON.parse(readFileSync(join(siteDir, channel === 'next' ? 'next-source.json' : 'echo-source.json'), 'utf8')); +if (!process.env.ECHO_SOURCE_DIR && manifest.revision !== pin.revision) { + throw new Error(`Generated Echo source is ${manifest.revision}; expected ${channel} revision ${pin.revision}. Run source:prepare for this channel first.`); +} +const configs = Object.fromEntries(manifest.configs.map((config) => [config.name, Object.fromEntries(config.fields.map((field) => [field.name, { type: field.type, deprecated: field.deprecated ?? false }]))])); +const functions = Object.fromEntries(manifest.functions.map((entry) => [entry.name, entry.signature])); +writeFileSync(join(siteDir, baselineFile), `${JSON.stringify({ configs, functions }, null, 2)}\n`); +const sources = JSON.parse(readFileSync(join(siteDir, 'external-sources.json'), 'utf8')); +const external = Object.fromEntries(Object.keys(sources).map((slug) => { + const data = JSON.parse(readFileSync(join(siteDir, `src/generated/${slug}.json`), 'utf8')); + return [slug, { + module: data.module, + version: data.revision, + configs: Object.fromEntries(data.configs.map((config) => [config.name, Object.fromEntries(config.fields.map((field) => [field.name, { type: field.type, deprecated: field.deprecated ?? false }]))])), + functions: Object.fromEntries(data.functions.map((entry) => [entry.name, entry.signature])), + }]; +})); +writeFileSync(join(siteDir, 'external-baseline.json'), `${JSON.stringify(external, null, 2)}\n`); +console.log(`Accepted ${channel} ${manifest.configs.length} Echo config types, ${manifest.functions.length} functions, and ${Object.keys(external).length} external modules for review`); diff --git a/site/scripts/accept-translations.mjs b/site/scripts/accept-translations.mjs new file mode 100644 index 00000000..ad30fec4 --- /dev/null +++ b/site/scripts/accept-translations.mjs @@ -0,0 +1,24 @@ +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { locales, pages, sections, siteDir } from './translation-sections.mjs'; + +const [locale, page] = process.argv.slice(2); +if (!locales.includes(locale) || !pages.includes(page)) { + console.error('Usage: npm run translations:accept -- '); + process.exit(1); +} +const path = join(siteDir, 'translation-baseline.json'); +const baseline = existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : {}; +const english = sections('', page); +const translated = sections(locale, page); +if (english.length !== translated.length) { + throw new Error(`${locale}/middleware/${page}: ${english.length} English sections and ${translated.length} translated sections`); +} +baseline[locale] ??= {}; +baseline[locale][page] = english.map((section, index) => ({ + heading: section.heading, + source: section.hash, + translation: translated[index].hash, +})); +writeFileSync(path, `${JSON.stringify(baseline, null, 2)}\n`); +console.log(`Recorded reviewed sections for ${locale}/middleware/${page}`); diff --git a/site/scripts/build-site.mjs b/site/scripts/build-site.mjs new file mode 100644 index 00000000..086a5009 --- /dev/null +++ b/site/scripts/build-site.mjs @@ -0,0 +1,41 @@ +import { execFileSync } from 'node:child_process'; +import { cpSync, existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const siteDir = fileURLToPath(new URL('..', import.meta.url)); +const proposedEcho = process.env.ECHO_SOURCE_DIR || ''; + +function run(args, channel, sourceDir = '') { + execFileSync('npm', args, { + cwd: siteDir, + env: { ...process.env, DOCS_CHANNEL: channel, ECHO_SOURCE_DIR: sourceDir }, + stdio: 'inherit', + }); +} + +run(['run', 'source:check'], 'stable'); +run(['run', 'translations:status'], 'stable'); +run(['run', 'astro', '--', 'build'], 'stable'); +run(['run', 'source:check'], 'next', proposedEcho); +run(['run', 'astro', '--', 'build'], 'next', proposedEcho); + +const nextDist = join(siteDir, 'dist-next'); +if (!existsSync(join(nextDist, 'index.html'))) throw new Error(`Next build missing: ${nextDist}`); +function rewriteNextLinks(dir) { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const path = join(dir, entry.name); + if (entry.isDirectory()) { rewriteNextLinks(path); continue; } + if (!path.endsWith('.html')) continue; + const html = readFileSync(path, 'utf8'); + const updated = html.replace(/]*>/g, (tag) => { + if (tag.includes('data-version-switch')) return tag; + return tag.replace(/\bhref="\/((?:(?:es|ja|pt-br|zh-cn)\/)?(?:guide|middleware|cookbook)\/[^\"]*)"/g, 'href="/next/$1"'); + }); + if (updated !== html) writeFileSync(path, updated); + } +} +rewriteNextLinks(nextDist); +cpSync(nextDist, join(siteDir, 'dist', 'next'), { recursive: true }); +run(['run', 'site:check'], 'stable'); +run(['run', 'performance:check'], 'stable'); diff --git a/site/scripts/check-performance.mjs b/site/scripts/check-performance.mjs new file mode 100644 index 00000000..d9cbda9a --- /dev/null +++ b/site/scripts/check-performance.mjs @@ -0,0 +1,52 @@ +import { readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { gzipSync } from 'node:zlib'; + +const siteDir = fileURLToPath(new URL('..', import.meta.url)); +const distDir = join(siteDir, 'dist'); +const baselineFile = join(siteDir, 'performance-baseline.json'); +const routes = ['/', '/middleware/cors/', '/next/', '/next/middleware/cors/']; + +function bytes(path) { + const data = readFileSync(path); + return { raw: data.length, gzip: gzipSync(data).length }; +} + +function metrics(route) { + const htmlFile = join(distDir, route, 'index.html'); + const html = readFileSync(htmlFile, 'utf8'); + const assetPaths = [...html.matchAll(/(?:src|href)="(\/(?:next\/)?_astro\/[^"?#]+)"/g)] + .map((match) => match[1]); + const assets = [...new Set(assetPaths)].filter((path) => /\.(?:js|css)$/.test(path)); + const assetBytes = assets.reduce((sum, path) => { + const size = bytes(join(distDir, path)); + return { raw: sum.raw + size.raw, gzip: sum.gzip + size.gzip }; + }, { raw: 0, gzip: 0 }); + const domains = [...new Set([...html.matchAll(/(?:src|href)="(https?:\/\/[^"?#]+)"/g)] + .map((match) => new URL(match[1]).hostname).filter((host) => host !== 'echo.labstack.com'))].sort(); + return { html: bytes(htmlFile), firstLoadAssets: assetBytes, externalDomains: domains }; +} + +const current = Object.fromEntries(routes.map((route) => [route, metrics(route)])); +if (process.argv.includes('--accept')) { + writeFileSync(baselineFile, `${JSON.stringify(current, null, 2)}\n`); + console.log('Accepted stable and next HTML/asset sizes for review'); + process.exit(0); +} +const baseline = JSON.parse(readFileSync(baselineFile, 'utf8')); +const problems = []; +for (const route of routes) { + const before = baseline[route]; + const after = current[route]; + for (const [part, metric] of [['html', 'gzip'], ['firstLoadAssets', 'gzip']]) { + if (after[part][metric] > before[part][metric] * 1.15 + 1024) { + problems.push(`${route} ${part} gzip grew from ${before[part][metric]} to ${after[part][metric]} bytes`); + } + } + for (const domain of after.externalDomains) { + if (!before.externalDomains.includes(domain)) problems.push(`${route} added external origin ${domain}`); + } +} +if (problems.length) throw new Error(`Review performance baseline changes:\n${problems.join('\n')}`); +console.log('Stable and next HTML/asset sizes are within the recorded baseline'); diff --git a/site/scripts/check-site.mjs b/site/scripts/check-site.mjs new file mode 100644 index 00000000..cf5b1d25 --- /dev/null +++ b/site/scripts/check-site.mjs @@ -0,0 +1,102 @@ +import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'; +import { extname, join, relative, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const siteDir = fileURLToPath(new URL('..', import.meta.url)); +const distDir = join(siteDir, 'dist'); +const baselineFile = join(siteDir, 'route-baseline.json'); +const accept = process.argv.includes('--accept'); +const origin = 'https://echo.labstack.com'; + +function walk(dir) { + return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { + const path = join(dir, entry.name); + return entry.isDirectory() ? walk(path) : [path]; + }); +} + +const files = walk(distDir); +const htmlFiles = files.filter((file) => file.endsWith('.html')); +const routes = htmlFiles.map((file) => { + const path = `/${relative(distDir, file).replaceAll('\\', '/')}`; + return path.endsWith('/index.html') ? path.slice(0, -10) : path; +}).sort(); + +if (accept) { + writeFileSync(baselineFile, `${JSON.stringify({ routes }, null, 2)}\n`); + console.log(`Accepted ${routes.length} routes for review`); + process.exit(0); +} + +const expected = JSON.parse(readFileSync(baselineFile, 'utf8')).routes; +const problems = []; +for (const route of expected.filter((item) => !routes.includes(item))) problems.push(`Removed route: ${route}`); +for (const route of routes.filter((item) => !expected.includes(item))) problems.push(`New route needs baseline review: ${route}`); +const ids = new Map(); +function idsIn(file) { + if (!ids.has(file)) { + const html = readFileSync(file, 'utf8'); + ids.set(file, new Set([...html.matchAll(/\bid="([^"]+)"/g)].map((match) => match[1]))); + } + return ids.get(file); +} + +let checked = 0; +for (const file of htmlFiles) { + const html = readFileSync(file, 'utf8'); + const markup = html.replace(/]*>[\s\S]*?<\/script>/gi, '').replace(/]*>[\s\S]*?<\/pre>/gi, ''); + const route = routes.find((item) => file === join(distDir, item, 'index.html') || file === join(distDir, item)); + const redirect = route.startsWith('/docs/') || route === '/docs/' || route === '/search/'; + if (route === '/' && (!html.includes('widget.kapa.ai') || !html.includes('id="echo-ask-ai"'))) { + problems.push('Release docs lost Ask AI'); + } + if (route.startsWith('/next/') && (html.includes('widget.kapa.ai') || html.includes('id="echo-ask-ai"'))) { + problems.push(`${route}: release-only Ask AI appears on next docs`); + } + if (!redirect && !/\]*\blang="[^"]+"/.test(html)) problems.push(`${route}: missing document language`); + if (route.endsWith('/404.html')) continue; + for (const [tag] of markup.matchAll(/]*>/g)) { + if (!/\balt(?:\s|=)/.test(tag)) problems.push(`${route}: image missing alt text`); + } + for (const [, names] of markup.matchAll(/\baria-labelledby="([^"]+)"/g)) { + for (const id of names.split(/\s+/)) { + if (id && !idsIn(file).has(id)) problems.push(`${route}: aria-labelledby references missing #${id}`); + } + } + if (route.startsWith('/next/')) { + for (const [tag] of markup.matchAll(/]*>/g)) { + if (tag.includes('data-version-switch')) continue; + const href = tag.match(/\bhref="(\/[^"]+)"/)?.[1]; + if (href && /^\/(?:es\/|ja\/|pt-br\/|zh-cn\/)?(?:guide|middleware|cookbook)\//.test(href)) { + problems.push(`${route}: next content links to stable route ${href}`); + } + } + } + for (const match of markup.matchAll(/\b(?:href|src)="([^"]+)"/g)) { + const value = match[1].replaceAll('&', '&'); + if (!value || value.startsWith('data:') || value.startsWith('mailto:') || value.startsWith('javascript:')) continue; + let target; + try { target = new URL(value, `${origin}${route}`); } catch { continue; } + if (target.origin !== origin) continue; + let pathname; + try { pathname = decodeURIComponent(target.pathname); } catch { problems.push(`${route}: malformed URL ${value}`); continue; } + const resolved = resolve(distDir, `.${pathname}`); + if (!resolved.startsWith(`${distDir}/`) && resolved !== distDir) { problems.push(`${route}: path escapes site ${value}`); continue; } + const targetFile = extname(resolved) ? resolved : join(resolved, 'index.html'); + if (!existsSync(targetFile) || !statSync(targetFile).isFile()) { problems.push(`${route}: missing ${value}`); continue; } + if (target.hash && targetFile.endsWith('.html') && !target.hash.startsWith('#:~:text=')) { + const fragment = decodeURIComponent(target.hash.slice(1)); + if (fragment && !idsIn(targetFile).has(fragment)) problems.push(`${route}: missing anchor ${value}`); + } + checked++; + } +} + +for (const locale of ['es', 'ja', 'pt-br', 'zh-cn']) { + if (!routes.includes(`/${locale}/middleware/`)) problems.push(`Missing ${locale} middleware task page`); +} +for (const path of ['sitemap-index.xml', 'pagefind/pagefind.js']) { + if (!existsSync(join(distDir, path))) problems.push(`Missing ${path}`); +} +if (problems.length) throw new Error(`${problems.length} site check failure(s):\n${problems.join('\n')}`); +console.log(`Checked ${routes.length} routes and ${checked} internal links/assets; all resolve`); diff --git a/site/scripts/check-source.mjs b/site/scripts/check-source.mjs new file mode 100644 index 00000000..1c14d2e0 --- /dev/null +++ b/site/scripts/check-source.mjs @@ -0,0 +1,93 @@ +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const siteDir = fileURLToPath(new URL('..', import.meta.url)); +const channel = process.env.DOCS_CHANNEL === 'next' ? 'next' : 'stable'; +const baselineFile = channel === 'next' ? 'next-reference-baseline.json' : 'reference-baseline.json'; +const manifest = JSON.parse(readFileSync(join(siteDir, 'src/generated/config-fields.json'), 'utf8')); +const pin = JSON.parse(readFileSync(join(siteDir, channel === 'next' ? 'next-source.json' : 'echo-source.json'), 'utf8')); +if (!process.env.ECHO_SOURCE_DIR && manifest.revision !== pin.revision) { + throw new Error(`Generated Echo source is ${manifest.revision}; expected ${channel} revision ${pin.revision}. Run source:prepare for this channel first.`); +} +const baseline = JSON.parse(readFileSync(join(siteDir, baselineFile), 'utf8')); +const pages = JSON.parse(readFileSync(join(siteDir, 'reference-pages.json'), 'utf8')); +const externalSources = JSON.parse(readFileSync(join(siteDir, 'external-sources.json'), 'utf8')); +const externalBaseline = JSON.parse(readFileSync(join(siteDir, 'external-baseline.json'), 'utf8')); + +function fieldsByConfig(configs) { + return Object.fromEntries(configs.map((config) => [config.name, Object.fromEntries(config.fields.map((field) => [field.name, { type: field.type, deprecated: field.deprecated ?? false }]))])); +} + +const actual = fieldsByConfig(manifest.configs); +const differences = []; +for (const name of new Set([...Object.keys(baseline.configs), ...Object.keys(actual)])) { + const before = baseline.configs[name] || {}; + const after = actual[name] || {}; + for (const field of new Set([...Object.keys(before), ...Object.keys(after)])) { + if (JSON.stringify(before[field]) !== JSON.stringify(after[field])) { + differences.push(`${name}.${field}: ${JSON.stringify(before[field] ?? null)} -> ${JSON.stringify(after[field] ?? null)}`); + } + } +} +const functions = Object.fromEntries(manifest.functions.map((entry) => [entry.name, entry.signature])); +for (const name of new Set([...Object.keys(baseline.functions), ...Object.keys(functions)])) { + if (baseline.functions[name] !== functions[name]) { + differences.push(`${name}: ${JSON.stringify(baseline.functions[name] ?? null)} -> ${JSON.stringify(functions[name] ?? null)}`); + } +} +for (const [slug, source] of Object.entries(externalSources)) { + const external = JSON.parse(readFileSync(join(siteDir, `src/generated/${slug}.json`), 'utf8')); + const before = externalBaseline[slug]; + if (!before || external.module !== source.module || external.revision !== source.version || before.version !== source.version) { + differences.push(`${slug}: source module/version differs from reviewed baseline`); + continue; + } + const fields = fieldsByConfig(external.configs); + for (const config of new Set([...Object.keys(before.configs), ...Object.keys(fields)])) { + const oldFields = before.configs[config] || {}; + const newFields = fields[config] || {}; + for (const field of new Set([...Object.keys(oldFields), ...Object.keys(newFields)])) { + if (JSON.stringify(oldFields[field]) !== JSON.stringify(newFields[field])) differences.push(`${slug}.${config}.${field}: ${JSON.stringify(oldFields[field] ?? null)} -> ${JSON.stringify(newFields[field] ?? null)}`); + } + } + const api = Object.fromEntries(external.functions.map((entry) => [entry.name, entry.signature])); + for (const name of new Set([...Object.keys(before.functions), ...Object.keys(api)])) { + if (before.functions[name] !== api[name]) differences.push(`${slug}.${name}: ${JSON.stringify(before.functions[name] ?? null)} -> ${JSON.stringify(api[name] ?? null)}`); + } +} +if (differences.length) { + throw new Error(`${channel} middleware API changed. Review the affected pages, then update ${baselineFile}:\n${differences.join('\n')}`); +} + +const documented = Object.values(pages).flat(); +for (const name of manifest.configs.map((entry) => entry.name)) { + if (documented.filter((entry) => entry === name).length !== 1) { + throw new Error(`${name} must be mapped to exactly one middleware page`); + } +} +for (const locale of ['', 'es/', 'ja/', 'pt-br/', 'zh-cn/']) { + for (const [page, configs] of Object.entries(pages)) { + const file = join(siteDir, 'src/content/docs', locale, 'middleware', `${page}.mdx`); + const content = readFileSync(file, 'utf8'); + for (const config of configs) { + if (!content.includes(` config.name === 'RequestLoggerConfig') || !manifest.configs.some((config) => config.name === 'StaticConfig')) { + throw new Error('Echo source extractor returned an incomplete or mismatched manifest'); +} +run('go', ['test', './...'], referenceDir, goEnvironment); +mkdirSync(generatedDir, { recursive: true }); +writeFileSync(join(generatedDir, 'config-fields.json'), `${JSON.stringify(manifest, null, 2)}\n`); +for (const [slug, source] of Object.entries(externalSources)) { + const download = JSON.parse(run('go', ['mod', 'download', '-json', `${source.module}@${source.version}`], repoDir, { GOWORK: 'off' })); + if (download.Error || !download.Dir) throw new Error(`${source.module}@${source.version}: ${download.Error || 'source directory missing'}`); + const extracted = JSON.parse(run('go', [ + 'run', './cmd/config-fields', '-root', download.Dir, '-revision', source.version, + '-module', source.module, '-directory', '.', '-package', source.package, + ], referenceDir, goEnvironment)); + if (extracted.module !== source.module || extracted.revision !== source.version || !extracted.configs.length || !extracted.functions.length) { + throw new Error(`${source.module}@${source.version}: incomplete API manifest`); + } + writeFileSync(join(generatedDir, `${slug}.json`), `${JSON.stringify(extracted, null, 2)}\n`); +} +copyFileSync(join(referenceDir, 'request-logger', 'main.go'), join(generatedDir, 'request-logger.go.txt')); +copyFileSync(join(referenceDir, 'static', 'main.go'), join(generatedDir, 'static.go.txt')); +console.log(`Prepared Echo source ${revision}`); diff --git a/site/scripts/translation-sections.mjs b/site/scripts/translation-sections.mjs new file mode 100644 index 00000000..a2bcdc50 --- /dev/null +++ b/site/scripts/translation-sections.mjs @@ -0,0 +1,31 @@ +import { createHash } from 'node:crypto'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +export const siteDir = fileURLToPath(new URL('..', import.meta.url)); +export const locales = ['es', 'ja', 'pt-br', 'zh-cn']; +export const pages = ['logger', 'static', 'index']; + +export function sections(locale, page) { + const extension = page === 'index' ? 'md' : 'mdx'; + const path = join(siteDir, 'src/content/docs', locale ? `${locale}/middleware` : 'middleware', `${page}.${extension}`); + const content = readFileSync(path, 'utf8'); + const blocks = []; + let heading = 'Introduction'; + let lines = []; + for (const line of content.split('\n')) { + if (/^#{2,3} /.test(line)) { + blocks.push({ heading, text: lines.join('\n').trim() }); + heading = line.replace(/^#{2,3} /, ''); + lines = []; + } else { + lines.push(line); + } + } + blocks.push({ heading, text: lines.join('\n').trim() }); + return blocks.map(({ heading, text }) => ({ + heading, + hash: createHash('sha256').update(`${heading}\n${text}`).digest('hex'), + })); +} diff --git a/site/scripts/translation-status.mjs b/site/scripts/translation-status.mjs new file mode 100644 index 00000000..bfb7c365 --- /dev/null +++ b/site/scripts/translation-status.mjs @@ -0,0 +1,34 @@ +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { locales, pages, sections, siteDir } from './translation-sections.mjs'; + +const baseline = JSON.parse(readFileSync(join(siteDir, 'translation-baseline.json'), 'utf8')); +let stale = 0; +for (const locale of locales) { + for (const page of pages) { + const english = sections('', page); + const translated = sections(locale, page); + const accepted = baseline[locale]?.[page]; + if (!Array.isArray(accepted)) { + console.log(`${locale}/middleware/${page}: needs review (no section baseline)`); + stale++; + continue; + } + const count = Math.max(english.length, translated.length, accepted.length); + for (let index = 0; index < count; index++) { + const source = english[index]; + const translation = translated[index]; + const recorded = accepted[index]; + const changes = []; + if (!source || !translation || !recorded) changes.push('section count changed'); + if (source && recorded && source.hash !== recorded.source) changes.push('English changed'); + if (translation && recorded && translation.hash !== recorded.translation) changes.push('translation changed'); + if (changes.length) { + console.log(`${locale}/middleware/${page} §${index + 1} ${source?.heading ?? recorded?.heading ?? 'missing'}: ${changes.join(', ')}`); + stale++; + } + } + } +} +console.log(`${stale} section(s) need review; matching hashes track edits, not translation quality`); +if (stale) process.exitCode = 1; diff --git a/site/src/components/ConfigReference.astro b/site/src/components/ConfigReference.astro new file mode 100644 index 00000000..0b2bec2f --- /dev/null +++ b/site/src/components/ConfigReference.astro @@ -0,0 +1,79 @@ +--- +import echoManifest from '../generated/config-fields.json'; +import jwtManifest from '../generated/jwt.json'; +import prometheusManifest from '../generated/prometheus.json'; +import telemetryManifest from '../generated/open-telemetry.json'; +import externalSources from '../../external-sources.json'; + +interface Props { + name: string; + locale?: 'en' | 'es' | 'ja' | 'pt-br' | 'zh-cn'; + source?: 'echo' | 'jwt' | 'prometheus' | 'open-telemetry'; +} + +const { name, locale = 'en', source = 'echo' } = Astro.props; +const manifest = { echo: echoManifest, jwt: jwtManifest, prometheus: prometheusManifest, 'open-telemetry': telemetryManifest }[source]; +const repository = source === 'echo' ? 'https://github.com/labstack/echo' : externalSources[source].repository; +const config = manifest.configs.find((entry) => entry.name === name); +if (!config) throw new Error(`Missing ${source} source reference for ${name}`); +const stem = name.replace(/Config$/, ''); +const functions = manifest.functions.filter((entry) => + entry.signature.includes(name) || entry.name === stem || entry.name === `New${stem}` || + (name === 'RedirectConfig' && /Redirect$/.test(entry.name)) || + (source === 'jwt' && entry.name === 'JWT') || + (source === 'open-telemetry' && name === 'Config' && entry.name === 'NewMiddleware') +); +const labels = { + en: { field: 'Field', type: 'Type', description: 'Description', source: 'Source', deprecated: 'Deprecated', caption: 'Fields from package source', functions: 'Functions from package source', note: '' }, + es: { field: 'Campo', type: 'Tipo', description: 'Descripción', source: 'Fuente', deprecated: 'Obsoleto', caption: 'Campos del código fuente', functions: 'Funciones del código fuente', note: 'Las descripciones de los campos se generan a partir de comentarios del código fuente en inglés.' }, + ja: { field: 'フィールド', type: '型', description: '説明', source: 'ソース', deprecated: '非推奨', caption: 'パッケージのソースコードのフィールド', functions: 'パッケージのソースコードの関数', note: 'フィールドの説明は英語のソースコードのコメントから生成されています。' }, + 'pt-br': { field: 'Campo', type: 'Tipo', description: 'Descrição', source: 'Código', deprecated: 'Obsoleto', caption: 'Campos do código-fonte', functions: 'Funções do código-fonte', note: 'As descrições dos campos são geradas dos comentários do código-fonte em inglês.' }, + 'zh-cn': { field: '字段', type: '类型', description: '说明', source: '源码', deprecated: '已弃用', caption: '包源码中的字段', functions: '包源码中的函数', note: '字段说明由英文源码注释生成。' }, +}[locale]; +const sourceUrl = (file: string, line: number) => `${repository}/blob/${manifest.revision}/${file}#L${line}`; +--- + +
+

{config.name} · {manifest.module}@{source === 'echo' ? manifest.revision.slice(0, 7) : manifest.revision}

+ {labels.note &&

{labels.note}

} +
+ + + + + {config.fields.map((field) => + + + + + )} + +
{labels.caption}
{labels.field}{labels.type}{labels.description}{labels.source}
{field.name}{field.deprecated && {labels.deprecated}}{field.type}{field.doc || '—'}L{field.line}
+
+ {functions.length > 0 &&
+ + + + + {functions.map((entry) => + + + )} + +
{labels.functions}
{labels.type}{labels.source}
{entry.signature}L{entry.line}
+
} +
+ + diff --git a/site/src/components/HomeHero.astro b/site/src/components/HomeHero.astro index abffe3a9..a78a3e00 100644 --- a/site/src/components/HomeHero.astro +++ b/site/src/components/HomeHero.astro @@ -2,12 +2,14 @@ // Living Terminal hero: code window + a terminal pane that types `curl` // and streams Echo's JSON response. Animation is client-side, with a // static final-state fallback for reduced-motion / no-JS. -import { starsLabel, echoVersion } from '../data/github.ts'; +import { starsLabel } from '../data/github.ts'; +import stable from '../../echo-source.json'; +const next = process.env.DOCS_CHANNEL === 'next'; ---
- Echo {echoVersion} — now released + {next ? 'Echo next — preview documentation' : `Echo ${stable.release} — release documentation`}

Build fast Go APIs.
Without the bloat.

A high-performance, minimalist Go web framework — a zero-allocation diff --git a/site/src/components/Search.astro b/site/src/components/Search.astro index 729344a6..7741eeda 100644 --- a/site/src/components/Search.astro +++ b/site/src/components/Search.astro @@ -1,35 +1,46 @@ --- import Default from '@astrojs/starlight/components/Search.astro'; +const isNext = process.env.DOCS_CHANNEL === 'next'; ---

- {/* Opens the kapa.ai modal — wired via data-modal-override-open-selector in astro.config. */} - + }
- + Echo

The following static files are served via HTTP/2 server push

  • /app.css
  • diff --git a/site/src/content/docs/es/cookbook/http2-server-push.md b/site/src/content/docs/es/cookbook/http2-server-push.md index fd71df5f..c45c60a7 100644 --- a/site/src/content/docs/es/cookbook/http2-server-push.md +++ b/site/src/content/docs/es/cookbook/http2-server-push.md @@ -77,7 +77,7 @@ if err := sc.StartTLS(context.Background(), e, "cert.pem", "key.pem"); err != ni - + Echo

    The following static files are served via HTTP/2 server push

    • /app.css
    • diff --git a/site/src/content/docs/es/cookbook/http2.md b/site/src/content/docs/es/cookbook/http2.md index 2ba77367..4566bfb3 100644 --- a/site/src/content/docs/es/cookbook/http2.md +++ b/site/src/content/docs/es/cookbook/http2.md @@ -9,6 +9,8 @@ HTTP/2 mejora la latencia mediante multiplexing de requests, compresión de head server push. El servidor HTTP de Go negocia HTTP/2 automáticamente sobre TLS, por lo que servir HTTP/2 con Echo consiste en iniciar el servidor con un certificado. + + ## 1. Generar un certificado TLS X.509 autofirmado Ejecuta el siguiente comando para generar `cert.pem` y `key.pem`: diff --git a/site/src/content/docs/es/guide/binding.md b/site/src/content/docs/es/guide/binding.md index 135b49ed..0e338050 100644 --- a/site/src/content/docs/es/guide/binding.md +++ b/site/src/content/docs/es/guide/binding.md @@ -148,6 +148,8 @@ Cada tipo soportado ofrece métodos `Type(...)`, `MustType(...)`, `Types(...)` ( `MustTypes(...)`, por ejemplo `Int64`, `MustInt64`, `Int64s`. Usa `BindWithDelimiter("id", &dest, ",")` para separar valores unidos por comas. + + ## Binder personalizado Registra un binder personalizado mediante `Echo#Binder`: diff --git a/site/src/content/docs/es/guide/customization.md b/site/src/content/docs/es/guide/customization.md index e9759c52..2762f6f8 100644 --- a/site/src/content/docs/es/guide/customization.md +++ b/site/src/content/docs/es/guide/customization.md @@ -43,6 +43,8 @@ en [json.go](https://github.com/labstack/echo/blob/master/json.go). [Aprende más](/es/guide/templates/) + + ## Handler de errores HTTP `Echo#HTTPErrorHandler` registra un handler de errores HTTP personalizado. diff --git a/site/src/content/docs/es/guide/request.md b/site/src/content/docs/es/guide/request.md index 2e17ed2d..21264784 100644 --- a/site/src/content/docs/es/guide/request.md +++ b/site/src/content/docs/es/guide/request.md @@ -106,6 +106,8 @@ curl http://localhost:1323/users/123 Echo también puede vincular datos de request a structs y variables nativas de Go. Consulta [Binding](/es/guide/binding/). + + ## Validar datos Echo no incluye validación de datos integrada. Puedes registrar un validator personalizado mediante diff --git a/site/src/content/docs/es/middleware/basic-auth.md b/site/src/content/docs/es/middleware/basic-auth.mdx similarity index 67% rename from site/src/content/docs/es/middleware/basic-auth.md rename to site/src/content/docs/es/middleware/basic-auth.mdx index 0ce5538e..be22c7d2 100644 --- a/site/src/content/docs/es/middleware/basic-auth.md +++ b/site/src/content/docs/es/middleware/basic-auth.mdx @@ -5,6 +5,8 @@ sidebar: order: 1 --- +import ConfigReference from '../../../../components/ConfigReference.astro'; + El middleware Basic Auth proporciona autenticación HTTP basic. - Para credenciales válidas llama al siguiente handler. @@ -37,27 +39,7 @@ e.Use(middleware.BasicAuthWithConfig(middleware.BasicAuthConfig{})) ## Configuración -```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 -} -``` + `Validator` tiene esta firma: diff --git a/site/src/content/docs/es/middleware/body-dump.md b/site/src/content/docs/es/middleware/body-dump.mdx similarity index 59% rename from site/src/content/docs/es/middleware/body-dump.md rename to site/src/content/docs/es/middleware/body-dump.mdx index 5de4d32e..64c92cf9 100644 --- a/site/src/content/docs/es/middleware/body-dump.md +++ b/site/src/content/docs/es/middleware/body-dump.mdx @@ -5,6 +5,8 @@ sidebar: order: 2 --- +import ConfigReference from '../../../../components/ConfigReference.astro'; + El middleware Body Dump captura los payloads de request y response y los pasa a un handler registrado. Generalmente se usa para debugging o logging. @@ -37,28 +39,7 @@ e.Use(middleware.BodyDumpWithConfig(middleware.BodyDumpConfig{})) ## Configuración -```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 -} -``` + `Handler` tiene esta firma: diff --git a/site/src/content/docs/es/middleware/body-limit.md b/site/src/content/docs/es/middleware/body-limit.mdx similarity index 83% rename from site/src/content/docs/es/middleware/body-limit.md rename to site/src/content/docs/es/middleware/body-limit.mdx index 2a19b210..8364609d 100644 --- a/site/src/content/docs/es/middleware/body-limit.md +++ b/site/src/content/docs/es/middleware/body-limit.mdx @@ -5,6 +5,8 @@ sidebar: order: 3 --- +import ConfigReference from '../../../../components/ConfigReference.astro'; + El middleware Body Limit establece el tamaño máximo permitido para un body de request. Si el tamaño supera el límite configurado, envía una response `413 Request Entity Too Large`. @@ -33,15 +35,7 @@ e.Use(middleware.BodyLimitWithConfig(middleware.BodyLimitConfig{})) ## Configuración -```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 -} -``` + ### Configuración por defecto diff --git a/site/src/content/docs/es/middleware/context-timeout.md b/site/src/content/docs/es/middleware/context-timeout.mdx similarity index 73% rename from site/src/content/docs/es/middleware/context-timeout.md rename to site/src/content/docs/es/middleware/context-timeout.mdx index e6754080..b0a5ef2c 100644 --- a/site/src/content/docs/es/middleware/context-timeout.md +++ b/site/src/content/docs/es/middleware/context-timeout.mdx @@ -5,6 +5,8 @@ sidebar: order: 5 --- +import ConfigReference from '../../../../components/ConfigReference.astro'; + El middleware Context Timeout aplica un timeout al contexto del request dentro de un periodo predefinido, para que los métodos conscientes del contexto puedan retornar antes cuando se supera el deadline. @@ -30,18 +32,7 @@ e.Use(middleware.ContextTimeoutWithConfig(middleware.ContextTimeoutConfig{ ## Configuración -```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 -} -``` + ### Configuración por defecto diff --git a/site/src/content/docs/es/middleware/cors.md b/site/src/content/docs/es/middleware/cors.md deleted file mode 100644 index 139e5514..00000000 --- a/site/src/content/docs/es/middleware/cors.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -title: CORS -description: Middleware Cross-Origin Resource Sharing para control de acceso seguro entre dominios. -sidebar: - order: 6 ---- - -El middleware CORS implementa la especificación [CORS](https://fetch.spec.whatwg.org/#http-cors-protocol). -CORS da a los servidores web controles de acceso entre dominios, lo que permite transferencias de datos seguras entre dominios. - -Todo el middleware principal reside en el paquete `middleware`: - -```go -import "github.com/labstack/echo/v5/middleware" -``` - -## Uso - -```go -e.Use(middleware.CORS("https://example.com", "https://subdomain.example.com")) -``` - -## Configuración personalizada - -```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}, -})) -``` - -## Configuración - -```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 -} -``` - -### Configuración por defecto - -```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}, -} -``` - -## Seguridad - -Un origin con wildcard (`AllowOrigins: []string{"*"}`) combinado con `AllowCredentials: true` -es peligroso: reflejaría el `Origin` de **cualquier** petición en -`Access-Control-Allow-Origin`, permitiendo que una página de cualquier sitio haga peticiones -cross-origin con credenciales a tu API (consulta [Exploiting CORS misconfigurations](https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html)). - -Echo rechaza esta combinación en lugar de construir un middleware inseguro: `CORS` y -`CORSWithConfig` hacen **panic**, y `CORSConfig.ToMiddleware()` devuelve un error. Para permitir -peticiones con credenciales, enumera explícitamente los orígenes de confianza: - -```go -e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ - AllowOrigins: []string{"https://example.com"}, - AllowCredentials: true, -})) -``` - -Para validación dinámica de origin, usa `UnsafeAllowOriginFunc` y valida cada origin con -cuidado: los atacantes pueden registrar nombres de (sub)dominio falsos u hostiles. diff --git a/site/src/content/docs/es/middleware/cors.mdx b/site/src/content/docs/es/middleware/cors.mdx new file mode 100644 index 00000000..31250f88 --- /dev/null +++ b/site/src/content/docs/es/middleware/cors.mdx @@ -0,0 +1,68 @@ +--- +title: CORS +description: Middleware Cross-Origin Resource Sharing para control de acceso seguro entre dominios. +sidebar: + order: 6 +--- + +import ConfigReference from '../../../../components/ConfigReference.astro'; + +El middleware CORS implementa la especificación [CORS](https://fetch.spec.whatwg.org/#http-cors-protocol). +CORS da a los servidores web controles de acceso entre dominios, lo que permite transferencias de datos seguras entre dominios. + +Todo el middleware principal reside en el paquete `middleware`: + +```go +import "github.com/labstack/echo/v5/middleware" +``` + +## Uso + +```go +e.Use(middleware.CORS("https://example.com", "https://subdomain.example.com")) +``` + +## Configuración personalizada + +```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}, +})) +``` + +## Configuración + + + +### Configuración por defecto + +```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}, +} +``` + +## Seguridad + +Un origin con wildcard (`AllowOrigins: []string{"*"}`) combinado con `AllowCredentials: true` +es peligroso: reflejaría el `Origin` de **cualquier** petición en +`Access-Control-Allow-Origin`, permitiendo que una página de cualquier sitio haga peticiones +cross-origin con credenciales a tu API (consulta [Exploiting CORS misconfigurations](https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html)). + +Echo rechaza esta combinación en lugar de construir un middleware inseguro: `CORS` y +`CORSWithConfig` hacen **panic**, y `CORSConfig.ToMiddleware()` devuelve un error. Para permitir +peticiones con credenciales, enumera explícitamente los orígenes de confianza: + +```go +e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ + AllowOrigins: []string{"https://example.com"}, + AllowCredentials: true, +})) +``` + +Para validación dinámica de origin, usa `UnsafeAllowOriginFunc` y valida cada origin con +cuidado: los atacantes pueden registrar nombres de (sub)dominio falsos u hostiles. diff --git a/site/src/content/docs/es/middleware/csrf.md b/site/src/content/docs/es/middleware/csrf.mdx similarity index 61% rename from site/src/content/docs/es/middleware/csrf.md rename to site/src/content/docs/es/middleware/csrf.mdx index 787796dc..1b4ae669 100644 --- a/site/src/content/docs/es/middleware/csrf.md +++ b/site/src/content/docs/es/middleware/csrf.mdx @@ -5,6 +5,8 @@ sidebar: order: 7 --- +import ConfigReference from '../../../../components/ConfigReference.astro'; + Cross-Site Request Forgery (CSRF, a veces pronunciado "sea-surf", o XSRF) es un tipo de exploit malicioso en el que se transmiten comandos no autorizados desde un usuario en el que un sitio web confía. @@ -93,75 +95,7 @@ middleware.CSRFWithConfig(middleware.CSRFConfig{ ## Configuración -```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 -} -``` + ### Configuración por defecto diff --git a/site/src/content/docs/es/middleware/decompress.md b/site/src/content/docs/es/middleware/decompress.mdx similarity index 61% rename from site/src/content/docs/es/middleware/decompress.md rename to site/src/content/docs/es/middleware/decompress.mdx index 9381e08a..976baba0 100644 --- a/site/src/content/docs/es/middleware/decompress.md +++ b/site/src/content/docs/es/middleware/decompress.mdx @@ -5,6 +5,8 @@ sidebar: order: 8 --- +import ConfigReference from '../../../../components/ConfigReference.astro'; + El middleware Decompress descomprime el body del request HTTP cuando el header `Content-Encoding` está establecido en `gzip`. @@ -36,22 +38,7 @@ e.Use(middleware.DecompressWithConfig(middleware.DecompressConfig{ ## Configuración -```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 -} -``` + ### Configuración por defecto diff --git a/site/src/content/docs/es/middleware/gzip.md b/site/src/content/docs/es/middleware/gzip.mdx similarity index 62% rename from site/src/content/docs/es/middleware/gzip.md rename to site/src/content/docs/es/middleware/gzip.mdx index 25e40e07..9e2ea1dd 100644 --- a/site/src/content/docs/es/middleware/gzip.md +++ b/site/src/content/docs/es/middleware/gzip.mdx @@ -5,6 +5,8 @@ sidebar: order: 9 --- +import ConfigReference from '../../../../components/ConfigReference.astro'; + El middleware Gzip comprime la response HTTP usando el esquema de compresión gzip. Todo el middleware principal reside en el paquete `middleware`: @@ -43,25 +45,7 @@ e.Use(middleware.GzipWithConfig(middleware.GzipConfig{ ## Configuración -```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 -} -``` + ### Configuración por defecto diff --git a/site/src/content/docs/es/middleware/index.md b/site/src/content/docs/es/middleware/index.md new file mode 100644 index 00000000..9ee5d940 --- /dev/null +++ b/site/src/content/docs/es/middleware/index.md @@ -0,0 +1,30 @@ +--- +title: Elige un middleware +description: Empieza por la tarea, sigue un ejemplo ejecutable y consulta la API exacta de Echo. +sidebar: + order: 0 +--- + +¿Nuevo en Echo? Crea primero un servidor con la [guía de inicio](../guide/quickstart/). Cada página explica cuándo usar el middleware, muestra cómo configurarlo y presenta campos y firmas tomados de una revisión concreta de Echo. + +## ¿Qué necesitas hacer? + +| Tarea | Empieza aquí | Pruébalo | +| --- | --- | --- | +| Ver peticiones y errores | [Request Logger](./logger/) y [Recover](./recover/) | Ejecuta el [ejemplo de logging](./logger/) | +| Permitir peticiones desde el navegador | [CORS](./cors/) | Usa orígenes de confianza explícitos | +| Proteger formularios | [CSRF](./csrf/) | Elige cómo guardar el token | +| Autenticar una API | [JWT](./jwt/) | Ejecuta el [recetario de JWT](../cookbook/jwt/) | +| Limitar peticiones abusivas | [Rate Limiter](./rate-limiter/) | Elige identificador y almacenamiento | +| Servir archivos | [Static](./static/) | Ejecuta el [ejemplo de archivos estáticos](./static/) | +| Reenviar peticiones | [Proxy](./proxy/) | Ejecuta el [recetario de proxy inverso](../cookbook/reverse-proxy/) | + +## Del código a una petición real + +1. Elige una tarea y copia el ejemplo de uso más pequeño. +2. Consulta la configuración para ver los campos y firmas de la revisión indicada de Echo. Lee los valores por defecto y las notas de seguridad antes de cambiar el comportamiento. +3. Ejecuta el ejemplo completo o la receta enlazada y envía una petición con `curl`. + +Para una primera API, sigue con [rutas](../guide/routing/), [binding](../guide/binding/), [gestión de errores](../guide/error-handling/) y [pruebas](../guide/testing/). Para tráfico de producción, añade [Recover](./recover/) y [Request Logger](./logger/) antes de elegir los middleware de seguridad. + +Integraciones como JWT se mantienen en módulos separados; sus páginas indican el paquete responsable. La API del middleware principal está en [`github.com/labstack/echo/v5/middleware`](https://pkg.go.dev/github.com/labstack/echo/v5/middleware). diff --git a/site/src/content/docs/es/middleware/jwt.md b/site/src/content/docs/es/middleware/jwt.md deleted file mode 100644 index 97cbbd8f..00000000 --- a/site/src/content/docs/es/middleware/jwt.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -title: JWT -description: Middleware de autenticación JSON Web Token provisto por el módulo echo-jwt. -sidebar: - order: 10 ---- - -El middleware JWT proporciona autenticación JSON Web Token (JWT). Vive en un módulo separado: -[github.com/labstack/echo-jwt](https://github.com/labstack/echo-jwt). - -Comportamiento: - -- Para un token válido, establece el usuario en el contexto y llama al siguiente handler. -- Para un token inválido, envía una response `401 Unauthorized`. -- Para un header `Authorization` ausente o inválido, envía una response `400 Bad Request`. - -## Dependencias - -```go -import "github.com/labstack/echo-jwt/v5" -``` - -## Uso - -```go -e.Use(echojwt.JWT([]byte("secret"))) -``` - -## Configuración personalizada - -```go -e.Use(echojwt.WithConfig(echojwt.Config{ - SigningKey: []byte("secret"), -})) -``` - -## Configuración - -```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 -} -``` - -## Ejemplo - -Consulta el [recetario de JWT](/es/cookbook/jwt/) para ver un ejemplo completo. diff --git a/site/src/content/docs/es/middleware/jwt.mdx b/site/src/content/docs/es/middleware/jwt.mdx new file mode 100644 index 00000000..6a404d5b --- /dev/null +++ b/site/src/content/docs/es/middleware/jwt.mdx @@ -0,0 +1,45 @@ +--- +title: JWT +description: Middleware de autenticación JSON Web Token provisto por el módulo echo-jwt. +sidebar: + order: 10 +--- + +import ConfigReference from '../../../../components/ConfigReference.astro'; + +El middleware JWT proporciona autenticación JSON Web Token (JWT). Vive en un módulo separado: +[github.com/labstack/echo-jwt](https://github.com/labstack/echo-jwt). + +Comportamiento: + +- Para un token válido, establece el usuario en el contexto y llama al siguiente handler. +- Para un token inválido, envía una response `401 Unauthorized`. +- Para un header `Authorization` ausente o inválido, envía una response `400 Bad Request`. + +## Dependencias + +```go +import "github.com/labstack/echo-jwt/v5" +``` + +## Uso + +```go +e.Use(echojwt.JWT([]byte("secret"))) +``` + +## Configuración personalizada + +```go +e.Use(echojwt.WithConfig(echojwt.Config{ + SigningKey: []byte("secret"), +})) +``` + +## Configuración + + + +## Ejemplo + +Consulta el [recetario de JWT](/es/cookbook/jwt/) para ver un ejemplo completo. diff --git a/site/src/content/docs/es/middleware/key-auth.md b/site/src/content/docs/es/middleware/key-auth.md deleted file mode 100644 index 6d2da984..00000000 --- a/site/src/content/docs/es/middleware/key-auth.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -title: Key Auth -description: Middleware de autenticación basada en clave que valida una API key desde header, query, form o cookie. -sidebar: - order: 11 ---- - -El middleware Key Auth proporciona autenticación basada en clave. - -- Para una clave válida llama al siguiente handler. -- Para una clave inválida, envía una response `401 Unauthorized`. -- Para una clave ausente, envía una response `400 Bad Request`. - -Todo el middleware principal reside en el paquete `middleware`: - -```go -import "github.com/labstack/echo/v5/middleware" -``` - -## Uso - -```go -e.Use(middleware.KeyAuth(func(c *echo.Context, key string, source middleware.ExtractorSource) (bool, error) { - return key == "valid-key", nil -})) -``` - -## Configuración personalizada - -```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 - }, -})) -``` - -## Configuración - -```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 -} -``` - -`Validator` tiene esta firma: - -```go -type KeyAuthValidator func(c *echo.Context, key string, source ExtractorSource) (bool, error) -``` - -### Configuración por defecto - -```go -DefaultKeyAuthConfig = KeyAuthConfig{ - Skipper: DefaultSkipper, - KeyLookup: "header:" + echo.HeaderAuthorization + ":Bearer ", -} -``` diff --git a/site/src/content/docs/es/middleware/key-auth.mdx b/site/src/content/docs/es/middleware/key-auth.mdx new file mode 100644 index 00000000..ae3adb58 --- /dev/null +++ b/site/src/content/docs/es/middleware/key-auth.mdx @@ -0,0 +1,59 @@ +--- +title: Key Auth +description: Middleware de autenticación basada en clave que valida una API key desde header, query, form o cookie. +sidebar: + order: 11 +--- + +import ConfigReference from '../../../../components/ConfigReference.astro'; + +El middleware Key Auth proporciona autenticación basada en clave. + +- Para una clave válida llama al siguiente handler. +- Para una clave inválida, envía una response `401 Unauthorized`. +- Para una clave ausente, envía una response `400 Bad Request`. + +Todo el middleware principal reside en el paquete `middleware`: + +```go +import "github.com/labstack/echo/v5/middleware" +``` + +## Uso + +```go +e.Use(middleware.KeyAuth(func(c *echo.Context, key string, source middleware.ExtractorSource) (bool, error) { + return key == "valid-key", nil +})) +``` + +## Configuración personalizada + +```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 + }, +})) +``` + +## Configuración + + + +`Validator` tiene esta firma: + +```go +type KeyAuthValidator func(c *echo.Context, key string, source ExtractorSource) (bool, error) +``` + +### Configuración por defecto + +```go +DefaultKeyAuthConfig = KeyAuthConfig{ + Skipper: DefaultSkipper, + KeyLookup: "header:" + echo.HeaderAuthorization + ":Bearer ", +} +``` diff --git a/site/src/content/docs/es/middleware/logger.md b/site/src/content/docs/es/middleware/logger.md deleted file mode 100644 index 25fb0640..00000000 --- a/site/src/content/docs/es/middleware/logger.md +++ /dev/null @@ -1,242 +0,0 @@ ---- -title: Request Logger -description: Logging de requests totalmente personalizable que se integra con bibliotecas de logging estructurado. -sidebar: - order: 12 ---- - -El middleware `RequestLogger` registra información sobre cada request HTTP. Te permite -personalizar por completo qué se registra y cómo, por lo que encaja bien con bibliotecas -de terceros de logging estructurado. - -Los valores que el logger puede extraer se controlan con los campos booleanos y slices de -`RequestLoggerConfig`. Habilita un campo (por ejemplo `LogStatus: true`) para que su valor -se rellene en `RequestLoggerValues`, que se pasa a tu `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 -} -``` - -Todo el middleware principal reside en el paquete `middleware`: - -```go -import "github.com/labstack/echo/v5/middleware" -``` - -## Ejemplos - -### 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 - }, -})) -``` - -Salida de ejemplo: - -```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 - }, -})) -``` - -Salida de ejemplo: - -```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 - }, -})) -``` - -Salida de ejemplo: - -```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 - }, -})) -``` - -Salida de ejemplo: - -```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 - }, -})) -``` - -Salida de ejemplo: - -```text -time="2024-12-30T21:08:49+08:00" level=info msg=request URI=/hello status=200 -``` - -## Solución de problemas - -### panic: missing LogValuesFunc callback function for request logger middleware - -Este panic ocurre cuando el callback obligatorio `LogValuesFunc` se deja sin configurar. -Define una función que coincida con la firma de `LogValuesFunc` y asígnala en la configuración: - -```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, -})) -``` - -### Los parámetros en los logs están vacíos - -Si valores como `v.URI` y `v.Status` están vacíos dentro de `LogValuesFunc`, comprueba que los -flags de extracción correspondientes (`LogStatus`, `LogURI`, etc.) estén establecidos en `true` -en la configuración. Cada valor solo se rellena cuando su flag está habilitado. diff --git a/site/src/content/docs/es/middleware/logger.mdx b/site/src/content/docs/es/middleware/logger.mdx new file mode 100644 index 00000000..1c65a829 --- /dev/null +++ b/site/src/content/docs/es/middleware/logger.mdx @@ -0,0 +1,54 @@ +--- +title: Request Logger +description: Registra valores seleccionados de la petición y la respuesta con tu biblioteca de logging. +sidebar: + order: 12 +--- + +import { Code } from '@astrojs/starlight/components'; +import ConfigReference from '../../../../components/ConfigReference.astro'; +import example from '../../../../generated/request-logger.go.txt?raw'; + +`RequestLogger` entrega los valores seleccionados de la petición y la respuesta a la función obligatoria `LogValuesFunc`. Puedes usar `slog` u otra biblioteca. Cada campo `Log*` activa la extracción de un valor; por ejemplo, `LogStatus: true` rellena `v.Status`. + +## Ejemplos + +### slog ([log/slog](https://pkg.go.dev/log/slog)) + +Este programa completo registra la URI y el estado como JSON. Desde el repositorio de `echox`, ejecuta `cd reference/request-logger && go run .` y solicita `http://localhost:1323/`. + + + +La página importa el mismo archivo que compila la CI de documentación. `v.Error` recibe el error devuelto por el siguiente handler. Con `HandleError: true`, el manejador global escribe la respuesta antes de ejecutar `LogValuesFunc`; los middleware anteriores ya no pueden cambiar el estado ni el cuerpo. + +### fmt.Printf + +En una aplicación pequeña, `LogValuesFunc` puede usar `fmt.Printf`. Activa los campos `Log*` de los valores que quieras leer. + +### Zerolog ([rs/zerolog](https://github.com/rs/zerolog)) + +Pasa los valores seleccionados a Zerolog dentro de `LogValuesFunc`. + +### Zap ([uber-go/zap](https://github.com/uber-go/zap)) + +Usa la misma función para enviar los valores a Zap. + +### Logrus ([sirupsen/logrus](https://github.com/sirupsen/logrus)) + +Crea los campos de Logrus a partir de `RequestLoggerValues`. + +## Configuración + +La tabla se genera a partir de los campos exportados de la revisión indicada de Echo. No presupone valores predeterminados en tiempo de ejecución. + + + +## Solución de problemas + +### panic: missing LogValuesFunc callback function for request logger middleware + +`LogValuesFunc` es obligatoria. Asígnala antes de llamar a `RequestLoggerWithConfig`; el ejemplo ejecutable incluye esta función. + +### Los parámetros en los logs están vacíos + +Activa el campo de extracción correspondiente: `LogURI: true` para `v.URI` o `LogStatus: true` para `v.Status`. diff --git a/site/src/content/docs/es/middleware/method-override.md b/site/src/content/docs/es/middleware/method-override.mdx similarity index 77% rename from site/src/content/docs/es/middleware/method-override.md rename to site/src/content/docs/es/middleware/method-override.mdx index aac8c37c..2bf06393 100644 --- a/site/src/content/docs/es/middleware/method-override.md +++ b/site/src/content/docs/es/middleware/method-override.mdx @@ -5,6 +5,8 @@ sidebar: order: 13 --- +import ConfigReference from '../../../../components/ConfigReference.astro'; + El middleware Method Override lee el método sobrescrito desde el request y lo usa en lugar del método original. @@ -37,16 +39,7 @@ El método puede obtenerse con `MethodFromHeader`, `MethodFromForm` o `MethodFro ## Configuración -```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 -} -``` + ### Configuración por defecto diff --git a/site/src/content/docs/es/middleware/open-telemetry.md b/site/src/content/docs/es/middleware/open-telemetry.mdx similarity index 92% rename from site/src/content/docs/es/middleware/open-telemetry.md rename to site/src/content/docs/es/middleware/open-telemetry.mdx index 4981e981..10572870 100644 --- a/site/src/content/docs/es/middleware/open-telemetry.md +++ b/site/src/content/docs/es/middleware/open-telemetry.mdx @@ -5,6 +5,8 @@ sidebar: order: 14 --- +import ConfigReference from '../../../../components/ConfigReference.astro'; + [Echo OpenTelemetry](https://github.com/labstack/echo-opentelemetry) es un middleware que proporciona instrumentación OpenTelemetry para requests HTTP. @@ -80,3 +82,7 @@ tracer, err := echo.ContextGet[trace.Tracer](c, echootel.TracerKey) El [ejemplo](https://github.com/labstack/echo-opentelemetry/blob/main/example/main.go) exporta metrics y spans a stdout, pero puedes usar cualquier exporter (OTLP, etc.). Consulta la documentación de [OpenTelemetry exporters](https://opentelemetry.io/docs/languages/go/exporters). + +## Referencia de la API + + diff --git a/site/src/content/docs/es/middleware/prometheus.md b/site/src/content/docs/es/middleware/prometheus.mdx similarity index 96% rename from site/src/content/docs/es/middleware/prometheus.md rename to site/src/content/docs/es/middleware/prometheus.mdx index 88398857..1f40da1f 100644 --- a/site/src/content/docs/es/middleware/prometheus.md +++ b/site/src/content/docs/es/middleware/prometheus.mdx @@ -5,6 +5,8 @@ sidebar: order: 15 --- +import ConfigReference from '../../../../components/ConfigReference.astro'; + El middleware [Echo Prometheus](https://github.com/labstack/echo-prometheus) genera metrics Prometheus para requests HTTP. @@ -282,3 +284,11 @@ func main() { } } ``` + +## Referencia de la API + + + + + + diff --git a/site/src/content/docs/es/middleware/proxy.md b/site/src/content/docs/es/middleware/proxy.md deleted file mode 100644 index 44ec44b8..00000000 --- a/site/src/content/docs/es/middleware/proxy.md +++ /dev/null @@ -1,140 +0,0 @@ ---- -title: Proxy -description: Middleware de reverse proxy HTTP y WebSocket con load balancing. -sidebar: - order: 16 ---- - -Proxy proporciona un middleware de reverse proxy HTTP/WebSocket. Reenvía un request a un -servidor upstream usando una técnica de load balancing configurada. - -Todo el middleware principal reside en el paquete `middleware`: - -```go -import "github.com/labstack/echo/v5/middleware" -``` - -## 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, - }, -}))) -``` - -## Configuración personalizada - -```go -e := echo.New() -e.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{})) -``` - -## Configuración - -```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 -} -``` - -### Configuración por defecto - -| Nombre | Valor | -| ---------- | -------------- | -| Skipper | DefaultSkipper | -| ContextKey | `target` | - -### Reglas basadas en regex - -Para rewriting avanzado de requests proxied, también se pueden definir reglas usando -expresiones regulares. Los grupos de captura normales se pueden definir con `()` y referenciar -por índice (`$1`, `$2`, ...) en el path reescrito. - -Las reglas `RegexRewrite` y `Rewrite` normales se pueden combinar. - -```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", - }, -})) -``` - -Consulta el recetario de [reverse proxy](/es/cookbook/reverse-proxy/) para ver un ejemplo completo. diff --git a/site/src/content/docs/es/middleware/proxy.mdx b/site/src/content/docs/es/middleware/proxy.mdx new file mode 100644 index 00000000..3fd867cd --- /dev/null +++ b/site/src/content/docs/es/middleware/proxy.mdx @@ -0,0 +1,79 @@ +--- +title: Proxy +description: Middleware de reverse proxy HTTP y WebSocket con load balancing. +sidebar: + order: 16 +--- + +import ConfigReference from '../../../../components/ConfigReference.astro'; + +Proxy proporciona un middleware de reverse proxy HTTP/WebSocket. Reenvía un request a un +servidor upstream usando una técnica de load balancing configurada. + +Todo el middleware principal reside en el paquete `middleware`: + +```go +import "github.com/labstack/echo/v5/middleware" +``` + +## 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, + }, +}))) +``` + +## Configuración personalizada + +```go +e := echo.New() +e.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{})) +``` + +## Configuración + + + +### Configuración por defecto + +| Nombre | Valor | +| ---------- | -------------- | +| Skipper | DefaultSkipper | +| ContextKey | `target` | + +### Reglas basadas en regex + +Para rewriting avanzado de requests proxied, también se pueden definir reglas usando +expresiones regulares. Los grupos de captura normales se pueden definir con `()` y referenciar +por índice (`$1`, `$2`, ...) en el path reescrito. + +Las reglas `RegexRewrite` y `Rewrite` normales se pueden combinar. + +```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", + }, +})) +``` + +Consulta el recetario de [reverse proxy](/es/cookbook/reverse-proxy/) para ver un ejemplo completo. diff --git a/site/src/content/docs/es/middleware/rate-limiter.md b/site/src/content/docs/es/middleware/rate-limiter.mdx similarity index 83% rename from site/src/content/docs/es/middleware/rate-limiter.md rename to site/src/content/docs/es/middleware/rate-limiter.mdx index f815d41b..5c0dfd31 100644 --- a/site/src/content/docs/es/middleware/rate-limiter.md +++ b/site/src/content/docs/es/middleware/rate-limiter.mdx @@ -5,6 +5,8 @@ sidebar: order: 17 --- +import ConfigReference from '../../../../components/ConfigReference.astro'; + `RateLimiter` proporciona un middleware de rate limiter que limita el número de requests enviados al servidor desde una IP o identificador particular dentro de un periodo. @@ -72,20 +74,9 @@ Para implementar tu propio store, satisface la interfaz `RateLimiterStore` y pá ## Configuración -```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 -} -``` + + + ### Configuración por defecto diff --git a/site/src/content/docs/es/middleware/recover.md b/site/src/content/docs/es/middleware/recover.mdx similarity index 67% rename from site/src/content/docs/es/middleware/recover.md rename to site/src/content/docs/es/middleware/recover.mdx index fee2951c..29c53d89 100644 --- a/site/src/content/docs/es/middleware/recover.md +++ b/site/src/content/docs/es/middleware/recover.mdx @@ -5,6 +5,8 @@ sidebar: order: 18 --- +import ConfigReference from '../../../../components/ConfigReference.astro'; + El middleware Recover se recupera de panics en cualquier punto de la cadena, imprime el stack trace y pasa el control al [HTTPErrorHandler](/es/guide/customization/#http-error-handler) centralizado. @@ -35,25 +37,7 @@ El ejemplo anterior usa un `StackSize` de 1 KB y valores por defecto para `Disab ## Configuración -```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 -} -``` + ### Configuración por defecto diff --git a/site/src/content/docs/es/middleware/redirect.md b/site/src/content/docs/es/middleware/redirect.mdx similarity index 88% rename from site/src/content/docs/es/middleware/redirect.md rename to site/src/content/docs/es/middleware/redirect.mdx index df641fc0..daab0e66 100644 --- a/site/src/content/docs/es/middleware/redirect.md +++ b/site/src/content/docs/es/middleware/redirect.mdx @@ -5,6 +5,8 @@ sidebar: order: 19 --- +import ConfigReference from '../../../../components/ConfigReference.astro'; + Todo el middleware principal reside en el paquete `middleware`: ```go @@ -85,16 +87,7 @@ El ejemplo anterior redirige requests HTTP a HTTPS con el código de estado ## Configuración -```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 -} -``` + ### Configuración por defecto diff --git a/site/src/content/docs/es/middleware/request-id.md b/site/src/content/docs/es/middleware/request-id.mdx similarity index 75% rename from site/src/content/docs/es/middleware/request-id.md rename to site/src/content/docs/es/middleware/request-id.mdx index 64b41732..345a52f4 100644 --- a/site/src/content/docs/es/middleware/request-id.md +++ b/site/src/content/docs/es/middleware/request-id.mdx @@ -5,6 +5,8 @@ sidebar: order: 20 --- +import ConfigReference from '../../../../components/ConfigReference.astro'; + El middleware Request ID genera un ID único para un request. Todo el middleware principal reside en el paquete `middleware`: @@ -49,23 +51,7 @@ e.Use(middleware.RequestIDWithConfig(middleware.RequestIDConfig{ ## Configuración -```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 -} -``` + ### Configuración por defecto diff --git a/site/src/content/docs/es/middleware/rewrite.md b/site/src/content/docs/es/middleware/rewrite.mdx similarity index 71% rename from site/src/content/docs/es/middleware/rewrite.md rename to site/src/content/docs/es/middleware/rewrite.mdx index dc3d86b4..61d79b5e 100644 --- a/site/src/content/docs/es/middleware/rewrite.md +++ b/site/src/content/docs/es/middleware/rewrite.mdx @@ -5,6 +5,8 @@ sidebar: order: 21 --- +import ConfigReference from '../../../../components/ConfigReference.astro'; + El middleware Rewrite reescribe el path de la URL según las reglas proporcionadas. Es útil para compatibilidad hacia atrás o para crear enlaces más limpios y descriptivos. @@ -42,29 +44,7 @@ e.Pre(middleware.RewriteWithConfig(middleware.RewriteConfig{})) ## Configuración -```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 -} -``` + Configuración por defecto: diff --git a/site/src/content/docs/es/middleware/secure.md b/site/src/content/docs/es/middleware/secure.md deleted file mode 100644 index 53a1a1b8..00000000 --- a/site/src/content/docs/es/middleware/secure.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -title: Secure -description: Protege contra XSS, content sniffing, clickjacking y otros ataques de inyección. -sidebar: - order: 22 ---- - -El middleware Secure proporciona protección contra cross-site scripting (XSS), content type -sniffing, clickjacking, conexiones inseguras y otros ataques de inyección de código. - -Todo el middleware principal reside en el paquete `middleware`: - -```go -import "github.com/labstack/echo/v5/middleware" -``` - -## Uso - -```go -e.Use(middleware.Secure()) -``` - -## Configuración personalizada - -```go -e := echo.New() -e.Use(middleware.SecureWithConfig(middleware.SecureConfig{ - XSSProtection: "", - ContentTypeNosniff: "", - XFrameOptions: "", - HSTSMaxAge: 3600, - ContentSecurityPolicy: "default-src 'self'", -})) -``` - -:::note -Pasar un `XSSProtection`, `ContentTypeNosniff`, `XFrameOptions` o -`ContentSecurityPolicy` vacío deshabilita esa protección. -::: - -## Configuración - -```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 ,