Matthew Holt
6 years ago
9 changed files with 454 additions and 25 deletions
@ -0,0 +1,37 @@ |
|||||
|
package caddylog |
||||
|
|
||||
|
import ( |
||||
|
"log" |
||||
|
"net/http" |
||||
|
"time" |
||||
|
|
||||
|
"bitbucket.org/lightcodelabs/caddy2" |
||||
|
"bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp" |
||||
|
) |
||||
|
|
||||
|
func init() { |
||||
|
caddy2.RegisterModule(caddy2.Module{ |
||||
|
Name: "http.middleware.log", |
||||
|
New: func() (interface{}, error) { return &Log{}, nil }, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// Log implements a simple logging middleware.
|
||||
|
type Log struct { |
||||
|
Filename string |
||||
|
} |
||||
|
|
||||
|
func (l *Log) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { |
||||
|
start := time.Now() |
||||
|
|
||||
|
if err := next.ServeHTTP(w, r); err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
log.Println("latency:", time.Now().Sub(start)) |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// Interface guard
|
||||
|
var _ caddyhttp.MiddlewareHandler = &Log{} |
@ -0,0 +1,102 @@ |
|||||
|
package caddyhttp |
||||
|
|
||||
|
import ( |
||||
|
"net/http" |
||||
|
"strings" |
||||
|
|
||||
|
"bitbucket.org/lightcodelabs/caddy2" |
||||
|
) |
||||
|
|
||||
|
func init() { |
||||
|
caddy2.RegisterModule(caddy2.Module{ |
||||
|
Name: "http.matchers.host", |
||||
|
New: func() (interface{}, error) { return matchHost{}, nil }, |
||||
|
}) |
||||
|
caddy2.RegisterModule(caddy2.Module{ |
||||
|
Name: "http.matchers.path", |
||||
|
New: func() (interface{}, error) { return matchPath{}, nil }, |
||||
|
}) |
||||
|
caddy2.RegisterModule(caddy2.Module{ |
||||
|
Name: "http.matchers.method", |
||||
|
New: func() (interface{}, error) { return matchMethod{}, nil }, |
||||
|
}) |
||||
|
caddy2.RegisterModule(caddy2.Module{ |
||||
|
Name: "http.matchers.query", |
||||
|
New: func() (interface{}, error) { return matchQuery{}, nil }, |
||||
|
}) |
||||
|
caddy2.RegisterModule(caddy2.Module{ |
||||
|
Name: "http.matchers.header", |
||||
|
New: func() (interface{}, error) { return matchHeader{}, nil }, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// TODO: Matchers should probably support regex of some sort... performance trade-offs?
|
||||
|
|
||||
|
type ( |
||||
|
matchHost []string |
||||
|
matchPath []string |
||||
|
matchMethod []string |
||||
|
matchQuery map[string][]string |
||||
|
matchHeader map[string][]string |
||||
|
) |
||||
|
|
||||
|
func (m matchHost) Match(r *http.Request) bool { |
||||
|
for _, host := range m { |
||||
|
if r.Host == host { |
||||
|
return true |
||||
|
} |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
func (m matchPath) Match(r *http.Request) bool { |
||||
|
for _, path := range m { |
||||
|
if strings.HasPrefix(r.URL.Path, path) { |
||||
|
return true |
||||
|
} |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
func (m matchMethod) Match(r *http.Request) bool { |
||||
|
for _, method := range m { |
||||
|
if r.Method == method { |
||||
|
return true |
||||
|
} |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
func (m matchQuery) Match(r *http.Request) bool { |
||||
|
for param, vals := range m { |
||||
|
paramVal := r.URL.Query().Get(param) |
||||
|
for _, v := range vals { |
||||
|
if paramVal == v { |
||||
|
return true |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
func (m matchHeader) Match(r *http.Request) bool { |
||||
|
for field, vals := range m { |
||||
|
fieldVals := r.Header[field] |
||||
|
for _, fieldVal := range fieldVals { |
||||
|
for _, v := range vals { |
||||
|
if fieldVal == v { |
||||
|
return true |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
var ( |
||||
|
_ RouteMatcher = matchHost{} |
||||
|
_ RouteMatcher = matchPath{} |
||||
|
_ RouteMatcher = matchMethod{} |
||||
|
_ RouteMatcher = matchQuery{} |
||||
|
_ RouteMatcher = matchHeader{} |
||||
|
) |
@ -0,0 +1,68 @@ |
|||||
|
package caddyhttp |
||||
|
|
||||
|
import ( |
||||
|
"bufio" |
||||
|
"fmt" |
||||
|
"net" |
||||
|
"net/http" |
||||
|
) |
||||
|
|
||||
|
// ResponseWriterWrapper wraps an underlying ResponseWriter and
|
||||
|
// promotes its Pusher/Flusher/CloseNotifier/Hijacker methods
|
||||
|
// as well. To use this type, embed a pointer to it within your
|
||||
|
// own struct type that implements the http.ResponseWriter
|
||||
|
// interface, then call methods on the embedded value. You can
|
||||
|
// make sure your type wraps correctly by asserting that it
|
||||
|
// implements the HTTPInterfaces interface.
|
||||
|
type ResponseWriterWrapper struct { |
||||
|
http.ResponseWriter |
||||
|
} |
||||
|
|
||||
|
// Hijack implements http.Hijacker. It simply calls the underlying
|
||||
|
// ResponseWriter's Hijack method if there is one, or returns an error.
|
||||
|
func (rww *ResponseWriterWrapper) Hijack() (net.Conn, *bufio.ReadWriter, error) { |
||||
|
if hj, ok := rww.ResponseWriter.(http.Hijacker); ok { |
||||
|
return hj.Hijack() |
||||
|
} |
||||
|
return nil, nil, fmt.Errorf("not a hijacker") |
||||
|
} |
||||
|
|
||||
|
// Flush implements http.Flusher. It simply calls the underlying
|
||||
|
// ResponseWriter's Flush method if there is one, or panics.
|
||||
|
func (rww *ResponseWriterWrapper) Flush() { |
||||
|
if f, ok := rww.ResponseWriter.(http.Flusher); ok { |
||||
|
f.Flush() |
||||
|
} else { |
||||
|
panic("not a flusher") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// CloseNotify implements http.CloseNotifier. It simply calls the underlying
|
||||
|
// ResponseWriter's CloseNotify method if there is one, or panics.
|
||||
|
func (rww *ResponseWriterWrapper) CloseNotify() <-chan bool { |
||||
|
if cn, ok := rww.ResponseWriter.(http.CloseNotifier); ok { |
||||
|
return cn.CloseNotify() |
||||
|
} |
||||
|
panic("not a close notifier") |
||||
|
} |
||||
|
|
||||
|
// Push implements http.Pusher. It simply calls the underlying
|
||||
|
// ResponseWriter's Push method if there is one, or returns an error.
|
||||
|
func (rww *ResponseWriterWrapper) Push(target string, opts *http.PushOptions) error { |
||||
|
if pusher, hasPusher := rww.ResponseWriter.(http.Pusher); hasPusher { |
||||
|
return pusher.Push(target, opts) |
||||
|
} |
||||
|
return fmt.Errorf("not a pusher") |
||||
|
} |
||||
|
|
||||
|
// HTTPInterfaces mix all the interfaces that middleware ResponseWriters need to support.
|
||||
|
type HTTPInterfaces interface { |
||||
|
http.ResponseWriter |
||||
|
http.Pusher |
||||
|
http.Flusher |
||||
|
http.CloseNotifier |
||||
|
http.Hijacker |
||||
|
} |
||||
|
|
||||
|
// Interface guards
|
||||
|
var _ HTTPInterfaces = (*ResponseWriterWrapper)(nil) |
@ -0,0 +1,28 @@ |
|||||
|
package staticfiles |
||||
|
|
||||
|
import ( |
||||
|
"net/http" |
||||
|
|
||||
|
"bitbucket.org/lightcodelabs/caddy2" |
||||
|
"bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp" |
||||
|
) |
||||
|
|
||||
|
func init() { |
||||
|
caddy2.RegisterModule(caddy2.Module{ |
||||
|
Name: "http.responders.static_files", |
||||
|
New: func() (interface{}, error) { return &StaticFiles{}, nil }, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// StaticFiles implements a static file server responder for Caddy.
|
||||
|
type StaticFiles struct { |
||||
|
Root string |
||||
|
} |
||||
|
|
||||
|
func (sf StaticFiles) ServeHTTP(w http.ResponseWriter, r *http.Request) error { |
||||
|
http.FileServer(http.Dir(sf.Root)).ServeHTTP(w, r) |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// Interface guard
|
||||
|
var _ caddyhttp.Handler = StaticFiles{} |
Loading…
Reference in new issue