mirror of
https://github.com/caddyserver/caddy.git
synced 2025-03-20 04:29:03 +01:00
* Create request_id directive #1590 * Address Comments * Fix TestListenerAddrEqual * requestid: Add some tests * Address Comments by tobya * Address Comments
This commit is contained in:
parent
b0ab3d4281
commit
133ed18374
12 changed files with 346 additions and 1 deletions
|
@ -24,6 +24,7 @@ import (
|
||||||
_ "github.com/mholt/caddy/caddyhttp/proxy"
|
_ "github.com/mholt/caddy/caddyhttp/proxy"
|
||||||
_ "github.com/mholt/caddy/caddyhttp/push"
|
_ "github.com/mholt/caddy/caddyhttp/push"
|
||||||
_ "github.com/mholt/caddy/caddyhttp/redirect"
|
_ "github.com/mholt/caddy/caddyhttp/redirect"
|
||||||
|
_ "github.com/mholt/caddy/caddyhttp/requestid"
|
||||||
_ "github.com/mholt/caddy/caddyhttp/rewrite"
|
_ "github.com/mholt/caddy/caddyhttp/rewrite"
|
||||||
_ "github.com/mholt/caddy/caddyhttp/root"
|
_ "github.com/mholt/caddy/caddyhttp/root"
|
||||||
_ "github.com/mholt/caddy/caddyhttp/status"
|
_ "github.com/mholt/caddy/caddyhttp/status"
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
// ensure that the standard plugins are in fact plugged in
|
// ensure that the standard plugins are in fact plugged in
|
||||||
// and registered properly; this is a quick/naive way to do it.
|
// and registered properly; this is a quick/naive way to do it.
|
||||||
func TestStandardPlugins(t *testing.T) {
|
func TestStandardPlugins(t *testing.T) {
|
||||||
numStandardPlugins := 31 // importing caddyhttp plugs in this many plugins
|
numStandardPlugins := 32 // importing caddyhttp plugs in this many plugins
|
||||||
s := caddy.DescribePlugins()
|
s := caddy.DescribePlugins()
|
||||||
if got, want := strings.Count(s, "\n"), numStandardPlugins+5; got != want {
|
if got, want := strings.Count(s, "\n"), numStandardPlugins+5; got != want {
|
||||||
t.Errorf("Expected all standard plugins to be plugged in, got:\n%s", s)
|
t.Errorf("Expected all standard plugins to be plugged in, got:\n%s", s)
|
||||||
|
|
|
@ -205,4 +205,7 @@ const (
|
||||||
|
|
||||||
// MitmCtxKey is the key for the result of MITM detection
|
// MitmCtxKey is the key for the result of MITM detection
|
||||||
MitmCtxKey caddy.CtxKey = "mitm"
|
MitmCtxKey caddy.CtxKey = "mitm"
|
||||||
|
|
||||||
|
// RequestIDCtxKey is the key for the U4 UUID value
|
||||||
|
RequestIDCtxKey caddy.CtxKey = "request_id"
|
||||||
)
|
)
|
||||||
|
|
|
@ -443,6 +443,7 @@ var directives = []string{
|
||||||
// services/utilities, or other directives that don't necessarily inject handlers
|
// services/utilities, or other directives that don't necessarily inject handlers
|
||||||
"startup",
|
"startup",
|
||||||
"shutdown",
|
"shutdown",
|
||||||
|
"requestid",
|
||||||
"realip", // github.com/captncraig/caddy-realip
|
"realip", // github.com/captncraig/caddy-realip
|
||||||
"git", // github.com/abiosoft/caddy-git
|
"git", // github.com/abiosoft/caddy-git
|
||||||
|
|
||||||
|
|
|
@ -243,6 +243,9 @@ func (r *replacer) getSubstitution(key string) string {
|
||||||
case "{path_escaped}":
|
case "{path_escaped}":
|
||||||
u, _ := r.request.Context().Value(OriginalURLCtxKey).(url.URL)
|
u, _ := r.request.Context().Value(OriginalURLCtxKey).(url.URL)
|
||||||
return url.QueryEscape(u.Path)
|
return url.QueryEscape(u.Path)
|
||||||
|
case "{request_id}":
|
||||||
|
reqid, _ := r.request.Context().Value(RequestIDCtxKey).(string)
|
||||||
|
return reqid
|
||||||
case "{rewrite_path}":
|
case "{rewrite_path}":
|
||||||
return r.request.URL.Path
|
return r.request.URL.Path
|
||||||
case "{rewrite_path_escaped}":
|
case "{rewrite_path_escaped}":
|
||||||
|
|
34
caddyhttp/requestid/requestid.go
Normal file
34
caddyhttp/requestid/requestid.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package requestid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||||
|
uuid "github.com/nu7hatch/gouuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler is a middleware handler
|
||||||
|
type Handler struct {
|
||||||
|
Next httpserver.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
reqid := UUID()
|
||||||
|
c := context.WithValue(r.Context(), httpserver.RequestIDCtxKey, reqid)
|
||||||
|
r = r.WithContext(c)
|
||||||
|
|
||||||
|
return h.Next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UUID returns U4 UUID
|
||||||
|
func UUID() string {
|
||||||
|
u4, err := uuid.NewV4()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] generating request ID: %v", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return u4.String()
|
||||||
|
}
|
33
caddyhttp/requestid/requestid_test.go
Normal file
33
caddyhttp/requestid/requestid_test.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package requestid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRequestID(t *testing.T) {
|
||||||
|
request, err := http.NewRequest("GET", "http://localhost/", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Could not create HTTP request:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqid := UUID()
|
||||||
|
|
||||||
|
c := context.WithValue(request.Context(), httpserver.RequestIDCtxKey, reqid)
|
||||||
|
|
||||||
|
request = request.WithContext(c)
|
||||||
|
|
||||||
|
// See caddyhttp/replacer.go
|
||||||
|
value, _ := request.Context().Value(httpserver.RequestIDCtxKey).(string)
|
||||||
|
|
||||||
|
if value == "" {
|
||||||
|
t.Fatal("Request ID should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if value != reqid {
|
||||||
|
t.Fatal("Request ID does not match")
|
||||||
|
}
|
||||||
|
}
|
27
caddyhttp/requestid/setup.go
Normal file
27
caddyhttp/requestid/setup.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package requestid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mholt/caddy"
|
||||||
|
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
caddy.RegisterPlugin("requestid", caddy.Plugin{
|
||||||
|
ServerType: "http",
|
||||||
|
Action: setup,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup(c *caddy.Controller) error {
|
||||||
|
for c.Next() {
|
||||||
|
if c.NextArg() {
|
||||||
|
return c.ArgErr() //no arg expected.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
|
||||||
|
return Handler{Next: next}
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
43
caddyhttp/requestid/setup_test.go
Normal file
43
caddyhttp/requestid/setup_test.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package requestid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy"
|
||||||
|
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetup(t *testing.T) {
|
||||||
|
c := caddy.NewTestController("http", `requestid`)
|
||||||
|
err := setup(c)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no errors, got: %v", err)
|
||||||
|
}
|
||||||
|
mids := httpserver.GetConfig(c).Middleware()
|
||||||
|
if len(mids) == 0 {
|
||||||
|
t.Fatal("Expected middleware, got 0 instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := mids[0](httpserver.EmptyNext)
|
||||||
|
myHandler, ok := handler.(Handler)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected handler to be type Handler, got: %#v", handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) {
|
||||||
|
t.Error("'Next' field of handler was not set properly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetupWithArg(t *testing.T) {
|
||||||
|
c := caddy.NewTestController("http", `requestid abc`)
|
||||||
|
err := setup(c)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected an error, got: %v", err)
|
||||||
|
}
|
||||||
|
mids := httpserver.GetConfig(c).Middleware()
|
||||||
|
if len(mids) != 0 {
|
||||||
|
t.Fatal("Expected no middleware")
|
||||||
|
}
|
||||||
|
}
|
19
vendor/github.com/nu7hatch/gouuid/COPYING
generated
vendored
Normal file
19
vendor/github.com/nu7hatch/gouuid/COPYING
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (C) 2011 by Krzysztof Kowalik <chris@nu7hat.ch>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
173
vendor/github.com/nu7hatch/gouuid/uuid.go
generated
vendored
Normal file
173
vendor/github.com/nu7hatch/gouuid/uuid.go
generated
vendored
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
// This package provides immutable UUID structs and the functions
|
||||||
|
// NewV3, NewV4, NewV5 and Parse() for generating versions 3, 4
|
||||||
|
// and 5 UUIDs as specified in RFC 4122.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011 by Krzysztof Kowalik <chris@nu7hat.ch>
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The UUID reserved variants.
|
||||||
|
const (
|
||||||
|
ReservedNCS byte = 0x80
|
||||||
|
ReservedRFC4122 byte = 0x40
|
||||||
|
ReservedMicrosoft byte = 0x20
|
||||||
|
ReservedFuture byte = 0x00
|
||||||
|
)
|
||||||
|
|
||||||
|
// The following standard UUIDs are for use with NewV3() or NewV5().
|
||||||
|
var (
|
||||||
|
NamespaceDNS, _ = ParseHex("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
|
||||||
|
NamespaceURL, _ = ParseHex("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
|
||||||
|
NamespaceOID, _ = ParseHex("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
|
||||||
|
NamespaceX500, _ = ParseHex("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pattern used to parse hex string representation of the UUID.
|
||||||
|
// FIXME: do something to consider both brackets at one time,
|
||||||
|
// current one allows to parse string with only one opening
|
||||||
|
// or closing bracket.
|
||||||
|
const hexPattern = "^(urn\\:uuid\\:)?\\{?([a-z0-9]{8})-([a-z0-9]{4})-" +
|
||||||
|
"([1-5][a-z0-9]{3})-([a-z0-9]{4})-([a-z0-9]{12})\\}?$"
|
||||||
|
|
||||||
|
var re = regexp.MustCompile(hexPattern)
|
||||||
|
|
||||||
|
// A UUID representation compliant with specification in
|
||||||
|
// RFC 4122 document.
|
||||||
|
type UUID [16]byte
|
||||||
|
|
||||||
|
// ParseHex creates a UUID object from given hex string
|
||||||
|
// representation. Function accepts UUID string in following
|
||||||
|
// formats:
|
||||||
|
//
|
||||||
|
// uuid.ParseHex("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
|
||||||
|
// uuid.ParseHex("{6ba7b814-9dad-11d1-80b4-00c04fd430c8}")
|
||||||
|
// uuid.ParseHex("urn:uuid:6ba7b814-9dad-11d1-80b4-00c04fd430c8")
|
||||||
|
//
|
||||||
|
func ParseHex(s string) (u *UUID, err error) {
|
||||||
|
md := re.FindStringSubmatch(s)
|
||||||
|
if md == nil {
|
||||||
|
err = errors.New("Invalid UUID string")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hash := md[2] + md[3] + md[4] + md[5] + md[6]
|
||||||
|
b, err := hex.DecodeString(hash)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u = new(UUID)
|
||||||
|
copy(u[:], b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse creates a UUID object from given bytes slice.
|
||||||
|
func Parse(b []byte) (u *UUID, err error) {
|
||||||
|
if len(b) != 16 {
|
||||||
|
err = errors.New("Given slice is not valid UUID sequence")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u = new(UUID)
|
||||||
|
copy(u[:], b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a UUID based on the MD5 hash of a namespace identifier
|
||||||
|
// and a name.
|
||||||
|
func NewV3(ns *UUID, name []byte) (u *UUID, err error) {
|
||||||
|
if ns == nil {
|
||||||
|
err = errors.New("Invalid namespace UUID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u = new(UUID)
|
||||||
|
// Set all bits to MD5 hash generated from namespace and name.
|
||||||
|
u.setBytesFromHash(md5.New(), ns[:], name)
|
||||||
|
u.setVariant(ReservedRFC4122)
|
||||||
|
u.setVersion(3)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a random UUID.
|
||||||
|
func NewV4() (u *UUID, err error) {
|
||||||
|
u = new(UUID)
|
||||||
|
// Set all bits to randomly (or pseudo-randomly) chosen values.
|
||||||
|
_, err = rand.Read(u[:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u.setVariant(ReservedRFC4122)
|
||||||
|
u.setVersion(4)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a UUID based on the SHA-1 hash of a namespace identifier
|
||||||
|
// and a name.
|
||||||
|
func NewV5(ns *UUID, name []byte) (u *UUID, err error) {
|
||||||
|
u = new(UUID)
|
||||||
|
// Set all bits to truncated SHA1 hash generated from namespace
|
||||||
|
// and name.
|
||||||
|
u.setBytesFromHash(sha1.New(), ns[:], name)
|
||||||
|
u.setVariant(ReservedRFC4122)
|
||||||
|
u.setVersion(5)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a MD5 hash of a namespace and a name, and copy it to the
|
||||||
|
// UUID slice.
|
||||||
|
func (u *UUID) setBytesFromHash(hash hash.Hash, ns, name []byte) {
|
||||||
|
hash.Write(ns[:])
|
||||||
|
hash.Write(name)
|
||||||
|
copy(u[:], hash.Sum([]byte{})[:16])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the two most significant bits (bits 6 and 7) of the
|
||||||
|
// clock_seq_hi_and_reserved to zero and one, respectively.
|
||||||
|
func (u *UUID) setVariant(v byte) {
|
||||||
|
switch v {
|
||||||
|
case ReservedNCS:
|
||||||
|
u[8] = (u[8] | ReservedNCS) & 0xBF
|
||||||
|
case ReservedRFC4122:
|
||||||
|
u[8] = (u[8] | ReservedRFC4122) & 0x7F
|
||||||
|
case ReservedMicrosoft:
|
||||||
|
u[8] = (u[8] | ReservedMicrosoft) & 0x3F
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant returns the UUID Variant, which determines the internal
|
||||||
|
// layout of the UUID. This will be one of the constants: RESERVED_NCS,
|
||||||
|
// RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE.
|
||||||
|
func (u *UUID) Variant() byte {
|
||||||
|
if u[8]&ReservedNCS == ReservedNCS {
|
||||||
|
return ReservedNCS
|
||||||
|
} else if u[8]&ReservedRFC4122 == ReservedRFC4122 {
|
||||||
|
return ReservedRFC4122
|
||||||
|
} else if u[8]&ReservedMicrosoft == ReservedMicrosoft {
|
||||||
|
return ReservedMicrosoft
|
||||||
|
}
|
||||||
|
return ReservedFuture
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the four most significant bits (bits 12 through 15) of the
|
||||||
|
// time_hi_and_version field to the 4-bit version number.
|
||||||
|
func (u *UUID) setVersion(v byte) {
|
||||||
|
u[6] = (u[6] & 0xF) | (v << 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version returns a version number of the algorithm used to
|
||||||
|
// generate the UUID sequence.
|
||||||
|
func (u *UUID) Version() uint {
|
||||||
|
return uint(u[6] >> 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns unparsed version of the generated UUID sequence.
|
||||||
|
func (u *UUID) String() string {
|
||||||
|
return fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
|
||||||
|
}
|
8
vendor/manifest
vendored
8
vendor/manifest
vendored
|
@ -165,6 +165,14 @@
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"notests": true
|
"notests": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"importpath": "github.com/nu7hatch/gouuid",
|
||||||
|
"repository": "https://github.com/nu7hatch/gouuid",
|
||||||
|
"vcs": "git",
|
||||||
|
"revision": "179d4d0c4d8d407a32af483c2354df1d2c91e6c3",
|
||||||
|
"branch": "master",
|
||||||
|
"notests": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"importpath": "github.com/russross/blackfriday",
|
"importpath": "github.com/russross/blackfriday",
|
||||||
"repository": "https://github.com/russross/blackfriday",
|
"repository": "https://github.com/russross/blackfriday",
|
||||||
|
|
Loading…
Reference in a new issue