summaryrefslogtreecommitdiff
path: root/modules/caddyhttp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/caddyhttp')
-rw-r--r--modules/caddyhttp/caddyhttp.go86
-rw-r--r--modules/caddyhttp/encode/brotli/brotli.go23
-rw-r--r--modules/caddyhttp/encode/caddyfile.go85
-rw-r--r--modules/caddyhttp/encode/encode.go32
-rw-r--r--modules/caddyhttp/encode/gzip/gzip.go25
-rw-r--r--modules/caddyhttp/encode/zstd/zstd.go13
-rw-r--r--modules/caddyhttp/fileserver/caddyfile.go87
-rw-r--r--modules/caddyhttp/fileserver/matcher.go34
-rw-r--r--modules/caddyhttp/fileserver/staticfiles.go6
-rw-r--r--modules/caddyhttp/headers/caddyfile.go92
-rw-r--r--modules/caddyhttp/matchers.go100
-rw-r--r--modules/caddyhttp/replacer.go37
-rwxr-xr-xmodules/caddyhttp/reverseproxy/module.go26
-rwxr-xr-xmodules/caddyhttp/reverseproxy/upstream.go22
-rw-r--r--modules/caddyhttp/rewrite/caddyfile.go38
-rw-r--r--modules/caddyhttp/routes.go26
-rw-r--r--modules/caddyhttp/server.go14
-rw-r--r--modules/caddyhttp/staticerror.go43
-rw-r--r--modules/caddyhttp/staticresp.go45
-rw-r--r--modules/caddyhttp/staticresp_test.go2
-rw-r--r--modules/caddyhttp/table.go55
-rw-r--r--modules/caddyhttp/templates/caddyfile.go63
-rw-r--r--modules/caddyhttp/templates/templates.go3
-rw-r--r--modules/caddyhttp/templates/tplcontext.go13
-rw-r--r--modules/caddyhttp/vars.go71
25 files changed, 867 insertions, 174 deletions
diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go
index 467b40f..21c5b6d 100644
--- a/modules/caddyhttp/caddyhttp.go
+++ b/modules/caddyhttp/caddyhttp.go
@@ -15,9 +15,12 @@
package caddyhttp
import (
+ "bytes"
"context"
"crypto/tls"
+ "encoding/json"
"fmt"
+ "io"
"log"
weakrand "math/rand"
"net"
@@ -244,6 +247,14 @@ func (app *App) automaticHTTPS() error {
for d := range domainSet {
domains = append(domains, d)
if !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.SkipCerts) {
+ // if a certificate for this name is already loaded,
+ // don't obtain another one for it, unless we are
+ // supposed to ignore loaded certificates
+ if !srv.AutoHTTPS.IgnoreLoadedCerts &&
+ len(tlsApp.CertificatesWithSAN(d)) > 0 {
+ log.Printf("[INFO][%s] Skipping automatic certificate management because a certificate with that SAN is already loaded", d)
+ continue
+ }
domainsForCerts = append(domainsForCerts, d)
}
}
@@ -319,7 +330,7 @@ func (app *App) automaticHTTPS() error {
}
redirTo += "{http.request.uri}"
- redirRoutes = append(redirRoutes, ServerRoute{
+ redirRoutes = append(redirRoutes, Route{
matcherSets: []MatcherSet{
{
MatchProtocol("http"),
@@ -328,7 +339,7 @@ func (app *App) automaticHTTPS() error {
},
handlers: []MiddlewareHandler{
StaticResponse{
- StatusCode: weakString(strconv.Itoa(http.StatusTemporaryRedirect)), // TODO: use permanent redirect instead
+ StatusCode: WeakString(strconv.Itoa(http.StatusTemporaryRedirect)), // TODO: use permanent redirect instead
Headers: http.Header{
"Location": []string{redirTo},
"Connection": []string{"close"},
@@ -431,6 +442,77 @@ type MiddlewareHandler interface {
// emptyHandler is used as a no-op handler.
var emptyHandler HandlerFunc = func(http.ResponseWriter, *http.Request) error { return nil }
+// WeakString is a type that unmarshals any JSON value
+// as a string literal, with the following exceptions:
+// 1) actual string values are decoded as strings, and
+// 2) null is decoded as empty string
+// and provides methods for getting the value as various
+// primitive types. However, using this type removes any
+// type safety as far as deserializing JSON is concerned.
+type WeakString string
+
+// UnmarshalJSON satisfies json.Unmarshaler according to
+// this type's documentation.
+func (ws *WeakString) UnmarshalJSON(b []byte) error {
+ if len(b) == 0 {
+ return io.EOF
+ }
+ if b[0] == byte('"') && b[len(b)-1] == byte('"') {
+ var s string
+ err := json.Unmarshal(b, &s)
+ if err != nil {
+ return err
+ }
+ *ws = WeakString(s)
+ return nil
+ }
+ if bytes.Equal(b, []byte("null")) {
+ return nil
+ }
+ *ws = WeakString(b)
+ return nil
+}
+
+// MarshalJSON marshals was a boolean if true or false,
+// a number if an integer, or a string otherwise.
+func (ws WeakString) MarshalJSON() ([]byte, error) {
+ if ws == "true" {
+ return []byte("true"), nil
+ }
+ if ws == "false" {
+ return []byte("false"), nil
+ }
+ if num, err := strconv.Atoi(string(ws)); err == nil {
+ return json.Marshal(num)
+ }
+ return json.Marshal(string(ws))
+}
+
+// Int returns ws as an integer. If ws is not an
+// integer, 0 is returned.
+func (ws WeakString) Int() int {
+ num, _ := strconv.Atoi(string(ws))
+ return num
+}
+
+// Float64 returns ws as a float64. If ws is not a
+// float value, the zero value is returned.
+func (ws WeakString) Float64() float64 {
+ num, _ := strconv.ParseFloat(string(ws), 64)
+ return num
+}
+
+// Bool returns ws as a boolean. If ws is not a
+// boolean, false is returned.
+func (ws WeakString) Bool() bool {
+ return string(ws) == "true"
+}
+
+// String returns ws as a string.
+func (ws WeakString) String() string {
+ return string(ws)
+}
+
const (
// DefaultHTTPPort is the default port for HTTP.
DefaultHTTPPort = 80
diff --git a/modules/caddyhttp/encode/brotli/brotli.go b/modules/caddyhttp/encode/brotli/brotli.go
index 0890d43..e30d7bc 100644
--- a/modules/caddyhttp/encode/brotli/brotli.go
+++ b/modules/caddyhttp/encode/brotli/brotli.go
@@ -16,8 +16,10 @@ package caddybrotli
import (
"fmt"
+ "strconv"
"github.com/andybalholm/brotli"
+ "github.com/caddyserver/caddy/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
)
@@ -35,6 +37,22 @@ type Brotli struct {
Quality *int `json:"quality,omitempty"`
}
+// UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
+func (b *Brotli) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ if !d.NextArg() {
+ continue
+ }
+ qualityStr := d.Val()
+ quality, err := strconv.Atoi(qualityStr)
+ if err != nil {
+ return err
+ }
+ b.Quality = &quality
+ }
+ return nil
+}
+
// Validate validates b's configuration.
func (b Brotli) Validate() error {
if b.Quality != nil {
@@ -64,6 +82,7 @@ func (b Brotli) NewEncoder() encode.Encoder {
// Interface guards
var (
- _ encode.Encoding = (*Brotli)(nil)
- _ caddy.Validator = (*Brotli)(nil)
+ _ encode.Encoding = (*Brotli)(nil)
+ _ caddy.Validator = (*Brotli)(nil)
+ _ caddyfile.Unmarshaler = (*Brotli)(nil)
)
diff --git a/modules/caddyhttp/encode/caddyfile.go b/modules/caddyhttp/encode/caddyfile.go
new file mode 100644
index 0000000..846ec03
--- /dev/null
+++ b/modules/caddyhttp/encode/caddyfile.go
@@ -0,0 +1,85 @@
+// Copyright 2015 Matthew Holt and The Caddy Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package encode
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/caddyserver/caddy"
+ "github.com/caddyserver/caddy/caddyconfig"
+ "github.com/caddyserver/caddy/caddyconfig/caddyfile"
+ "github.com/caddyserver/caddy/caddyconfig/httpcaddyfile"
+)
+
+// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
+//
+// encode [<matcher>] <formats...> {
+// gzip [<level>]
+// zstd
+// brotli [<quality>]
+// }
+//
+// Specifying the formats on the first line will use those formats' defaults.
+func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ for _, arg := range d.RemainingArgs() {
+ mod, err := caddy.GetModule("http.encoders." + arg)
+ if err != nil {
+ return fmt.Errorf("finding encoder module '%s': %v", mod.Name, err)
+ }
+ encoding, ok := mod.New().(Encoding)
+ if !ok {
+ return fmt.Errorf("module %s is not an HTTP encoding", mod.Name)
+ }
+ if enc.EncodingsRaw == nil {
+ enc.EncodingsRaw = make(map[string]json.RawMessage)
+ }
+ enc.EncodingsRaw[arg] = caddyconfig.JSON(encoding, nil)
+ }
+
+ for d.NextBlock() {
+ name := d.Val()
+ mod, err := caddy.GetModule("http.encoders." + name)
+ if err != nil {
+ return fmt.Errorf("getting encoder module '%s': %v", mod.Name, err)
+ }
+ unm, ok := mod.New().(caddyfile.Unmarshaler)
+ if !ok {
+ return fmt.Errorf("encoder module '%s' is not a Caddyfile unmarshaler", mod.Name)
+ }
+ err = unm.UnmarshalCaddyfile(d.NewFromNextTokens())
+ if err != nil {
+ return err
+ }
+ encoding, ok := unm.(Encoding)
+ if !ok {
+ return fmt.Errorf("module %s is not an HTTP encoding", mod.Name)
+ }
+ if enc.EncodingsRaw == nil {
+ enc.EncodingsRaw = make(map[string]json.RawMessage)
+ }
+ enc.EncodingsRaw[name] = caddyconfig.JSON(encoding, nil)
+ }
+ }
+
+ return nil
+}
+
+// Bucket returns the HTTP Caddyfile handler bucket number.
+func (enc Encode) Bucket() int { return 3 }
+
+// Interface guard
+var _ httpcaddyfile.HandlerDirective = (*Encode)(nil)
diff --git a/modules/caddyhttp/encode/encode.go b/modules/caddyhttp/encode/encode.go
index b2c1327..4e5f743 100644
--- a/modules/caddyhttp/encode/encode.go
+++ b/modules/caddyhttp/encode/encode.go
@@ -52,19 +52,15 @@ type Encode struct {
// Provision provisions enc.
func (enc *Encode) Provision(ctx caddy.Context) error {
- enc.writerPools = make(map[string]*sync.Pool)
-
for modName, rawMsg := range enc.EncodingsRaw {
val, err := ctx.LoadModule("http.encoders."+modName, rawMsg)
if err != nil {
return fmt.Errorf("loading encoder module '%s': %v", modName, err)
}
- encoder := val.(Encoding)
-
- enc.writerPools[encoder.AcceptEncoding()] = &sync.Pool{
- New: func() interface{} {
- return encoder.NewEncoder()
- },
+ encoding := val.(Encoding)
+ err = enc.addEncoding(encoding)
+ if err != nil {
+ return err
}
}
enc.EncodingsRaw = nil // allow GC to deallocate - TODO: Does this help?
@@ -85,10 +81,28 @@ func (enc *Encode) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyh
defer w.(*responseWriter).Close()
break
}
-
return next.ServeHTTP(w, r)
}
+func (enc *Encode) addEncoding(e Encoding) error {
+ ae := e.AcceptEncoding()
+ if ae == "" {
+ return fmt.Errorf("encoder does not specify an Accept-Encoding value")
+ }
+ if _, ok := enc.writerPools[ae]; ok {
+ return fmt.Errorf("encoder already added: %s", ae)
+ }
+ if enc.writerPools == nil {
+ enc.writerPools = make(map[string]*sync.Pool)
+ }
+ enc.writerPools[ae] = &sync.Pool{
+ New: func() interface{} {
+ return e.NewEncoder()
+ },
+ }
+ return nil
+}
+
// openResponseWriter creates a new response writer that may (or may not)
// encode the response with encodingName. The returned response writer MUST
// be closed after the handler completes.
diff --git a/modules/caddyhttp/encode/gzip/gzip.go b/modules/caddyhttp/encode/gzip/gzip.go
index 45c5f54..28b08c2 100644
--- a/modules/caddyhttp/encode/gzip/gzip.go
+++ b/modules/caddyhttp/encode/gzip/gzip.go
@@ -18,7 +18,9 @@ import (
"compress/flate"
"compress/gzip" // TODO: consider using https://github.com/klauspost/compress/gzip
"fmt"
+ "strconv"
+ "github.com/caddyserver/caddy/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
)
@@ -35,6 +37,22 @@ type Gzip struct {
Level int `json:"level,omitempty"`
}
+// UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
+func (g *Gzip) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ if !d.NextArg() {
+ continue
+ }
+ levelStr := d.Val()
+ level, err := strconv.Atoi(levelStr)
+ if err != nil {
+ return err
+ }
+ g.Level = level
+ }
+ return nil
+}
+
// Provision provisions g's configuration.
func (g *Gzip) Provision(ctx caddy.Context) error {
if g.Level == 0 {
@@ -69,7 +87,8 @@ var defaultGzipLevel = 5
// Interface guards
var (
- _ encode.Encoding = (*Gzip)(nil)
- _ caddy.Provisioner = (*Gzip)(nil)
- _ caddy.Validator = (*Gzip)(nil)
+ _ encode.Encoding = (*Gzip)(nil)
+ _ caddy.Provisioner = (*Gzip)(nil)
+ _ caddy.Validator = (*Gzip)(nil)
+ _ caddyfile.Unmarshaler = (*Gzip)(nil)
)
diff --git a/modules/caddyhttp/encode/zstd/zstd.go b/modules/caddyhttp/encode/zstd/zstd.go
index acebff5..1ec2337 100644
--- a/modules/caddyhttp/encode/zstd/zstd.go
+++ b/modules/caddyhttp/encode/zstd/zstd.go
@@ -15,6 +15,7 @@
package caddyzstd
import (
+ "github.com/caddyserver/caddy/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
"github.com/klauspost/compress/zstd"
@@ -30,6 +31,11 @@ func init() {
// Zstd can create Zstandard encoders.
type Zstd struct{}
+// UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
+func (z *Zstd) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ return nil
+}
+
// AcceptEncoding returns the name of the encoding as
// used in the Accept-Encoding request headers.
func (Zstd) AcceptEncoding() string { return "zstd" }
@@ -40,5 +46,8 @@ func (z Zstd) NewEncoder() encode.Encoder {
return writer
}
-// Interface guard
-var _ encode.Encoding = (*Zstd)(nil)
+// Interface guards
+var (
+ _ encode.Encoding = (*Zstd)(nil)
+ _ caddyfile.Unmarshaler = (*Zstd)(nil)
+)
diff --git a/modules/caddyhttp/fileserver/caddyfile.go b/modules/caddyhttp/fileserver/caddyfile.go
new file mode 100644
index 0000000..49c5728
--- /dev/null
+++ b/modules/caddyhttp/fileserver/caddyfile.go
@@ -0,0 +1,87 @@
+// Copyright 2015 Matthew Holt and The Caddy Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fileserver
+
+import (
+ "github.com/caddyserver/caddy/caddyconfig/caddyfile"
+ "github.com/caddyserver/caddy/caddyconfig/httpcaddyfile"
+)
+
+// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
+//
+// file_server [<matcher>] [browse] {
+// hide <files...>
+// index <files...>
+// browse [<template_file>]
+// root <path>
+// }
+//
+// If browse is given on the first line, it can't be used in the block also.
+// The default root is the one given by the root directive.
+func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ args := d.RemainingArgs()
+ switch len(args) {
+ case 0:
+ case 1:
+ if args[0] != "browse" {
+ return d.ArgErr()
+ }
+ fsrv.Browse = new(Browse)
+ default:
+ return d.ArgErr()
+ }
+
+ for d.NextBlock() {
+ switch d.Val() {
+ case "hide":
+ fsrv.Hide = d.RemainingArgs()
+ if len(fsrv.Hide) == 0 {
+ return d.ArgErr()
+ }
+ case "index":
+ fsrv.IndexNames = d.RemainingArgs()
+ if len(fsrv.Hide) == 0 {
+ return d.ArgErr()
+ }
+ case "root":
+ if !d.Args(&fsrv.Root) {
+ return d.ArgErr()
+ }
+ case "browse":
+ if fsrv.Browse != nil {
+ return d.Err("browsing is already configured")
+ }
+ fsrv.Browse = new(Browse)
+ d.Args(&fsrv.Browse.TemplateFile)
+ default:
+ return d.Errf("unknown subdirective '%s'", d.Val())
+ }
+ }
+ }
+
+ // if no root was configured explicitly, use site root
+ if fsrv.Root == "" {
+ fsrv.Root = "{http.var.root}"
+ }
+
+ return nil
+}
+
+// Bucket returns the HTTP Caddyfile handler bucket number.
+func (fsrv FileServer) Bucket() int { return 7 }
+
+// Interface guard
+var _ httpcaddyfile.HandlerDirective = (*FileServer)(nil)
diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go
index 17d5c11..c2e38ca 100644
--- a/modules/caddyhttp/fileserver/matcher.go
+++ b/modules/caddyhttp/fileserver/matcher.go
@@ -20,6 +20,7 @@ import (
"os"
"time"
+ "github.com/caddyserver/caddy/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
@@ -51,6 +52,39 @@ type MatchFile struct {
TryPolicy string `json:"try_policy,omitempty"`
}
+// UnmarshalCaddyfile sets up the matcher from Caddyfile tokens. Syntax:
+//
+// file {
+// root <path>
+// try_files <files...>
+// try_policy <first_exist|smallest_size|largest_size|most_recent_modified>
+// }
+//
+func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ for d.NextBlock() {
+ switch d.Val() {
+ case "root":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ m.Root = d.Val()
+ case "try_files":
+ m.TryFiles = d.RemainingArgs()
+ if len(m.TryFiles) == 0 {
+ return d.ArgErr()
+ }
+ case "try_policy":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ m.TryPolicy = d.Val()
+ }
+ }
+ }
+ return nil
+}
+
// Validate ensures m has a valid configuration.
func (m MatchFile) Validate() error {
switch m.TryPolicy {
diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go
index a66b753..1b542cf 100644
--- a/modules/caddyhttp/fileserver/staticfiles.go
+++ b/modules/caddyhttp/fileserver/staticfiles.go
@@ -48,8 +48,6 @@ type FileServer struct {
Hide []string `json:"hide,omitempty"`
IndexNames []string `json:"index_names,omitempty"`
Browse *Browse `json:"browse,omitempty"`
-
- // TODO: Content negotiation
}
// Provision sets up the static files responder.
@@ -83,7 +81,7 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ cadd
filesToHide := fsrv.transformHidePaths(repl)
- root := repl.ReplaceAll(fsrv.Root, "")
+ root := repl.ReplaceAll(fsrv.Root, ".")
suffix := repl.ReplaceAll(r.URL.Path, "")
filename := sanitizedPathJoin(root, suffix)
@@ -302,7 +300,7 @@ func calculateEtag(d os.FileInfo) string {
return `"` + t + s + `"`
}
-var defaultIndexNames = []string{"index.html"}
+var defaultIndexNames = []string{"index.html", "index.txt"}
var bufPool = sync.Pool{
New: func() interface{} {
diff --git a/modules/caddyhttp/headers/caddyfile.go b/modules/caddyhttp/headers/caddyfile.go
new file mode 100644
index 0000000..03f9e68
--- /dev/null
+++ b/modules/caddyhttp/headers/caddyfile.go
@@ -0,0 +1,92 @@
+// Copyright 2015 Matthew Holt and The Caddy Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package headers
+
+import (
+ "net/http"
+ "strings"
+
+ "github.com/caddyserver/caddy/caddyconfig/caddyfile"
+ "github.com/caddyserver/caddy/caddyconfig/httpcaddyfile"
+)
+
+// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
+//
+// headers [<matcher>] [[+|-]<field> <value>] {
+// [+][<field>] [<value>]
+// [-<field>]
+// }
+//
+// Either a block can be opened or a single header field can be configured
+// in the first line, but not both in the same directive.
+func (h *Headers) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ // first see if headers are in the initial line
+ var hasArgs bool
+ if d.NextArg() {
+ hasArgs = true
+ field := d.Val()
+ d.NextArg()
+ value := d.Val()
+ h.processCaddyfileLine(field, value)
+ }
+
+ // if not, they should be in a block
+ for d.NextBlock() {
+ if hasArgs {
+ return d.Err("cannot specify headers in both arguments and block")
+ }
+ field := d.Val()
+ var value string
+ if d.NextArg() {
+ value = d.Val()
+ }
+ h.processCaddyfileLine(field, value)
+ }
+ }
+ return nil
+}
+
+func (h *Headers) processCaddyfileLine(field, value string) {
+ if strings.HasPrefix(field, "+") {
+ if h.Response == nil {
+ h.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
+ }
+ if h.Response.Add == nil {
+ h.Response.Add = make(http.Header)
+ }
+ h.Response.Add.Set(field[1:], value)
+ } else if strings.HasPrefix(field, "-") {
+ if h.Response == nil {
+ h.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
+ }
+ h.Response.Delete = append(h.Response.Delete, field[1:])
+ h.Response.Deferred = true
+ } else {
+ if h.Response == nil {
+ h.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
+ }
+ if h.Response.Set == nil {
+ h.Response.Set = make(http.Header)
+ }
+ h.Response.Set.Set(field, value)
+ }
+}
+
+// Bucket returns the HTTP Caddyfile handler bucket number.
+func (h Headers) Bucket() int { return 3 }
+
+// Interface guard
+var _ httpcaddyfile.HandlerDirective = (*Headers)(nil)
diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go
index 6c5a23e..72b5476 100644
--- a/modules/caddyhttp/matchers.go
+++ b/modules/caddyhttp/matchers.go
@@ -28,6 +28,7 @@ import (
"strings"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/pkg/caddyscript"
"go.starlark.net/starlark"
)
@@ -125,6 +126,12 @@ func init() {
})
}
+// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
+func (m *MatchHost) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ *m = d.RemainingArgs()
+ return nil
+}
+
// Match returns true if r matches m.
func (m MatchHost) Match(r *http.Request) bool {
reqHost, _, err := net.SplitHostPort(r.Host)
@@ -177,12 +184,24 @@ func (m MatchPath) Match(r *http.Request) bool {
return false
}
+// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
+func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ *m = d.RemainingArgs()
+ return nil
+}
+
// Match returns true if r matches m.
func (m MatchPathRE) Match(r *http.Request) bool {
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
return m.MatchRegexp.Match(r.URL.Path, repl, "path_regexp")
}
+// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
+func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ *m = d.RemainingArgs()
+ return nil
+}
+
// Match returns true if r matches m.
func (m MatchMethod) Match(r *http.Request) bool {
for _, method := range m {
@@ -193,6 +212,18 @@ func (m MatchMethod) Match(r *http.Request) bool {
return false
}
+// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
+func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ parts := strings.SplitN(d.Val(), "=", 2)
+ if len(parts) != 2 {
+ return d.Errf("malformed query matcher token: %s; must be in param=val format", d.Val())
+ }
+ url.Values(*m).Set(parts[0], parts[1])
+ }
+ return nil
+}
+
// Match returns true if r matches m.
func (m MatchQuery) Match(r *http.Request) bool {
for param, vals := range m {
@@ -206,6 +237,18 @@ func (m MatchQuery) Match(r *http.Request) bool {
return false
}
+// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
+func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ var field, val string
+ if !d.Args(&field, &val) {
+ return d.Errf("expected both field and value")
+ }
+ http.Header(*m).Set(field, val)
+ }
+ return nil
+}
+
// Match returns true if r matches m.
func (m MatchHeader) Match(r *http.Request) bool {
for field, allowedFieldVals := range m {
@@ -227,6 +270,21 @@ func (m MatchHeader) Match(r *http.Request) bool {
return true
}
+// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
+func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ if *m == nil {
+ *m = make(map[string]*MatchRegexp)
+ }
+ for d.Next() {
+ var field, val string
+ if !d.Args(&field, &val) {
+ return d.ArgErr()
+ }
+ (*m)[field] = &MatchRegexp{Pattern: val}
+ }
+ return nil
+}
+
// Match returns true if r matches m.
func (m MatchHeaderRE) Match(r *http.Request) bool {
for field, rm := range m {
@@ -274,6 +332,16 @@ func (m MatchProtocol) Match(r *http.Request) bool {
return false
}
+// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
+func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ var proto string
+ if !d.Args(&proto) {
+ return d.Err("expected exactly one protocol")
+ }
+ *m = MatchProtocol(proto)
+ return nil
+}
+
// UnmarshalJSON unmarshals data into m's unexported map field.
// This is done because we cannot embed the map directly into
// the struct, but we need a struct because we need another
@@ -282,6 +350,12 @@ func (m *MatchNegate) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &m.matchersRaw)
}
+// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
+func (m *MatchNegate) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ // TODO: figure out how this will work
+ return nil
+}
+
// Provision loads the matcher modules to be negated.
func (m *MatchNegate) Provision(ctx caddy.Context) error {
for modName, rawMsg := range m.matchersRaw {
@@ -301,6 +375,12 @@ func (m MatchNegate) Match(r *http.Request) bool {
return !m.matchers.Match(r)
}
+// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
+func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ m.Ranges = d.RemainingArgs()
+ return nil
+}
+
// Provision parses m's IP ranges, either from IP or CIDR expressions.
func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
for _, str := range m.Ranges {
@@ -379,7 +459,7 @@ func (m MatchStarlarkExpr) Match(r *http.Request) bool {
// MatchRegexp is an embeddable type for matching
// using regular expressions.
type MatchRegexp struct {
- Name string `json:"name"`
+ Name string `json:"name,omitempty"`
Pattern string `json:"pattern"`
compiled *regexp.Regexp
}
@@ -431,6 +511,14 @@ func (mre *MatchRegexp) Match(input string, repl caddy.Replacer, scope string) b
return true
}
+// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
+func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ if !d.Args(&mre.Name, &mre.Pattern) {
+ return fmt.Errorf("missing arguments")
+ }
+ return nil
+}
+
// ResponseMatcher is a type which can determine if a given response
// status code and its headers match some criteria.
type ResponseMatcher struct {
@@ -506,4 +594,14 @@ var (
_ caddy.Provisioner = (*MatchNegate)(nil)
_ RequestMatcher = (*MatchStarlarkExpr)(nil)
_ caddy.Provisioner = (*MatchRegexp)(nil)
+
+ _ caddyfile.Unmarshaler = (*MatchHost)(nil)
+ _ caddyfile.Unmarshaler = (*MatchPath)(nil)
+ _ caddyfile.Unmarshaler = (*MatchPathRE)(nil)
+ _ caddyfile.Unmarshaler = (*MatchMethod)(nil)
+ _ caddyfile.Unmarshaler = (*MatchQuery)(nil)
+ _ caddyfile.Unmarshaler = (*MatchHeader)(nil)
+ _ caddyfile.Unmarshaler = (*MatchHeaderRE)(nil)
+ _ caddyfile.Unmarshaler = (*MatchProtocol)(nil)
+ _ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil)
)
diff --git a/modules/caddyhttp/replacer.go b/modules/caddyhttp/replacer.go
index 439d245..cc29789 100644
--- a/modules/caddyhttp/replacer.go
+++ b/modules/caddyhttp/replacer.go
@@ -15,6 +15,7 @@
package caddyhttp
import (
+ "fmt"
"net"
"net/http"
"net/textproto"
@@ -28,6 +29,7 @@ import (
func addHTTPVarsToReplacer(repl caddy.Replacer, req *http.Request, w http.ResponseWriter) {
httpVars := func(key string) (string, bool) {
if req != nil {
+ // query string parameters
if strings.HasPrefix(key, queryReplPrefix) {
vals := req.URL.Query()[key[len(queryReplPrefix):]]
// always return true, since the query param might
@@ -35,6 +37,7 @@ func addHTTPVarsToReplacer(repl caddy.Replacer, req *http.Request, w http.Respon
return strings.Join(vals, ","), true
}
+ // request header fields
if strings.HasPrefix(key, reqHeaderReplPrefix) {
field := key[len(reqHeaderReplPrefix):]
vals := req.Header[textproto.CanonicalMIMEHeaderKey(field)]
@@ -43,6 +46,7 @@ func addHTTPVarsToReplacer(repl caddy.Replacer, req *http.Request, w http.Respon
return strings.Join(vals, ","), true
}
+ // cookies
if strings.HasPrefix(key, cookieReplPrefix) {
name := key[len(cookieReplPrefix):]
for _, cookie := range req.Cookies() {
@@ -87,14 +91,7 @@ func addHTTPVarsToReplacer(repl caddy.Replacer, req *http.Request, w http.Respon
return req.URL.RawQuery, true
}
- if strings.HasPrefix(key, respHeaderReplPrefix) {
- field := key[len(respHeaderReplPrefix):]
- vals := w.Header()[textproto.CanonicalMIMEHeaderKey(field)]
- // always return true, since the header field might
- // be present only in some requests
- return strings.Join(vals, ","), true
- }
-
+ // hostname labels
if strings.HasPrefix(key, hostLabelReplPrefix) {
idxStr := key[len(hostLabelReplPrefix):]
idx, err := strconv.Atoi(idxStr)
@@ -111,6 +108,7 @@ func addHTTPVarsToReplacer(repl caddy.Replacer, req *http.Request, w http.Respon
return hostLabels[idx], true
}
+ // path parts
if strings.HasPrefix(key, pathPartsReplPrefix) {
idxStr := key[len(pathPartsReplPrefix):]
idx, err := strconv.Atoi(idxStr)
@@ -129,9 +127,31 @@ func addHTTPVarsToReplacer(repl caddy.Replacer, req *http.Request, w http.Respon
}
return pathParts[idx], true
}
+
+ // middleware variables
+ if strings.HasPrefix(key, varsReplPrefix) {
+ varName := key[len(varsReplPrefix):]
+ tbl := req.Context().Value(VarCtxKey).(map[string]interface{})
+ raw, ok := tbl[varName]
+ if !ok {
+ // variables can be dynamic, so always return true
+ // even when it may not be set; treat as empty
+ return "", true
+ }
+ // do our best to convert it to a string efficiently
+ switch val := raw.(type) {
+ case string:
+ return val, true
+ case fmt.Stringer:
+ return val.String(), true
+ default:
+ return fmt.Sprintf("%s", val), true
+ }
+ }
}
if w != nil {
+ // response header fields
if strings.HasPrefix(key, respHeaderReplPrefix) {
field := key[len(respHeaderReplPrefix):]
vals := w.Header()[textproto.CanonicalMIMEHeaderKey(field)]
@@ -153,5 +173,6 @@ const (
cookieReplPrefix = "http.request.cookie."
hostLabelReplPrefix = "http.request.host.labels."
pathPartsReplPrefix = "http.request.uri.path."
+ varsReplPrefix = "http.var."
respHeaderReplPrefix = "http.response.header."
)
diff --git a/modules/caddyhttp/reverseproxy/module.go b/modules/caddyhttp/reverseproxy/module.go
index 2e6a338..0bae58e 100755
--- a/modules/caddyhttp/reverseproxy/module.go
+++ b/modules/caddyhttp/reverseproxy/module.go
@@ -15,6 +15,8 @@
package reverseproxy
import (
+ "github.com/caddyserver/caddy/caddyconfig/caddyfile"
+ "github.com/caddyserver/caddy/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2"
)
@@ -25,3 +27,27 @@ func init() {
New: func() interface{} { return new(LoadBalanced) },
})
}
+
+// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
+//
+// proxy [<matcher>] <to>
+//
+// TODO: This needs to be finished. It definitely needs to be able to open a block...
+func (lb *LoadBalanced) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ allTo := d.RemainingArgs()
+ if len(allTo) == 0 {
+ return d.ArgErr()
+ }
+ for _, to := range allTo {
+ lb.Upstreams = append(lb.Upstreams, &UpstreamConfig{Host: to})
+ }
+ }
+ return nil
+}
+
+// Bucket returns the HTTP Caddyfile handler bucket number.
+func (*LoadBalanced) Bucket() int { return 7 }
+
+// Interface guard
+var _ httpcaddyfile.HandlerDirective = (*LoadBalanced)(nil)
diff --git a/modules/caddyhttp/reverseproxy/upstream.go b/modules/caddyhttp/reverseproxy/upstream.go
index 10df80b..1f0693e 100755
--- a/modules/caddyhttp/reverseproxy/upstream.go
+++ b/modules/caddyhttp/reverseproxy/upstream.go
@@ -179,21 +179,21 @@ type LoadBalanced struct {
// The following struct fields are set by caddy configuration.
// TryInterval is the max duration for which request retrys will be performed for a request.
- TryInterval string `json:"try_interval"`
+ TryInterval string `json:"try_interval,omitempty"`
// Upstreams are the configs for upstream hosts
- Upstreams []*UpstreamConfig `json:"upstreams"`
+ Upstreams []*UpstreamConfig `json:"upstreams,omitempty"`
// LoadBalanceType is the string representation of what loadbalancing algorithm to use. i.e. "random" or "round_robin".
- LoadBalanceType string `json:"load_balance_type"`
+ LoadBalanceType string `json:"load_balance_type,omitempty"`
// NoHealthyUpstreamsMessage is returned as a response when there are no healthy upstreams to loadbalance to.
- NoHealthyUpstreamsMessage string `json:"no_healthy_upstreams_message"`
+ NoHealthyUpstreamsMessage string `json:"no_healthy_upstreams_message,omitempty"`
// TODO :- store healthcheckers as package level state where each upstream gets a single healthchecker
// currently a healthchecker is created for each upstream defined, even if a healthchecker was previously created
// for that upstream
- HealthCheckers []*HealthChecker
+ HealthCheckers []*HealthChecker `json:"health_checkers,omitempty"`
}
// Cleanup stops all health checkers on a loadbalanced reverse proxy.
@@ -320,22 +320,22 @@ func (lb *LoadBalanced) random() *upstream {
// UpstreamConfig represents the config of an upstream.
type UpstreamConfig struct {
// Host is the host name of the upstream server.
- Host string `json:"host"`
+ Host string `json:"host,omitempty"`
// FastHealthCheckDuration is the duration for which a health check is performed when a node is considered unhealthy.
- FastHealthCheckDuration string `json:"fast_health_check_duration"`
+ FastHealthCheckDuration string `json:"fast_health_check_duration,omitempty"`
- CircuitBreaker json.RawMessage `json:"circuit_breaker"`
+ CircuitBreaker json.RawMessage `json:"circuit_breaker,omitempty"`
// // CircuitBreakerConfig is the config passed to setup a circuit breaker.
- // CircuitBreakerConfig *circuitbreaker.Config `json:"circuit_breaker"`
+ // CircuitBreakerConfig *circuitbreaker.Config `json:"circuit_breaker,omitempty"`
circuitbreaker CircuitBreaker
// HealthCheckDuration is the default duration for which a health check is performed.
- HealthCheckDuration string `json:"health_check_duration"`
+ HealthCheckDuration string `json:"health_check_duration,omitempty"`
// HealthCheckPath is the path at the upstream host to use for healthchecks.
- HealthCheckPath string `json:"health_check_path"`
+ HealthCheckPath string `json:"health_check_path,omitempty"`
}
// upstream represents an upstream host.
diff --git a/modules/caddyhttp/rewrite/caddyfile.go b/modules/caddyhttp/rewrite/caddyfile.go
new file mode 100644
index 0000000..e2111a2
--- /dev/null
+++ b/modules/caddyhttp/rewrite/caddyfile.go
@@ -0,0 +1,38 @@
+// Copyright 2015 Matthew Holt and The Caddy Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package rewrite
+
+import (
+ "github.com/caddyserver/caddy/caddyconfig/caddyfile"
+ "github.com/caddyserver/caddy/caddyconfig/httpcaddyfile"
+)
+
+// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
+//
+// rewrite [<matcher>] <to>
+//
+// The <to> parameter becomes the new URI.
+func (rewr *Rewrite) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ rewr.URI = d.Val()
+ }
+ return nil
+}
+
+// Bucket returns the HTTP Caddyfile handler bucket number.
+func (rewr Rewrite) Bucket() int { return 1 }
+
+// Interface guard
+var _ httpcaddyfile.HandlerDirective = (*Rewrite)(nil)
diff --git a/modules/caddyhttp/routes.go b/modules/caddyhttp/routes.go
index b0672b1..ffa7ce7 100644
--- a/modules/caddyhttp/routes.go
+++ b/modules/caddyhttp/routes.go
@@ -22,10 +22,10 @@ import (
"github.com/caddyserver/caddy/v2"
)
-// ServerRoute represents a set of matching rules,
+// Route represents a set of matching rules,
// middlewares, and a responder for handling HTTP
// requests.
-type ServerRoute struct {
+type Route struct {
Group string `json:"group,omitempty"`
MatcherSets []map[string]json.RawMessage `json:"match,omitempty"`
Handle []json.RawMessage `json:"handle,omitempty"`
@@ -37,22 +37,22 @@ type ServerRoute struct {
}
// Empty returns true if the route has all zero/default values.
-func (sr ServerRoute) Empty() bool {
- return len(sr.MatcherSets) == 0 &&
- len(sr.Handle) == 0 &&
- len(sr.handlers) == 0 &&
- !sr.Terminal &&
- sr.Group == ""
+func (r Route) Empty() bool {
+ return len(r.MatcherSets) == 0 &&
+ len(r.Handle) == 0 &&
+ len(r.handlers) == 0 &&
+ !r.Terminal &&
+ r.Group == ""
}
-func (sr ServerRoute) anyMatcherSetMatches(r *http.Request) bool {
- for _, ms := range sr.matcherSets {
- if ms.Match(r) {
+func (r Route) anyMatcherSetMatches(req *http.Request) bool {
+ for _, ms := range r.matcherSets {
+ if ms.Match(req) {
return true
}
}
// if no matchers, always match
- return len(sr.matcherSets) == 0
+ return len(r.matcherSets) == 0
}
// MatcherSet is a set of matchers which
@@ -73,7 +73,7 @@ func (mset MatcherSet) Match(r *http.Request) bool {
// RouteList is a list of server routes that can
// create a middleware chain.
-type RouteList []ServerRoute
+type RouteList []Route
// Provision sets up all the routes by loading the modules.
func (routes RouteList) Provision(ctx caddy.Context) error {
diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go
index d79d8d3..f820f71 100644
--- a/modules/caddyhttp/server.go
+++ b/modules/caddyhttp/server.go
@@ -57,7 +57,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
repl := caddy.NewReplacer()
ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, repl)
ctx = context.WithValue(ctx, ServerCtxKey, s)
- ctx = context.WithValue(ctx, TableCtxKey, make(map[string]interface{})) // TODO: Implement this
+ ctx = context.WithValue(ctx, VarCtxKey, make(map[string]interface{}))
r = r.WithContext(ctx)
// once the pointer to the request won't change
@@ -201,6 +201,14 @@ type AutoHTTPSConfig struct {
// that certificates will not be provisioned and managed
// for these names.
SkipCerts []string `json:"skip_certificates,omitempty"`
+
+ // By default, automatic HTTPS will obtain and renew
+ // certificates for qualifying hostnames. However, if
+ // a certificate with a matching SAN is already loaded
+ // into the cache, certificate management will not be
+ // enabled. To force automated certificate management
+ // regardless of loaded certificates, set this to true.
+ IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"`
}
// Skipped returns true if name is in skipSlice, which
@@ -225,6 +233,6 @@ const (
// For referencing the server instance
ServerCtxKey caddy.CtxKey = "server"
- // For the request's variable table (TODO: implement this)
- TableCtxKey caddy.CtxKey = "table"
+ // For the request's variable table
+ VarCtxKey caddy.CtxKey = "vars"
)
diff --git a/modules/caddyhttp/staticerror.go b/modules/caddyhttp/staticerror.go
index 3a8e8bc..1834cf7 100644
--- a/modules/caddyhttp/staticerror.go
+++ b/modules/caddyhttp/staticerror.go
@@ -18,7 +18,6 @@ import (
"fmt"
"net/http"
"strconv"
- "strings"
"github.com/caddyserver/caddy/v2"
)
@@ -33,7 +32,7 @@ func init() {
// StaticError implements a simple handler that returns an error.
type StaticError struct {
Error string `json:"error,omitempty"`
- StatusCode weakString `json:"status_code,omitempty"`
+ StatusCode WeakString `json:"status_code,omitempty"`
}
func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
@@ -53,43 +52,3 @@ func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler
// Interface guard
var _ MiddlewareHandler = (*StaticError)(nil)
-
-// weakString is a type that unmarshals any JSON value
-// as a string literal, and provides methods for
-// getting the value as different primitive types.
-// However, using this type removes any type safety
-// as far as deserializing JSON is concerned.
-type weakString string
-
-// UnmarshalJSON satisfies json.Unmarshaler. It
-// unmarshals b by always interpreting it as a
-// string literal.
-func (ws *weakString) UnmarshalJSON(b []byte) error {
- *ws = weakString(strings.Trim(string(b), `"`))
- return nil
-}
-
-// Int returns ws as an integer. If ws is not an
-// integer, 0 is returned.
-func (ws weakString) Int() int {
- num, _ := strconv.Atoi(string(ws))
- return num
-}
-
-// Float64 returns ws as a float64. If ws is not a
-// float value, the zero value is returned.
-func (ws weakString) Float64() float64 {
- num, _ := strconv.ParseFloat(string(ws), 64)
- return num
-}
-
-// Bool returns ws as a boolean. If ws is not a
-// boolean, false is returned.
-func (ws weakString) Bool() bool {
- return string(ws) == "true"
-}
-
-// String returns ws as a string.
-func (ws weakString) String() string {
- return string(ws)
-}
diff --git a/modules/caddyhttp/staticresp.go b/modules/caddyhttp/staticresp.go
index 291d992..cafee35 100644
--- a/modules/caddyhttp/staticresp.go
+++ b/modules/caddyhttp/staticresp.go
@@ -20,6 +20,7 @@ import (
"strconv"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
)
func init() {
@@ -31,12 +32,48 @@ func init() {
// StaticResponse implements a simple responder for static responses.
type StaticResponse struct {
- StatusCode weakString `json:"status_code"`
- Headers http.Header `json:"headers"`
- Body string `json:"body"`
- Close bool `json:"close"`
+ StatusCode WeakString `json:"status_code,omitempty"`
+ Headers http.Header `json:"headers,omitempty"`
+ Body string `json:"body,omitempty"`
+ Close bool `json:"close,omitempty"`
}
+// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
+//
+// static_response [<matcher>] <status> {
+// body <text>
+// close
+// }
+//
+func (s *StaticResponse) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ var statusCodeStr string
+ if d.Args(&statusCodeStr) {
+ s.StatusCode = WeakString(statusCodeStr)
+ }
+ for d.NextBlock() {
+ switch d.Val() {
+ case "body":
+ if s.Body != "" {
+ return d.Err("body already specified")
+ }
+ if !d.Args(&s.Body) {
+ return d.ArgErr()
+ }
+ case "close":
+ if s.Close {
+ return d.Err("close already specified")
+ }
+ s.Close = true
+ }
+ }
+ }
+ return nil
+}
+
+// Bucket returns the HTTP Caddyfile handler bucket number.
+func (StaticResponse) Bucket() int { return 7 }
+
func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
diff --git a/modules/caddyhttp/staticresp_test.go b/modules/caddyhttp/staticresp_test.go
index 49adedd..cd0d1a1 100644
--- a/modules/caddyhttp/staticresp_test.go
+++ b/modules/caddyhttp/staticresp_test.go
@@ -30,7 +30,7 @@ func TestStaticResponseHandler(t *testing.T) {
w := httptest.NewRecorder()
s := StaticResponse{
- StatusCode: weakString(strconv.Itoa(http.StatusNotFound)),
+ StatusCode: WeakString(strconv.Itoa(http.StatusNotFound)),
Headers: http.Header{
"X-Test": []string{"Testing"},
},
diff --git a/modules/caddyhttp/table.go b/modules/caddyhttp/table.go
deleted file mode 100644
index 5b1fed5..0000000
--- a/modules/caddyhttp/table.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2015 Matthew Holt and The Caddy Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package caddyhttp
-
-import (
- "net/http"
-
- "github.com/caddyserver/caddy/v2"
-)
-
-func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "http.handlers.table",
- New: func() interface{} { return new(tableMiddleware) },
- })
-
- caddy.RegisterModule(caddy.Module{
- Name: "http.matchers.table",
- New: func() interface{} { return new(tableMatcher) },
- })
-}
-
-type tableMiddleware struct {
-}
-
-func (t tableMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error {
- // tbl := r.Context().Value(TableCtxKey).(map[string]interface{})
-
- // TODO: implement this...
-
- return nil
-}
-
-type tableMatcher struct {
-}
-
-func (m tableMatcher) Match(r *http.Request) bool {
- return false // TODO: implement
-}
-
-// Interface guards
-var _ MiddlewareHandler = (*tableMiddleware)(nil)
-var _ RequestMatcher = (*tableMatcher)(nil)
diff --git a/modules/caddyhttp/templates/caddyfile.go b/modules/caddyhttp/templates/caddyfile.go
new file mode 100644
index 0000000..50bb3f8
--- /dev/null
+++ b/modules/caddyhttp/templates/caddyfile.go
@@ -0,0 +1,63 @@
+// Copyright 2015 Matthew Holt and The Caddy Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+ "github.com/caddyserver/caddy/caddyconfig/caddyfile"
+ "github.com/caddyserver/caddy/caddyconfig/httpcaddyfile"
+)
+
+// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
+//
+// templates [<matcher>] {
+// mime <types...>
+// between <open_delim> <close_delim>
+// root <path>
+// }
+//
+func (t *Templates) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ for d.NextBlock() {
+ switch d.Val() {
+ case "mime":
+ t.MIMETypes = d.RemainingArgs()
+ if len(t.MIMETypes) == 0 {
+ return d.ArgErr()
+ }
+ case "between":
+ t.Delimiters = d.RemainingArgs()
+ if len(t.Delimiters) != 2 {
+ return d.ArgErr()
+ }
+ case "root":
+ if !d.Args(&t.IncludeRoot) {
+ return d.ArgErr()
+ }
+ }
+ }
+ }
+
+ if t.IncludeRoot == "" {
+ t.IncludeRoot = "{http.var.root}"
+ }
+
+ return nil
+}
+
+// Bucket returns the HTTP Caddyfile handler bucket number.
+func (t Templates) Bucket() int { return 5 }
+
+// Interface guard
+var _ httpcaddyfile.HandlerDirective = (*Templates)(nil)
diff --git a/modules/caddyhttp/templates/templates.go b/modules/caddyhttp/templates/templates.go
index 9a41b6d..442e177 100644
--- a/modules/caddyhttp/templates/templates.go
+++ b/modules/caddyhttp/templates/templates.go
@@ -108,7 +108,8 @@ func (t *Templates) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy
func (t *Templates) executeTemplate(rr caddyhttp.ResponseRecorder, r *http.Request) error {
var fs http.FileSystem
if t.IncludeRoot != "" {
- fs = http.Dir(t.IncludeRoot)
+ repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
+ fs = http.Dir(repl.ReplaceAll(t.IncludeRoot, "."))
}
ctx := &templateContext{
diff --git a/modules/caddyhttp/templates/tplcontext.go b/modules/caddyhttp/templates/tplcontext.go
index ffcc636..a51e54b 100644
--- a/modules/caddyhttp/templates/tplcontext.go
+++ b/modules/caddyhttp/templates/tplcontext.go
@@ -136,19 +136,6 @@ func (c templateContext) Cookie(name string) string {
return ""
}
-// Hostname gets the (remote) hostname of the client making the request.
-// Performance warning: This involves a DNS lookup.
-func (c templateContext) Hostname() string {
- ip := c.RemoteIP()
-
- hostnameList, err := net.LookupAddr(ip)
- if err != nil || len(hostnameList) == 0 {
- return c.Req.RemoteAddr
- }
-
- return hostnameList[0]
-}
-
// RemoteIP gets the IP address of the client making the request.
func (c templateContext) RemoteIP() string {
ip, _, err := net.SplitHostPort(c.Req.RemoteAddr)
diff --git a/modules/caddyhttp/vars.go b/modules/caddyhttp/vars.go
new file mode 100644
index 0000000..f74556a
--- /dev/null
+++ b/modules/caddyhttp/vars.go
@@ -0,0 +1,71 @@
+// Copyright 2015 Matthew Holt and The Caddy Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package caddyhttp
+
+import (
+ "net/http"
+
+ "github.com/caddyserver/caddy/v2"
+)
+
+func init() {
+ caddy.RegisterModule(caddy.Module{
+ Name: "http.handlers.vars",
+ New: func() interface{} { return new(VarsMiddleware) },
+ })
+ caddy.RegisterModule(caddy.Module{
+ Name: "http.matchers.vars",
+ New: func() interface{} { return new(VarsMiddleware) },
+ })
+}
+
+// VarsMiddleware is an HTTP middleware which sets variables
+// in the context, mainly for use by placeholders.
+type VarsMiddleware map[string]string
+
+func (t VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error {
+ vars := r.Context().Value(VarCtxKey).(map[string]interface{})
+ repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
+ for k, v := range t {
+ keyExpanded := repl.ReplaceAll(k, "")
+ valExpanded := repl.ReplaceAll(v, "")
+ vars[keyExpanded] = valExpanded
+ }
+ return next.ServeHTTP(w, r)
+}
+
+// VarsMatcher is an HTTP request matcher which can match
+// requests based on variables in the context.
+type VarsMatcher map[string]string
+
+// Match matches a request based on variables in the context.
+func (m VarsMatcher) Match(r *http.Request) bool {
+ vars := r.Context().Value(VarCtxKey).(map[string]string)
+ repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
+ for k, v := range m {
+ keyExpanded := repl.ReplaceAll(k, "")
+ valExpanded := repl.ReplaceAll(v, "")
+ if vars[keyExpanded] != valExpanded {
+ return false
+ }
+ }
+ return true
+}
+
+// Interface guards
+var (
+ _ MiddlewareHandler = (*VarsMiddleware)(nil)
+ _ RequestMatcher = (*VarsMatcher)(nil)
+)