diff --git a/caddy.go b/caddy.go index cde42b100..5477267e5 100644 --- a/caddy.go +++ b/caddy.go @@ -506,7 +506,7 @@ func goModule(mod *debug.Module) *debug.Module { // TODO: track related Go issue: https://github.com/golang/go/issues/29228 // once that issue is fixed, we should just be able to use bi.Main... hopefully. for _, dep := range bi.Deps { - if dep.Path == "github.com/caddyserver/caddy/v2" { + if dep.Path == ImportPath { return dep } } @@ -543,3 +543,6 @@ var ( // path, for converting /id/ paths to /config/ paths. rawCfgIndex map[string]string ) + +// ImportPath is the package import path for Caddy core. +const ImportPath = "github.com/caddyserver/caddy/v2" diff --git a/modules.go b/modules.go index b036bea15..46fdd3b91 100644 --- a/modules.go +++ b/modules.go @@ -75,16 +75,16 @@ type ModuleInfo struct { // label is the module name, and the labels before that constitute // the namespace (or scope). // -// Thus, a module ID has the form: . +// Thus, a module ID has the form: . // // An ID with no dot has the empty namespace, which is appropriate // for app modules (these are "top-level" modules that Caddy core // loads and runs). // -// Module IDs should be lowercase and use underscore (_) instead of +// Module IDs should be lowercase and use underscores (_) instead of // spaces. // -// Example valid names: +// Examples of valid IDs: // - http // - http.handlers.file_server // - caddy.logging.encoders.json @@ -92,7 +92,7 @@ type ModuleID string // Namespace returns the namespace (or scope) portion of a module ID, // which is all but the last label of the ID. If the ID has only one -// label, then +// label, then the namespace is empty. func (id ModuleID) Namespace() string { lastDot := strings.LastIndex(string(id), ".") if lastDot < 0 { diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go index d3be288be..38e9a65f2 100644 --- a/modules/caddyhttp/caddyhttp.go +++ b/modules/caddyhttp/caddyhttp.go @@ -44,7 +44,47 @@ func init() { } } -// App is a robust, flexible HTTP server for Caddy. +// App is a robust, production-ready HTTP server. +// +// HTTPS is enabled by default if host matchers with qualifying names are used +// in any of routes; certificates are automatically provisioned and renewed. +// Additionally, automatic HTTPS will also enable HTTPS for servers that listen +// only on the HTTPS port but which do not have any TLS connection policies +// defined by adding a good, default TLS connection policy. +// +// In HTTP routes, additional placeholders are available: +// +// Placeholder | Description +// ------------|--------------- +// `{http.request.cookie.*}` | HTTP request cookie +// `{http.request.header.*}` | Specific request header field +// `{http.request.host.labels.*}` | Request host labels (0-based from right); e.g. for foo.example.com: 0=com, 1=example, 2=foo +// `{http.request.host}` | The host part of the request's Host header +// `{http.request.hostport}` | The host and port from the request's Host header +// `{http.request.method}` | The request method +// `{http.request.orig.method}` | The request's original method +// `{http.request.orig.path.dir}` | The request's original directory +// `{http.request.orig.path.file}` | The request's original filename +// `{http.request.orig.uri.path}` | The request's original path +// `{http.request.orig.uri.query_string}` | The request's original full query string (with `?`) +// `{http.request.orig.uri.query}` | The request's original query string (without `?`) +// `{http.request.orig.uri}` | The request's original URI +// `{http.request.port}` | The port part of the request's Host header +// `{http.request.proto}` | The protocol of the request +// `{http.request.remote.host}` | The host part of the remote client's address +// `{http.request.remote.port}` | The port part of the remote client's address +// `{http.request.remote}` | The address of the remote client +// `{http.request.scheme}` | The request scheme +// `{http.request.uri.path.*}` | Parts of the path, split by `/` (0-based from left) +// `{http.request.uri.path.dir}` | The directory, excluding leaf filename +// `{http.request.uri.path.file}` | The filename of the path, excluding directory +// `{http.request.uri.path}` | The path component of the request URI +// `{http.request.uri.query_string}` | The full query string (with `?`) +// `{http.request.uri.query.*}` | Individual query string value +// `{http.request.uri.query}` | The query string (without `?`) +// `{http.request.uri}` | The full request URI +// `{http.response.header.*}` | Specific response header field +// `{http.vars.*}` | Custom variables in the HTTP handler chain type App struct { // HTTPPort specifies the port to use for HTTP (as opposed to HTTPS), // which is used when setting up HTTP->HTTPS redirects or ACME HTTP diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go index 6c1e880a0..c11986501 100644 --- a/modules/caddyhttp/fileserver/matcher.go +++ b/modules/caddyhttp/fileserver/matcher.go @@ -33,10 +33,20 @@ func init() { // MatchFile is an HTTP request matcher that can match // requests based upon file existence. +// +// Upon matching, two new placeholders will be made +// available: +// +// - `{http.matchers.file.relative}` The root-relative +// path of the file. This is often useful when rewriting +// requests. +// - `{http.matchers.file.absolute}` The absolute path +// of the matched file. type MatchFile struct { // The root directory, used for creating absolute // file paths, and required when working with - // relative paths; if not specified, the current + // relative paths; if not specified, `{http.vars.root}` + // will be used, if set; otherwise, the current // directory is assumed. Accepts placeholders. Root string `json:"root,omitempty"` diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go index 170dbe7bf..6c441089a 100644 --- a/modules/caddyhttp/matchers.go +++ b/modules/caddyhttp/matchers.go @@ -40,6 +40,12 @@ type ( MatchPath []string // MatchPathRE matches requests by a regular expression on the URI's path. + // + // Upon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}` + // where `name` is the regular expression's name, and `capture_group` is either + // the named or positional capture group from the expression itself. If no name + // is given, then the placeholder omits the name: `{http.regexp.capture_group}` + // (potentially leading to collisions). MatchPathRE struct{ MatchRegexp } // MatchMethod matches requests by the method. @@ -52,6 +58,12 @@ type ( MatchHeader http.Header // MatchHeaderRE matches requests by a regular expression on header fields. + // + // Upon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}` + // where `name` is the regular expression's name, and `capture_group` is either + // the named or positional capture group from the expression itself. If no name + // is given, then the placeholder omits the name: `{http.regexp.capture_group}` + // (potentially leading to collisions). MatchHeaderRE map[string]*MatchRegexp // MatchProtocol matches requests by protocol. @@ -65,6 +77,8 @@ type ( } // MatchNegate matches requests by negating its matchers' results. + // To use, simply specify a set of matchers like you normally would; + // the only difference is that their result will be negated. MatchNegate struct { MatchersRaw caddy.ModuleMap `json:"-" caddy:"namespace=http.matchers"` @@ -624,11 +638,26 @@ func (m MatchStarlarkExpr) Match(r *http.Request) bool { } // MatchRegexp is an embeddable type for matching -// using regular expressions. +// using regular expressions. It adds placeholders +// to the request's replacer. type MatchRegexp struct { - Name string `json:"name,omitempty"` - Pattern string `json:"pattern"` + // A unique name for this regular expression. Optional, + // but useful to prevent overwriting captures from other + // regexp matchers. + Name string `json:"name,omitempty"` + + // The regular expression to evaluate, in RE2 syntax, + // which is the same general syntax used by Go, Perl, + // and Python. For details, see + // [Go's regexp package](https://golang.org/pkg/regexp/). + // Captures are accessible via placeholders. Unnamed + // capture groups are exposed as their numeric, 1-based + // index, while named capture groups are available by + // the capture group name. + Pattern string `json:"pattern"` + compiled *regexp.Regexp + phPrefix string } // Provision compiles the regular expression. @@ -638,6 +667,10 @@ func (mre *MatchRegexp) Provision(caddy.Context) error { return fmt.Errorf("compiling matcher regexp %s: %v", mre.Pattern, err) } mre.compiled = re + mre.phPrefix = regexpPlaceholderPrefix + if mre.Name != "" { + mre.phPrefix += "." + mre.Name + } return nil } @@ -661,14 +694,14 @@ func (mre *MatchRegexp) Match(input string, repl *caddy.Replacer) bool { // save all capture groups, first by index for i, match := range matches { - key := fmt.Sprintf("http.regexp.%s.%d", mre.Name, i) + key := fmt.Sprintf("%s.%d", mre.phPrefix, i) repl.Set(key, match) } // then by name for i, name := range mre.compiled.SubexpNames() { if i != 0 && name != "" { - key := fmt.Sprintf("http.regexp.%s.%s", mre.Name, name) + key := fmt.Sprintf("%s.%s", mre.phPrefix, name) repl.Set(key, matches[i]) } } @@ -752,6 +785,8 @@ func (rm ResponseMatcher) matchHeaders(hdr http.Header) bool { var wordRE = regexp.MustCompile(`\w+`) +const regexpPlaceholderPrefix = "http.regexp" + // Interface guards var ( _ RequestMatcher = (*MatchHost)(nil) diff --git a/modules/caddyhttp/reverseproxy/reverseproxy.go b/modules/caddyhttp/reverseproxy/reverseproxy.go index 238e86f68..d0c483370 100644 --- a/modules/caddyhttp/reverseproxy/reverseproxy.go +++ b/modules/caddyhttp/reverseproxy/reverseproxy.go @@ -41,24 +41,19 @@ func init() { } // Handler implements a highly configurable and production-ready reverse proxy. +// // Upon proxying, this module sets the following placeholders (which can be used // both within and after this handler): // -// {http.reverse_proxy.upstream.address} -// The full address to the upstream as given in the config -// {http.reverse_proxy.upstream.hostport} -// The host:port of the upstream -// {http.reverse_proxy.upstream.host} -// The host of the upstream -// {http.reverse_proxy.upstream.port} -// The port of the upstream -// {http.reverse_proxy.upstream.requests} -// The approximate current number of requests to the upstream -// {http.reverse_proxy.upstream.max_requests} -// The maximum approximate number of requests allowed to the upstream -// {http.reverse_proxy.upstream.fails} -// The number of recent failed requests to the upstream -// +// Placeholder | Description +// ------------|------------- +// `{http.reverse_proxy.upstream.address}` | The full address to the upstream as given in the config +// `{http.reverse_proxy.upstream.hostport}` | The host:port of the upstream +// `{http.reverse_proxy.upstream.host}` | The host of the upstream +// `{http.reverse_proxy.upstream.port}` | The port of the upstream +// `{http.reverse_proxy.upstream.requests}` | The approximate current number of requests to the upstream +// `{http.reverse_proxy.upstream.max_requests}` | The maximum approximate number of requests allowed to the upstream +// `{http.reverse_proxy.upstream.fails}` | The number of recent failed requests to the upstream type Handler struct { // Configures the method of transport for the proxy. A transport // is what performs the actual "round trip" to the backend. diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index 0c30139a4..c333991b7 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -33,39 +33,9 @@ import ( // Server describes an HTTP server. type Server struct { - // Socket interfaces to which to bind listeners. Caddy network - // addresses have the following form: - // - // network/address - // - // The network part is anything that [Go's `net` package](https://golang.org/pkg/net/) - // recognizes, and is optional. The default network is `tcp`. If - // a network is specified, a single forward slash `/` is used to - // separate the network and address portions. - // - // The address part may be any of these forms: - // - // - `host` - // - `host:port` - // - `:port` - // - `/path/to/unix/socket` - // - // The host may be any hostname, resolvable domain name, or IP address. - // The port may be a single value (`:8080`) or a range (`:8080-8085`). - // A port range will be multiplied into singular addresses. Not all - // config parameters accept port ranges, but Listen does. - // - // Valid examples: - // - // :8080 - // 127.0.0.1:8080 - // localhost:8080 - // localhost:8080-8085 - // tcp/localhost:8080 - // tcp/localhost:8080-8085 - // udp/localhost:9005 - // unix//path/to/socket - // + // Socket addresses to which to bind listeners. Accepts + // [network addresses](/docs/conventions#network-addresses) + // that may include port ranges. Listen []string `json:"listen,omitempty"` // How long to allow a read from a client's upload. Setting this @@ -105,12 +75,15 @@ type Server struct { // The error routes work exactly like the normal routes. Errors *HTTPErrorConfig `json:"errors,omitempty"` - // How to handle TLS connections. + // How to handle TLS connections. At least one policy is + // required to enable HTTPS on this server if automatic + // HTTPS is disabled or does not apply. TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies,omitempty"` // AutoHTTPS configures or disables automatic HTTPS within this server. // HTTPS is enabled automatically and by default when qualifying names - // are present in a Host matcher. + // are present in a Host matcher and/or when the server is listening + // only on the HTTPS port. AutoHTTPS *AutoHTTPSConfig `json:"automatic_https,omitempty"` // MaxRehandles is the maximum number of times to allow a diff --git a/modules/caddyhttp/templates/templates.go b/modules/caddyhttp/templates/templates.go index d01b447c9..adf984447 100644 --- a/modules/caddyhttp/templates/templates.go +++ b/modules/caddyhttp/templates/templates.go @@ -29,7 +29,153 @@ func init() { caddy.RegisterModule(Templates{}) } -// Templates is a middleware which execute response bodies as templates. +// Templates is a middleware which executes response bodies as Go templates. +// The syntax is documented in the Go standard library's +// [text/template package](https://golang.org/pkg/text/template/). +// +// [All Sprig functions](https://masterminds.github.io/sprig/) are supported. +// +// In addition to the standard functions and Sprig functions, Caddy adds +// extra functions and data that are available to a template: +// +// ##### **`.Args`** +// +// Access arguments passed to this page/context, for example as the result of a `include`. +// +// ``` +// {{.Args 0}} // first argument +// ``` +// +// ##### `.Cookie` +// +// Gets the value of a cookie by name. +// +// ``` +// {{.Cookie "cookiename"}} +// ``` +// +// ##### `.Host` +// +// Returns the hostname portion (no port) of the Host header of the HTTP request. +// +// ``` +// {{.Host}} +// ``` +// +// ##### `httpInclude` +// +// Includes the contents of another file by making a virtual HTTP request (also known as a sub-request). The URI path must exist on the same virtual server because the request does not use sockets; instead, the request is crafted in memory and the handler is invoked directly for increased efficiency. +// +// ``` +// {{httpInclude "/foo/bar?q=val"}} +// ``` +// +// ##### `include` +// +// Includes the contents of another file. Optionally can pass key-value pairs as arguments to be accessed by the included file. +// +// ``` +// {{include "path/to/file.html"}} // no arguments +// {{include "path/to/file.html" "arg1" 2 "value 3"}} // with arguments +// ``` +// +// ##### `listFiles` +// +// Returns a list of the files in the given directory, which is relative to the template context's file root. +// +// ``` +// {{listFiles "/mydir"}} +// ``` +// +// ##### `markdown` +// +// Renders the given Markdown text as HTML. +// +// ``` +// {{markdown "My _markdown_ text"}} +// ``` +// +// ##### `.RemoteIP` +// +// Returns the client's IP address. +// +// ``` +// {{.RemoteIP}} +// ``` +// +// ##### `.RespHeader.Add` +// +// Adds a header field to the HTTP response. +// +// ``` +// {{.RespHeader.Add "Field-Name" "val"}} +// ``` +// +// ##### `.RespHeader.Del` +// +// Deletes a header field on the HTTP response. +// +// ``` +// {{.RespHeader.Del "Field-Name"}} +// ``` +// +// ##### `.RespHeader.Set` +// +// Sets a header field on the HTTP response, replacing any existing value. +// +// ``` +// {{.RespHeader.Set "Field-Name" "val"}} +// ``` +// +// ##### `splitFrontMatter` +// +// Splits front matter out from the body. Front matter is metadata that appears at the very beginning of a file or string. Front matter can be in YAML, TOML, or JSON formats: +// +// **TOML** front matter starts and ends with `+++`: +// +// ``` +// +++ +// template = "blog" +// title = "Blog Homepage" +// sitename = "A Caddy site" +// +++ +// ``` +// +// **YAML** is surrounded by `---`: +// +// ``` +// --- +// template: blog +// title: Blog Homepage +// sitename: A Caddy site +// --- +// ``` +// +// +// **JSON** is simply `{` and `}`: +// +// ``` +// { +// "template": "blog", +// "title": "Blog Homepage", +// "sitename": "A Caddy site" +// } +// ``` +// +// The resulting front matter will be made available like so: +// +// - `.Meta` to access the metadata fields, for example: `{{$parsed.Meta.title}}` +// - `.Body` to access the body after the front matter, for example: `{{markdown $parsed.Body}}` +// +// +// ##### `stripHTML` +// +// Removes HTML from a string. +// +// ``` +// {{stripHTML "Shows only text content"}} +// ``` +// type Templates struct { // The root path from which to load files. Required if template functions // accessing the file system are used (such as include). Default is