waf

package module
v0.14.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 19, 2024 License: Apache-2.0 Imports: 53 Imported by: 2

README

Web application framework

pkg.go.dev Go Report Card pipeline status coverage report

A Go package providing a Vue-compatible web application framework. It combines common patterns and best practices into a high-level API abstraction so that you can focus on building apps.

Features:

  • Routes are matched in the same way as Vue Router and supports having a single source of truth for both frontend and backend routes.
  • Integrates well with Vite development by proxying requests to Vite.
  • Production ready and can be exposed directly on open Internet.
  • Supports HTTP2 and TLS out of the box. TLS certificates can be automatically obtained (and updated) using Let's Encrypt (when running accessible from the Internet).
  • Efficient serving of static files from memory with compression, caching, and HTTP range requests.
  • Makes canonical log lines for each request.
  • Supports server timing measurements and response header.
  • Supports structured metadata in a response header encoded based on RFC 8941.
  • Supports web sockets.
  • Can serve multiple sites/configurations.
  • Supports CORS handling per route.

Installation

This is a Go package. You can add it to your project using go get:

go get gitlab.com/tozd/waf

It requires Go 1.21 or newer.

Automatic media type detection uses file extensions and a file extension database has to be available on the system. On Alpine this can be mailcap package. On Debina/Ubuntu media-types package.

Usage

See full package documentation on pkg.go.dev.

See examples to see how can components combine together into an app.

Local execution

To run apps locally, you need a HTTPS TLS certificate (as required by HTTP2). When running locally you can use mkcert, a tool to create a local CA keypair which is then used to create a TLS certificate. Use Go 1.19 or newer.

go install filippo.io/mkcert@latest
mkcert -install
mkcert localhost 127.0.0.1 ::1

This creates two files, localhost+2.pem and localhost+2-key.pem, which you can then pass in TLS configuration to Waf.

Vite integration

During development you might want to use Vite. Vite compiles frontend files and serves them. It also watches for changes in frontend files, recompiles them, and hot-reloads the frontend as necessary. Node 16 or newer is required.

After installing dependencies and running vite serve, Vite listens on http://localhost:5173. Pass that to Service's Development field. Open https://localhost:8080/ in your browser, which will connect you to the backend which then proxies unknown requests (non-API requests) to Vite, the frontend.

If you want your handler to proxy to Vite during development, you can do something like:

func (s *Service) Home(w http.ResponseWriter, req *http.Request, _ Params) {
  if s.Development != "" {
    s.Proxy(w, req)
    return
  }

  // ... your handler ...
}
Vue Router integration

You can create JSON with routes in your repository, e.g., routes.json which you can then use both in your Go code and Vue Router as a single source of truth for routes:

{
  "routes": [
    {
      "name": "Home",
      "path": "/",
      "api": null,
      "get": {}
    }
  ]
}

To populate Service's Routes field:

import _ "embed"
import "encoding/json"

import "gitlab.com/tozd/waf"

//go:embed routes.json
var routesConfiguration []byte

func newService() (*waf.Service, err) {
  var config struct {
    Routes []waf.Route `json:"routes"`
  }
  err := json.Unmarshal(routesConfiguration, &config)
  if err != nil {
    return err
  }
  return &waf.Service[*waf.Site]{
    Routes: config.Routes,
    // ... the rest ...
  }
}

On the frontend:

import { createRouter, createWebHistory } from "vue-router";
import { routes } from "@/../routes.json";

const router = createRouter({
  history: createWebHistory(),
  routes: routes
    .filter((route) => route.get)
    .map((route) => ({
      path: route.path,
      name: route.name,
      component: () => import(`./views/${route.name}.vue`),
      props: true,
    })),
});

const apiRouter = createRouter({
  history: createWebHistory(),
  routes: routes
    .filter((route) => route.api)
    .map((route) => ({
      path: route.path == "/" ? "/api" : `/api${route.path}`,
      name: route.name,
      component: () => null,
      props: true,
    })),
});

router.apiResolve = apiRouter.resolve.bind(apiRouter);

// ... create the app, use router, and mount the app ...

You can then use router.resolve to resolve non-API routes and router.apiResolve to resolve API routes.

Why API paths have /api prefix and do not use content negotiation?

Content negotiated responses do not cache well. Browsers cache by path and ignore Accept header. This means that if your frontend requests both text/html and application/json at same path only one of them will be cached and then if you then repeat both requests, the request for non-cached content type might arrive first, invalidating even the cached one. Browsers at least do not serve wrong content because Etag header depends on the content itself so browsers detect cache mismatch.

There are many great projects doing similar things. Waf's primarily goal is being compatible with Vue Router and frontend development with Vite.

This package works well with gitlab.com/tozd/go/zerolog (based on zerolog) and gitlab.com/tozd/go/cli (based on Kong) packages.

GitHub mirror

There is also a read-only GitHub mirror available, if you need to fork the project there.

Documentation

Overview

Package waf provides reusable components of a Vue-compatible web application framework.

Those components can be combined in a way to create a production ready HTTP/1.1 and HTTP2 application server.

Index

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.Base("not found")

Functions

func Error

func Error(w http.ResponseWriter, _ *http.Request, code int)

Error replies to the request with the specified HTTP code. Error message is automatically generated based on the HTTP code using http.StatusText.

It does not otherwise end the request; the caller should ensure no further writes are done to w.

func GetSite

func GetSite[SiteT hasSite](ctx context.Context) (SiteT, bool)

GetSite returns the site from context ctx and true if the site is stored in the context.

Note, Waf service always stores the site (based on host header in the request) in the request context.

func MustGetSite

func MustGetSite[SiteT hasSite](ctx context.Context) SiteT

MustGetSite returns the site from context ctx or panics if the site is not stored in the context.

Note, Waf service always stores the site (based on host header in the request) in the request context.

func MustRequestID

func MustRequestID(ctx context.Context) identifier.Identifier

MustRequestID returns the request identifier from context ctx or panics if the request identifier is not stored in the context.

Note, Waf service always stores the request identifier in the request context.

func RequestID

func RequestID(ctx context.Context) (identifier.Identifier, bool)

RequestID returns the request identifier from context ctx and true if the request identifier is stored in the context.

Note, Waf service always stores the request identifier in the request context.

Types

type CORSOptions added in v0.11.0

type CORSOptions struct {
	AllowedOrigins       []string `json:"allowedOrigins,omitempty"`
	AllowedMethods       []string `json:"allowedMethods,omitempty"`
	AllowedHeaders       []string `json:"allowedHeaders,omitempty"`
	ExposedHeaders       []string `json:"exposedHeaders,omitempty"`
	MaxAge               int      `json:"maxAge,omitempty"`
	AllowCredentials     bool     `json:"allowCredentials,omitempty"`
	AllowPrivateNetwork  bool     `json:"allowPrivateNetwork,omitempty"`
	OptionsSuccessStatus int      `json:"optionsSuccessStatus,omitempty"`
}

CORSOptions is a subset of cors.Options.

See description of fields in cors.Options.

See: https://github.com/rs/cors/pull/164

func (*CORSOptions) GetAllowedMethods added in v0.11.0

func (c *CORSOptions) GetAllowedMethods() []string

type Handler

type Handler func(http.ResponseWriter, *http.Request, Params)

Handler type defines Waf router handler function signature.

The function signature is similar to http.HandlerFunc, but has additional parameter with Params parsed from the request URL path.

type MethodNotAllowedError added in v0.5.0

type MethodNotAllowedError struct {
	Allow []string
}

func (*MethodNotAllowedError) Error added in v0.5.0

func (*MethodNotAllowedError) Error() string

type Params

type Params map[string]string

Params are parsed from the request URL path based on the matched route. Map keys are parameter names.

type ResolvedRoute added in v0.8.0

type ResolvedRoute struct {
	Name    string
	Handler Handler
	Params  Params
}

type Route

type Route struct {
	// Name of the route. It should be unique.
	Name string `json:"name"`

	// Path for the route. It can contain parameters.
	Path string `json:"path"`

	// Does this route support API handlers.
	// API paths are automatically prefixed with /api.
	API *RouteOptions `json:"api,omitempty"`

	// Does this route have a non-API handler.
	Get *RouteOptions `json:"get,omitempty"`
}

Route is a high-level route definition which is used by a service to register handlers with the router. It can also be used by Vue Router to register routes there.

type RouteOptions added in v0.11.0

type RouteOptions struct {
	// Enable CORS on handler(s)?
	CORS *CORSOptions `json:"cors,omitempty"`
}

type Router

type Router struct {
	// NotFound is called if no route matches URL path.
	// If not defined, the request is replied with the 404 (not found) HTTP code error.
	NotFound func(http.ResponseWriter, *http.Request)

	// MethodNotAllowed is called the route does not support used HTTP method.
	// If not defined, the request is replied with the 405 (method not allowed) HTTP code error.
	MethodNotAllowed func(http.ResponseWriter, *http.Request, Params, []string)

	// Panic is called if handler panics instead of returning.
	// If not defined, panics propagate.
	Panic func(w http.ResponseWriter, req *http.Request, err interface{})

	// EncodeQuery allows customization of how query strings are encoded
	// when reversing a route in Reverse and ReverseAPI methods.
	EncodeQuery func(qs url.Values) string
	// contains filtered or unexported fields
}

Router calls handlers for HTTP requests based on URL path and HTTP method.

The goal of the router is to match routes in the same way as Vue Router. In addition, it supports also API handlers matched on HTTP method. API handlers share the same route name but have their path automatically prefixed with /api.

func (*Router) Get added in v0.8.0

func (r *Router) Get(path, method string) (ResolvedRoute, errors.E)

Get resolves path and method to a route, or returns MethodNotAllowedError or ErrNotFound errors.

func (*Router) Handle

func (r *Router) Handle(name, method, path string, api bool, handler Handler) errors.E

Handle registers the route handler with route name at path and with HTTP method.

Path can contain parameters which start with ":". E.g., "/post/:id" is a path with one parameter "id". Those parameters are parsed from the request URL and passed to handlers.

Routes are matched in the order in which they are registered.

Non-API handlers can use only GET HTTP method, which is used also for HEAD HTTP method automatically.

Route is identified with the name and can have only one path associated with it, but it can have different handlers for different HTTP methods and can have both API and non-API handlers. Path for API handlers is automatically prefixed with /api, so you must not prefix it yourself.

func (*Router) Reverse

func (r *Router) Reverse(name string, params Params, qs url.Values) (string, errors.E)

Reverse constructs the path and query string portion of an URL based on the route name, Params, and query string values.

func (*Router) ReverseAPI

func (r *Router) ReverseAPI(name string, params Params, qs url.Values) (string, errors.E)

ReverseAPI constructs the path and query string portion of an URL for API calls based on the route name, Params, and query string values.

func (*Router) ServeHTTP

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP matches the route for the given request based on URL path, extracts Params from the path, and calls route's handler for the HTTP method.

If no route matches URL path, NotFound is called, if defined, or the request is replied with the 404 (not found) HTTP code error.

If the route does not support used HTTP method, MethodNotAllowed is called, if defined, or the request is replied with the 405 (method not allowed) HTTP code error.

type Server

type Server[SiteT hasSite] struct {
	// Logger to be used by the server.
	Logger zerolog.Logger `kong:"-" yaml:"-"`

	// Run in development mode. By default proxy unknown requests.
	Development bool `help:"Run in development mode.${developmentModeHelp}" short:"d" yaml:"development"`

	// Base URL to proxy to in development mode.
	ProxyTo string `` /* 147-byte string literal not displayed */

	// TLS configuration.
	TLS TLS `embed:"" prefix:"tls." yaml:"tls"`

	// Exposed primarily for use in tests.
	Addr string `json:"-" kong:"-" yaml:"-"`

	// Exposed primarily for use in tests.
	HTTPServer *http.Server `json:"-" kong:"-" yaml:"-"`
	// contains filtered or unexported fields
}

Server listens to HTTP/1.1 and HTTP2 requests on TLS enabled port 8080 and serves requests using the provided handler. Server is production ready and can be exposed directly on open Internet.

Certificates for TLS can be provided as files (which are daily reread to allow updating them) or can be automatically obtained (and updated) using Let's Encrypt (when running accessible from the Internet).

func (*Server[SiteT]) Init

func (s *Server[SiteT]) Init(sites map[string]SiteT) (map[string]SiteT, errors.E)

Init determines the set of sites based on TLS configuration and sites provided, returning possibly updated and expanded set of sites.

If sites parameter is empty, sites are determined from domain names found in TLS certificates. If sites are provided and TLS certificates are not, their domains are used to obtain the necessary certificate from Let's Encrypt.

Key in sites map must match site's domain.

func (*Server[SiteT]) ListenAddr

func (s *Server[SiteT]) ListenAddr() string

ListenAddr returns the address on which the server is listening.

Available only after the server runs. It blocks until the server runs if called before. If server fails to start before the address is obtained, it unblocks and returns an empty string.

func (*Server[SiteT]) ProxyToInDevelopment added in v0.13.0

func (s *Server[SiteT]) ProxyToInDevelopment() string

ProxyToInDevelopment returns ProxyTo base URL if Development is true. Otherwise it returns an empty string.

func (*Server[SiteT]) Run

func (s *Server[SiteT]) Run(ctx context.Context, handler http.Handler) errors.E

Run runs the server serving requests using the provided handler.

It returns only on error or if the server is gracefully shut down when the context is canceled.

type Service

type Service[SiteT hasSite] struct {
	// General logger for the service.
	Logger zerolog.Logger

	// Canonical log line logger for the service which logs one log entry per
	// request. It is automatically populated with data about the request.
	CanonicalLogger zerolog.Logger

	// WithContext is a function which adds to the context a logger.
	// It is then accessible using zerolog.Ctx(ctx).
	// The first function is called when the request is handled and allows
	// any cleanup necessary. The second function is called on panic.
	// If WithContext is not set, Logger is used instead.
	WithContext func(context.Context) (context.Context, func(), func()) `exhaustruct:"optional"`

	// StaticFiles to be served by the service. All paths are anchored at / when served.
	// HTML files (those with ".html" extension) are rendered using html/template
	// with site struct as data. Other files are served as-is.
	StaticFiles fs.ReadFileFS

	// Routes to be handled by the service and mapped to its Handler methods.
	Routes []Route

	// Sites configured for the service. Key in the map must match site's domain.
	// This should generally be set to sites returned from Server.Init method.
	Sites map[string]SiteT

	// Middleware is a chain of additional middleware to append before the router.
	Middleware []func(http.Handler) http.Handler `exhaustruct:"optional"`

	// SiteContextPath is the path at which site context (JSON of site struct)
	// should be added to static files.
	SiteContextPath string `exhaustruct:"optional"`

	// MetadataHeaderPrefix is an optional prefix to the Metadata response header.
	MetadataHeaderPrefix string `exhaustruct:"optional"`

	// ProxyStaticTo is a base URL to proxy to during development, if set.
	// This should generally be set to result of Server.ProxyToInDevelopment method.
	// If set, StaticFiles are not served by the service so that they can be proxied instead.
	ProxyStaticTo string `exhaustruct:"optional"`

	// IsImmutableFile should return true if the static file is immutable and
	// should have such caching headers. Static files are those which do not change
	// during a runtime of the program. Immutable files are those which are never changed.
	IsImmutableFile func(path string) bool `exhaustruct:"optional"`

	// SkipServingFile should return true if the static file should not be automatically
	// registered with the router to be served. It can still be served using ServeStaticFile.
	SkipServingFile func(path string) bool `exhaustruct:"optional"`
	// contains filtered or unexported fields
}

Service defines the application logic for your service.

You should embed the Service struct inside your service struct on which you define handlers as methods with Handler signature. Handlers together with StaticFiles, Routes and Sites define how should the service handle HTTP requests.

func (*Service[SiteT]) AddMetadata

func (s *Service[SiteT]) AddMetadata(w http.ResponseWriter, req *http.Request, metadata map[string]interface{}) ([]byte, errors.E)

AddMetadata adds header with metadata to the response.

Metadata is encoded based on RFC 8941. Header name is "Metadata" with optional MetadataHeaderPrefix.

func (*Service[SiteT]) BadRequest

func (s *Service[SiteT]) BadRequest(w http.ResponseWriter, req *http.Request)

BadRequest replies to the request with the 400 (bad request) HTTP code and the corresponding error message.

It does not otherwise end the request; the caller should ensure no further writes are done to w.

func (*Service[SiteT]) BadRequestWithError

func (s *Service[SiteT]) BadRequestWithError(w http.ResponseWriter, req *http.Request, err errors.E)

BadRequestWithError replies to the request with the 400 (bad request) HTTP code and the corresponding error message. Error err is logged to the canonical log line.

As a special case, if err is context.Canceled or context.DeadlineExceeded it instead replies with the 408 (request timeout) HTTP code, the corresponding error message, and logs to the canonical log line that the context has been canceled or that deadline exceeded, respectively.

It does not otherwise end the request; the caller should ensure no further writes are done to w.

func (*Service[SiteT]) GetRoute added in v0.9.0

func (s *Service[SiteT]) GetRoute(name, method string) (ResolvedRoute, errors.E)

GetRoute calls router's Get.

func (*Service[SiteT]) InternalServerError

func (s *Service[SiteT]) InternalServerError(w http.ResponseWriter, req *http.Request)

InternalServerError replies to the request with the 500 (internal server error) HTTP code and the corresponding error message.

It does not otherwise end the request; the caller should ensure no further writes are done to w.

func (*Service[SiteT]) InternalServerErrorWithError

func (s *Service[SiteT]) InternalServerErrorWithError(w http.ResponseWriter, req *http.Request, err errors.E)

InternalServerErrorWithError replies to the request with the 500 (internal server error) HTTP code and the corresponding error message. Error err is logged to the canonical log line.

As a special case, if err is context.Canceled or context.DeadlineExceeded it instead replies with the 408 (request timeout) HTTP code, the corresponding error message, and logs to the canonical log line that the context has been canceled or that deadline exceeded, respectively.

It does not otherwise end the request; the caller should ensure no further writes are done to w.

func (*Service[SiteT]) MethodNotAllowed

func (s *Service[SiteT]) MethodNotAllowed(w http.ResponseWriter, req *http.Request, allow []string)

MethodNotAllowed replies to the request with the 405 (method not allowed) HTTP code and the corresponding error message. It adds Allow response header based on the list of allowed methods in allow.

It does not otherwise end the request; the caller should ensure no further writes are done to w.

func (*Service[SiteT]) NotFound

func (s *Service[SiteT]) NotFound(w http.ResponseWriter, req *http.Request)

NotFound replies to the request with the 404 (not found) HTTP code and the corresponding error message.

It does not otherwise end the request; the caller should ensure no further writes are done to w.

func (*Service[SiteT]) NotFoundWithError

func (s *Service[SiteT]) NotFoundWithError(w http.ResponseWriter, req *http.Request, err errors.E)

NotFound replies to the request with the 404 (not found) HTTP code and the corresponding error message. Error err is logged to the canonical log line.

As a special case, if err is context.Canceled or context.DeadlineExceeded it instead replies with the 408 (request timeout) HTTP code, the corresponding error message, and logs to the canonical log line that the context has been canceled or that deadline exceeded, respectively.

It does not otherwise end the request; the caller should ensure no further writes are done to w.

func (*Service[SiteT]) PrepareJSON added in v0.6.0

func (s *Service[SiteT]) PrepareJSON(w http.ResponseWriter, req *http.Request, data interface{}, metadata map[string]interface{}) []byte

PrepareJSON prepares the JSON response to the request. It populates response headers and encodes data as JSON. Optional metadata is added as the response header.

Besides other types, data can be of type []byte and json.RawMessage in which case it is expected that it already contains a well-formed JSON and is returned as-is.

If there is an error, PrepareJSON responds to the request and returns nil.

func (*Service[SiteT]) Proxy

func (s *Service[SiteT]) Proxy(w http.ResponseWriter, req *http.Request)

Proxy proxies request to the development backend (e.g., Vite).

func (*Service[SiteT]) RedirectToMainSite added in v0.4.0

func (s *Service[SiteT]) RedirectToMainSite(mainDomain string) func(next http.Handler) http.Handler

RedirectToMainSite is a middleware which redirects all requests to the site with mainDomain if they are made for another site on non-main domain.

func (*Service[SiteT]) Reverse

func (s *Service[SiteT]) Reverse(name string, params Params, qs url.Values) (string, errors.E)

Reverse calls router's Reverse.

func (*Service[SiteT]) ReverseAPI

func (s *Service[SiteT]) ReverseAPI(name string, params Params, qs url.Values) (string, errors.E)

Reverse calls router's ReverseAPI.

func (*Service[SiteT]) RouteWith

func (s *Service[SiteT]) RouteWith(service interface{}, router *Router) (http.Handler, errors.E)

RouteWith registers static files and handlers with the router based on Routes and service Handler methods and returns a http.Handler to be used with the Server.

You should generally pass your service struct with embedded Service struct as service parameter so that handler methods can be detected. Non-API handler methods should have the same name as the route. While API handler methods should have the name matching the route name with HTTP method name as suffix (e.g., "CommentPost" for route with name "Comment" and POST HTTP method).

func (*Service[SiteT]) ServeStaticFile

func (s *Service[SiteT]) ServeStaticFile(w http.ResponseWriter, req *http.Request, path string)

ServeStaticFile replies to the request by serving the file at path from service's static files.

It does not otherwise end the request; the caller should ensure no further writes are done to w.

func (*Service[SiteT]) TemporaryRedirectGetMethod added in v0.2.0

func (s *Service[SiteT]) TemporaryRedirectGetMethod(w http.ResponseWriter, req *http.Request, location string)

TemporaryRedirectGetMethod redirects the client to a new URL with the 303 (see other) HTTP code which makes the client do the request to a new location with the GET method.

func (*Service[SiteT]) TemporaryRedirectSameMethod added in v0.2.0

func (s *Service[SiteT]) TemporaryRedirectSameMethod(w http.ResponseWriter, req *http.Request, location string)

TemporaryRedirectSameMethod redirects the client to a new URL with the 307 (temporary redirect) HTTP code which makes the client redo the request to a new location with the same method and body.

func (*Service[SiteT]) WithError added in v0.5.0

func (s *Service[SiteT]) WithError(ctx context.Context, err errors.E)

WithError logs err to the canonical log line.

As a special case, if err is context.Canceled or context.DeadlineExceeded it logs to the canonical log line that the context has been canceled or that deadline exceeded, respectively.

func (*Service[SiteT]) WriteJSON

func (s *Service[SiteT]) WriteJSON(w http.ResponseWriter, req *http.Request, data interface{}, metadata map[string]interface{})

WriteJSON replies to the request by writing data as JSON.

Optional metadata is added as the response header.

Besides other types, data can be of type []byte and json.RawMessage in which case it is expected that it already contains a well-formed JSON and is written as-is.

It does not otherwise end the request; the caller should ensure no further writes are done to w.

type Site

type Site struct {
	Domain string `json:"domain" required:"" yaml:"domain"`

	// Certificate file path for the site. It should be valid for the domain.
	// Used when Let's Encrypt is not configured.
	CertFile string `` /* 139-byte string literal not displayed */

	// Key file path. Used when Let's Encrypt is not configured.
	KeyFile string `` /* 143-byte string literal not displayed */
	// contains filtered or unexported fields
}

Site describes the site at a domain.

A service can have multiple sites which share static files and handlers, but have different configuration and rendered HTML files. Core such configuration is site's domain, but you can provide your own site struct and embed Site to add additional configuration. Your site struct is then used when rendering HTML files and as site context to the frontend at SiteContextPath URL path.

Certificate and key file paths are not exposed in site context JSON.

func (*Site) GetSite

func (s *Site) GetSite() *Site

GetSite returns Site. This is used when you want to provide your own site struct to access the Site struct. If you embed Site inside your site struct then this method propagates to your site struct and does the right thing automatically.

func (*Site) Validate added in v0.13.1

func (s *Site) Validate() error

type TLS

type TLS struct {
	// Default certificate for TLS, when not using Let's Encrypt.
	CertFile string `` /* 164-byte string literal not displayed */

	// Default certificate's private key, when not using Let's Encrypt.
	KeyFile string `` /* 168-byte string literal not displayed */

	// Contact e-mail to use with Let's Encrypt.
	Email string `group:"Let's Encrypt:" help:"Contact e-mail to use with Let's Encrypt." short:"E" yaml:"email"`

	// Let's Encrypt's cache directory.
	Cache string `` /* 174-byte string literal not displayed */

	// Used primarily for testing.
	ACMEDirectory        string `json:"-" kong:"-" yaml:"-"`
	ACMEDirectoryRootCAs string `json:"-" kong:"-" yaml:"-"`
}

TLS configuration used by the server.

func (*TLS) Validate

func (t *TLS) Validate() error

Validate is used by Kong to validate the struct.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL