mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-26 21:53:48 +03:00
Rename and export some types, other minor changes
This commit is contained in:
parent
8ae0d6a509
commit
f9d93ead4e
7 changed files with 151 additions and 106 deletions
|
@ -22,29 +22,31 @@ func init() {
|
|||
|
||||
err := caddy2.RegisterModule(caddy2.Module{
|
||||
Name: "http",
|
||||
New: func() (interface{}, error) { return new(httpModuleConfig), nil },
|
||||
New: func() (interface{}, error) { return new(App), nil },
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
type httpModuleConfig struct {
|
||||
HTTPPort int `json:"http_port"`
|
||||
HTTPSPort int `json:"https_port"`
|
||||
GracePeriod caddy2.Duration `json:"grace_period"`
|
||||
Servers map[string]*httpServerConfig `json:"servers"`
|
||||
// App is the HTTP app for Caddy.
|
||||
type App struct {
|
||||
HTTPPort int `json:"http_port"`
|
||||
HTTPSPort int `json:"https_port"`
|
||||
GracePeriod caddy2.Duration `json:"grace_period"`
|
||||
Servers map[string]*Server `json:"servers"`
|
||||
|
||||
servers []*http.Server
|
||||
}
|
||||
|
||||
func (hc *httpModuleConfig) Provision() error {
|
||||
// Provision sets up the app.
|
||||
func (hc *App) Provision() error {
|
||||
for _, srv := range hc.Servers {
|
||||
err := srv.Routes.setup()
|
||||
err := srv.Routes.Provision()
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting up server routes: %v", err)
|
||||
}
|
||||
err = srv.Errors.Routes.setup()
|
||||
err = srv.Errors.Routes.Provision()
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting up server error handling routes: %v", err)
|
||||
}
|
||||
|
@ -53,7 +55,8 @@ func (hc *httpModuleConfig) Provision() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (hc *httpModuleConfig) Validate() error {
|
||||
// Validate ensures the app's configuration is valid.
|
||||
func (hc *App) Validate() error {
|
||||
// each server must use distinct listener addresses
|
||||
lnAddrs := make(map[string]string)
|
||||
for srvName, srv := range hc.Servers {
|
||||
|
@ -74,7 +77,8 @@ func (hc *httpModuleConfig) Validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (hc *httpModuleConfig) Start(handle caddy2.Handle) error {
|
||||
// Start runs the app. It sets up automatic HTTPS if enabled.
|
||||
func (hc *App) Start(handle caddy2.Handle) error {
|
||||
err := hc.automaticHTTPS(handle)
|
||||
if err != nil {
|
||||
return fmt.Errorf("enabling automatic HTTPS: %v", err)
|
||||
|
@ -129,7 +133,7 @@ func (hc *httpModuleConfig) Start(handle caddy2.Handle) error {
|
|||
}
|
||||
|
||||
// Stop gracefully shuts down the HTTP server.
|
||||
func (hc *httpModuleConfig) Stop() error {
|
||||
func (hc *App) Stop() error {
|
||||
ctx := context.Background()
|
||||
if hc.GracePeriod > 0 {
|
||||
var cancel context.CancelFunc
|
||||
|
@ -145,7 +149,7 @@ func (hc *httpModuleConfig) Stop() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (hc *httpModuleConfig) automaticHTTPS(handle caddy2.Handle) error {
|
||||
func (hc *App) automaticHTTPS(handle caddy2.Handle) error {
|
||||
tlsAppIface, err := handle.App("tls")
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting tls app: %v", err)
|
||||
|
@ -153,7 +157,7 @@ func (hc *httpModuleConfig) automaticHTTPS(handle caddy2.Handle) error {
|
|||
tlsApp := tlsAppIface.(*caddytls.TLS)
|
||||
|
||||
lnAddrMap := make(map[string]struct{})
|
||||
var redirRoutes routeList
|
||||
var redirRoutes RouteList
|
||||
|
||||
for srvName, srv := range hc.Servers {
|
||||
srv.tlsApp = tlsApp
|
||||
|
@ -222,7 +226,7 @@ func (hc *httpModuleConfig) automaticHTTPS(handle caddy2.Handle) error {
|
|||
}
|
||||
redirTo += "{request.uri}"
|
||||
|
||||
redirRoutes = append(redirRoutes, serverRoute{
|
||||
redirRoutes = append(redirRoutes, ServerRoute{
|
||||
matchers: []RouteMatcher{
|
||||
matchProtocol("http"),
|
||||
matchHost(domains),
|
||||
|
@ -255,7 +259,7 @@ func (hc *httpModuleConfig) automaticHTTPS(handle caddy2.Handle) error {
|
|||
}
|
||||
lnAddrs = append(lnAddrs, addr)
|
||||
}
|
||||
hc.Servers["auto_https_redirects"] = &httpServerConfig{
|
||||
hc.Servers["auto_https_redirects"] = &Server{
|
||||
Listen: lnAddrs,
|
||||
Routes: redirRoutes,
|
||||
DisableAutoHTTPS: true,
|
||||
|
@ -265,7 +269,7 @@ func (hc *httpModuleConfig) automaticHTTPS(handle caddy2.Handle) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (hc *httpModuleConfig) listenerTaken(network, address string) bool {
|
||||
func (hc *App) listenerTaken(network, address string) bool {
|
||||
for _, srv := range hc.Servers {
|
||||
for _, addr := range srv.Listen {
|
||||
netw, addrs, err := parseListenAddr(addr)
|
||||
|
@ -284,12 +288,13 @@ func (hc *httpModuleConfig) listenerTaken(network, address string) bool {
|
|||
|
||||
var defaultALPN = []string{"h2", "http/1.1"}
|
||||
|
||||
type httpServerConfig struct {
|
||||
// Server is an HTTP server.
|
||||
type Server struct {
|
||||
Listen []string `json:"listen"`
|
||||
ReadTimeout caddy2.Duration `json:"read_timeout"`
|
||||
ReadHeaderTimeout caddy2.Duration `json:"read_header_timeout"`
|
||||
HiddenFiles []string `json:"hidden_files"` // TODO:... experimenting with shared/common state
|
||||
Routes routeList `json:"routes"`
|
||||
Routes RouteList `json:"routes"`
|
||||
Errors httpErrorConfig `json:"errors"`
|
||||
TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies"`
|
||||
DisableAutoHTTPS bool `json:"disable_auto_https"`
|
||||
|
@ -299,23 +304,24 @@ type httpServerConfig struct {
|
|||
}
|
||||
|
||||
type httpErrorConfig struct {
|
||||
Routes routeList `json:"routes"`
|
||||
// TODO: some way to configure the logging of errors, probably? standardize the logging configuration first.
|
||||
Routes RouteList `json:"routes"`
|
||||
// TODO: some way to configure the logging of errors, probably? standardize
|
||||
// the logging configuration first.
|
||||
}
|
||||
|
||||
// ServeHTTP is the entry point for all HTTP requests.
|
||||
func (s httpServerConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if s.tlsApp.HandleHTTPChallenge(w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
// set up the replacer
|
||||
repl := &Replacer{req: r, resp: w, custom: make(map[string]string)}
|
||||
repl := NewReplacer(r, w)
|
||||
ctx := context.WithValue(r.Context(), ReplacerCtxKey, repl)
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
// build and execute the main middleware chain
|
||||
stack := s.Routes.buildMiddlewareChain(w, r)
|
||||
stack := s.Routes.BuildHandlerChain(w, r)
|
||||
err := executeMiddlewareChain(w, r, stack)
|
||||
if err != nil {
|
||||
// add the error value to the request context so
|
||||
|
@ -328,7 +334,7 @@ func (s httpServerConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
// TODO: implement a default error handler?
|
||||
log.Printf("[ERROR] %s", err)
|
||||
} else {
|
||||
errStack := s.Errors.Routes.buildMiddlewareChain(w, r)
|
||||
errStack := s.Errors.Routes.BuildHandlerChain(w, r)
|
||||
err := executeMiddlewareChain(w, r, errStack)
|
||||
if err != nil {
|
||||
// TODO: what should we do if the error handler has an error?
|
||||
|
@ -411,6 +417,7 @@ func parseListenAddr(a string) (network string, addrs []string, err error) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
host = NewReplacer(nil, nil).Replace(host, "")
|
||||
ports := strings.SplitN(port, "-", 2)
|
||||
if len(ports) == 1 {
|
||||
ports = append(ports, ports[0])
|
||||
|
@ -474,9 +481,6 @@ func (mrw middlewareResponseWriter) Write(b []byte) (int, error) {
|
|||
return mrw.ResponseWriterWrapper.Write(b)
|
||||
}
|
||||
|
||||
// ReplacerCtxKey is the context key for the request's replacer.
|
||||
const ReplacerCtxKey caddy2.CtxKey = "replacer"
|
||||
|
||||
const (
|
||||
// DefaultHTTPPort is the default port for HTTP.
|
||||
DefaultHTTPPort = 80
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package caddyhttp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
@ -114,6 +115,11 @@ func TestJoinListenerAddr(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseListenerAddr(t *testing.T) {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot ascertain system hostname: %v", err)
|
||||
}
|
||||
|
||||
for i, tc := range []struct {
|
||||
input string
|
||||
expectNetwork string
|
||||
|
@ -170,6 +176,11 @@ func TestParseListenerAddr(t *testing.T) {
|
|||
expectNetwork: "tcp",
|
||||
expectAddrs: []string{"localhost:0"},
|
||||
},
|
||||
{
|
||||
input: "{system.hostname}:0",
|
||||
expectNetwork: "tcp",
|
||||
expectAddrs: []string{hostname + ":0"},
|
||||
},
|
||||
} {
|
||||
actualNetwork, actualAddrs, err := parseListenAddr(tc.input)
|
||||
if tc.expectErr && err == nil {
|
||||
|
|
|
@ -205,7 +205,7 @@ func TestPathREMatcher(t *testing.T) {
|
|||
|
||||
// set up the fake request and its Replacer
|
||||
req := &http.Request{URL: &url.URL{Path: tc.input}}
|
||||
repl := &Replacer{req: req, resp: httptest.NewRecorder(), custom: make(map[string]string)}
|
||||
repl := NewReplacer(req, httptest.NewRecorder())
|
||||
ctx := context.WithValue(req.Context(), ReplacerCtxKey, repl)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
|
@ -322,7 +322,7 @@ func TestHeaderREMatcher(t *testing.T) {
|
|||
|
||||
// set up the fake request and its Replacer
|
||||
req := &http.Request{Header: tc.input, URL: new(url.URL)}
|
||||
repl := &Replacer{req: req, resp: httptest.NewRecorder(), custom: make(map[string]string)}
|
||||
repl := NewReplacer(req, httptest.NewRecorder())
|
||||
ctx := context.WithValue(req.Context(), ReplacerCtxKey, repl)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
|
|
|
@ -5,18 +5,31 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"bitbucket.org/lightcodelabs/caddy2"
|
||||
)
|
||||
|
||||
// Replacer can replace values in strings based
|
||||
// on a request and/or response writer. The zero
|
||||
// Replacer is not valid; it must be initialized
|
||||
// within this package.
|
||||
// Replacer is not valid; use NewReplacer() to
|
||||
// initialize one.
|
||||
type Replacer struct {
|
||||
req *http.Request
|
||||
resp http.ResponseWriter
|
||||
custom map[string]string
|
||||
}
|
||||
|
||||
// NewReplacer makes a new Replacer, initializing all necessary
|
||||
// fields. The request and response writer are optional, but
|
||||
// necessary for most replacements to work.
|
||||
func NewReplacer(req *http.Request, rw http.ResponseWriter) *Replacer {
|
||||
return &Replacer{
|
||||
req: req,
|
||||
resp: rw,
|
||||
custom: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// Map sets a custom variable mapping to a value.
|
||||
func (r *Replacer) Map(variable, value string) {
|
||||
r.custom[variable] = value
|
||||
|
@ -48,28 +61,6 @@ func (r *Replacer) replaceAll(input, empty string, mapping map[string]string) st
|
|||
|
||||
func (r *Replacer) defaults() map[string]string {
|
||||
m := map[string]string{
|
||||
"request.host": func() string {
|
||||
host, _, err := net.SplitHostPort(r.req.Host)
|
||||
if err != nil {
|
||||
return r.req.Host // OK; there probably was no port
|
||||
}
|
||||
return host
|
||||
}(),
|
||||
"request.hostport": r.req.Host, // may include both host and port
|
||||
"request.method": r.req.Method,
|
||||
"request.port": func() string {
|
||||
// if there is no port, there will be an error; in
|
||||
// that case, port is the empty string anyway
|
||||
_, port, _ := net.SplitHostPort(r.req.Host)
|
||||
return port
|
||||
}(),
|
||||
"request.scheme": func() string {
|
||||
if r.req.TLS != nil {
|
||||
return "https"
|
||||
}
|
||||
return "http"
|
||||
}(),
|
||||
"request.uri": r.req.URL.RequestURI(),
|
||||
"system.hostname": func() string {
|
||||
// OK if there is an error; just return empty string
|
||||
name, _ := os.Hostname()
|
||||
|
@ -77,22 +68,51 @@ func (r *Replacer) defaults() map[string]string {
|
|||
}(),
|
||||
}
|
||||
|
||||
// TODO: why should header fields, cookies, and query params get special treatment like this?
|
||||
// maybe they should be scoped by words like "request.header." just like everything else.
|
||||
for field, vals := range r.req.Header {
|
||||
m[">"+strings.ToLower(field)] = strings.Join(vals, ",")
|
||||
}
|
||||
for field, vals := range r.resp.Header() {
|
||||
m["<"+strings.ToLower(field)] = strings.Join(vals, ",")
|
||||
}
|
||||
for _, cookie := range r.req.Cookies() {
|
||||
m["~"+cookie.Name] = cookie.Value
|
||||
}
|
||||
for param, vals := range r.req.URL.Query() {
|
||||
m["?"+param] = strings.Join(vals, ",")
|
||||
if r.req != nil {
|
||||
m["request.host"] = func() string {
|
||||
host, _, err := net.SplitHostPort(r.req.Host)
|
||||
if err != nil {
|
||||
return r.req.Host // OK; there probably was no port
|
||||
}
|
||||
return host
|
||||
}()
|
||||
m["request.hostport"] = r.req.Host // may include both host and port
|
||||
m["request.method"] = r.req.Method
|
||||
m["request.port"] = func() string {
|
||||
// if there is no port, there will be an error; in
|
||||
// that case, port is the empty string anyway
|
||||
_, port, _ := net.SplitHostPort(r.req.Host)
|
||||
return port
|
||||
}()
|
||||
m["request.scheme"] = func() string {
|
||||
if r.req.TLS != nil {
|
||||
return "https"
|
||||
}
|
||||
return "http"
|
||||
}()
|
||||
m["request.uri"] = r.req.URL.RequestURI()
|
||||
m["request.uri.path"] = r.req.URL.Path
|
||||
|
||||
// TODO: why should header fields, cookies, and query params get special treatment like this?
|
||||
// maybe they should be scoped by words like "request.header." just like everything else.
|
||||
for field, vals := range r.req.Header {
|
||||
m[">"+strings.ToLower(field)] = strings.Join(vals, ",")
|
||||
}
|
||||
for field, vals := range r.resp.Header() {
|
||||
m["<"+strings.ToLower(field)] = strings.Join(vals, ",")
|
||||
}
|
||||
for _, cookie := range r.req.Cookies() {
|
||||
m["~"+cookie.Name] = cookie.Value
|
||||
}
|
||||
for param, vals := range r.req.URL.Query() {
|
||||
m["?"+param] = strings.Join(vals, ",")
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
const phOpen, phClose = "{", "}"
|
||||
|
||||
// ReplacerCtxKey is the context key for the request's replacer.
|
||||
const ReplacerCtxKey caddy2.CtxKey = "replacer"
|
||||
|
|
|
@ -8,7 +8,10 @@ import (
|
|||
"bitbucket.org/lightcodelabs/caddy2"
|
||||
)
|
||||
|
||||
type serverRoute struct {
|
||||
// ServerRoute represents a set of matching rules,
|
||||
// middlewares, and a responder for handling HTTP
|
||||
// requests.
|
||||
type ServerRoute struct {
|
||||
Matchers map[string]json.RawMessage `json:"match"`
|
||||
Apply []json.RawMessage `json:"apply"`
|
||||
Respond json.RawMessage `json:"respond"`
|
||||
|
@ -21,9 +24,49 @@ type serverRoute struct {
|
|||
responder Handler
|
||||
}
|
||||
|
||||
type routeList []serverRoute
|
||||
// RouteList is a list of server routes that can
|
||||
// create a middleware chain.
|
||||
type RouteList []ServerRoute
|
||||
|
||||
func (routes routeList) buildMiddlewareChain(w http.ResponseWriter, r *http.Request) Handler {
|
||||
// Provision sets up all the routes by loading the modules.
|
||||
func (routes RouteList) Provision() error {
|
||||
for i, route := range routes {
|
||||
// matchers
|
||||
for modName, rawMsg := range route.Matchers {
|
||||
val, err := caddy2.LoadModule("http.matchers."+modName, rawMsg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading matcher module '%s': %v", modName, err)
|
||||
}
|
||||
routes[i].matchers = append(routes[i].matchers, val.(RouteMatcher))
|
||||
}
|
||||
routes[i].Matchers = nil // allow GC to deallocate - TODO: Does this help?
|
||||
|
||||
// middleware
|
||||
for j, rawMsg := range route.Apply {
|
||||
mid, err := caddy2.LoadModuleInline("middleware", "http.middleware", rawMsg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading middleware module in position %d: %v", j, err)
|
||||
}
|
||||
routes[i].middleware = append(routes[i].middleware, mid.(MiddlewareHandler))
|
||||
}
|
||||
routes[i].Apply = nil // allow GC to deallocate - TODO: Does this help?
|
||||
|
||||
// responder
|
||||
if route.Respond != nil {
|
||||
resp, err := caddy2.LoadModuleInline("responder", "http.responders", route.Respond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading responder module: %v", err)
|
||||
}
|
||||
routes[i].responder = resp.(Handler)
|
||||
}
|
||||
routes[i].Respond = nil // allow GC to deallocate - TODO: Does this help?
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildHandlerChain creates a chain of handlers by
|
||||
// applying all the matching routes.
|
||||
func (routes RouteList) BuildHandlerChain(w http.ResponseWriter, r *http.Request) Handler {
|
||||
if len(routes) == 0 {
|
||||
return emptyHandler
|
||||
}
|
||||
|
@ -68,38 +111,3 @@ routeLoop:
|
|||
|
||||
return stack
|
||||
}
|
||||
|
||||
func (routes routeList) setup() error {
|
||||
for i, route := range routes {
|
||||
// matchers
|
||||
for modName, rawMsg := range route.Matchers {
|
||||
val, err := caddy2.LoadModule("http.matchers."+modName, rawMsg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading matcher module '%s': %v", modName, err)
|
||||
}
|
||||
routes[i].matchers = append(routes[i].matchers, val.(RouteMatcher))
|
||||
}
|
||||
routes[i].Matchers = nil // allow GC to deallocate - TODO: Does this help?
|
||||
|
||||
// middleware
|
||||
for j, rawMsg := range route.Apply {
|
||||
mid, err := caddy2.LoadModuleInline("middleware", "http.middleware", rawMsg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading middleware module in position %d: %v", j, err)
|
||||
}
|
||||
routes[i].middleware = append(routes[i].middleware, mid.(MiddlewareHandler))
|
||||
}
|
||||
routes[i].Apply = nil // allow GC to deallocate - TODO: Does this help?
|
||||
|
||||
// responder
|
||||
if route.Respond != nil {
|
||||
resp, err := caddy2.LoadModuleInline("responder", "http.responders", route.Respond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading responder module: %v", err)
|
||||
}
|
||||
routes[i].responder = resp.(Handler)
|
||||
}
|
||||
routes[i].Respond = nil // allow GC to deallocate - TODO: Does this help?
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ func init() {
|
|||
}
|
||||
|
||||
// Static implements a simple responder for static responses.
|
||||
// It is Caddy's default responder. TODO: Or is it?
|
||||
type Static struct {
|
||||
StatusCode int `json:"status_code"`
|
||||
Headers http.Header `json:"headers"`
|
||||
|
|
|
@ -85,6 +85,9 @@ func (cp *ConnectionPolicy) buildStandardTLSConfig(handle caddy2.Handle) error {
|
|||
NextProtos: cp.ALPN,
|
||||
PreferServerCipherSuites: true,
|
||||
GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
// TODO: Must fix https://github.com/mholt/caddy/issues/2588
|
||||
// (allow customizing the selection of a very specific certificate
|
||||
// based on the ClientHelloInfo)
|
||||
cfgTpl, err := tlsApp.getConfigForName(hello.ServerName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting config for name %s: %v", hello.ServerName, err)
|
||||
|
|
Loading…
Reference in a new issue