mirror of
https://github.com/adnanh/webhook.git
synced 2025-06-27 14:58:31 +00:00
Use gorilla/mux for middleware and extend
- Use gorilla/mux for middleware. - Add Dumper, RequestID, and Logger middlewares. - Add makeURL helper
This commit is contained in:
parent
93ce24d3f3
commit
be815d0a41
323 changed files with 90756 additions and 16711 deletions
124
internal/middleware/dumper.go
Normal file
124
internal/middleware/dumper.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
package middleware
|
||||
|
||||
// Derived from from the Goa project, MIT Licensed
|
||||
// https://github.com/goadesign/goa/blob/v3/http/middleware/debug.go
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// responseDupper tees the response to a buffer and a response writer.
|
||||
type responseDupper struct {
|
||||
http.ResponseWriter
|
||||
Buffer *bytes.Buffer
|
||||
Status int
|
||||
}
|
||||
|
||||
// Dumper returns a debug middleware which prints detailed information about
|
||||
// incoming requests and outgoing responses including all headers, parameters
|
||||
// and bodies.
|
||||
func Dumper(w io.Writer) func(http.Handler) http.Handler {
|
||||
return func(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
buf := &bytes.Buffer{}
|
||||
// Request ID
|
||||
rid := r.Context().Value(RequestIDKey)
|
||||
|
||||
// Request URL
|
||||
buf.WriteString(fmt.Sprintf("> [%s] %s %s", rid, r.Method, r.URL.String()))
|
||||
|
||||
// Request Headers
|
||||
keys := make([]string, len(r.Header))
|
||||
i := 0
|
||||
for k := range r.Header {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
buf.WriteString(fmt.Sprintf("\n> [%s] %s: %s", rid, k, strings.Join(r.Header[k], ", ")))
|
||||
}
|
||||
|
||||
// Request parameters
|
||||
params := mux.Vars(r)
|
||||
keys = make([]string, len(params))
|
||||
i = 0
|
||||
for k := range params {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
buf.WriteString(fmt.Sprintf("\n> [%s] %s: %s", rid, k, strings.Join(r.Header[k], ", ")))
|
||||
}
|
||||
|
||||
// Request body
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
b = []byte("failed to read body: " + err.Error())
|
||||
}
|
||||
if len(b) > 0 {
|
||||
buf.WriteByte('\n')
|
||||
lines := strings.Split(string(b), "\n")
|
||||
for _, line := range lines {
|
||||
buf.WriteString(fmt.Sprintf("> [%s] %s\n", rid, line))
|
||||
}
|
||||
}
|
||||
r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
|
||||
|
||||
dupper := &responseDupper{ResponseWriter: rw, Buffer: &bytes.Buffer{}}
|
||||
h.ServeHTTP(dupper, r)
|
||||
|
||||
buf.WriteString(fmt.Sprintf("\n< [%s] %s", rid, http.StatusText(dupper.Status)))
|
||||
keys = make([]string, len(dupper.Header()))
|
||||
i = 0
|
||||
for k := range dupper.Header() {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
buf.WriteString(fmt.Sprintf("\n< [%s] %s: %s", rid, k, strings.Join(dupper.Header()[k], ", ")))
|
||||
}
|
||||
if dupper.Buffer.Len() > 0 {
|
||||
buf.WriteByte('\n')
|
||||
lines := strings.Split(dupper.Buffer.String(), "\n")
|
||||
for _, line := range lines {
|
||||
buf.WriteString(fmt.Sprintf("< [%s] %s\n", rid, line))
|
||||
}
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
w.Write(buf.Bytes())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Write writes the data to the buffer and connection as part of an HTTP reply.
|
||||
func (r *responseDupper) Write(b []byte) (int, error) {
|
||||
r.Buffer.Write(b)
|
||||
return r.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
// WriteHeader records the status and sends an HTTP response header with status code.
|
||||
func (r *responseDupper) WriteHeader(s int) {
|
||||
r.Status = s
|
||||
r.ResponseWriter.WriteHeader(s)
|
||||
}
|
||||
|
||||
// Hijack supports the http.Hijacker interface.
|
||||
func (r *responseDupper) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hijacker, ok := r.ResponseWriter.(http.Hijacker); ok {
|
||||
return hijacker.Hijack()
|
||||
}
|
||||
return nil, nil, fmt.Errorf("dumper middleware: inner ResponseWriter cannot be hijacked: %T", r.ResponseWriter)
|
||||
}
|
59
internal/middleware/logger.go
Normal file
59
internal/middleware/logger.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
)
|
||||
|
||||
// Logger is a middleware that logs useful data about each HTTP request.
|
||||
type Logger struct {
|
||||
Logger middleware.LoggerInterface
|
||||
}
|
||||
|
||||
// NewLogger creates a new RequestLogger Handler.
|
||||
func NewLogger() func(next http.Handler) http.Handler {
|
||||
return middleware.RequestLogger(&Logger{})
|
||||
}
|
||||
|
||||
// NewLogEntry creates a new LogEntry for the request.
|
||||
func (l *Logger) NewLogEntry(r *http.Request) middleware.LogEntry {
|
||||
e := &LogEntry{
|
||||
req: r,
|
||||
buf: &bytes.Buffer{},
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// LogEntry represents an individual log entry.
|
||||
type LogEntry struct {
|
||||
*Logger
|
||||
req *http.Request
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
// Write constructs and writes the final log entry.
|
||||
func (l *LogEntry) Write(status, bytes int, elapsed time.Duration) {
|
||||
rid := GetReqID(l.req.Context())
|
||||
if rid != "" {
|
||||
fmt.Fprintf(l.buf, "[%s] ", rid)
|
||||
}
|
||||
|
||||
fmt.Fprintf(l.buf, "%03d | %s | %s | ", status, humanize.IBytes(uint64(bytes)), elapsed)
|
||||
l.buf.WriteString(l.req.Host + " | " + l.req.Method + " " + l.req.RequestURI)
|
||||
log.Print(l.buf.String())
|
||||
}
|
||||
|
||||
/// Panic prints the call stack for a panic.
|
||||
func (l *LogEntry) Panic(v interface{}, stack []byte) {
|
||||
e := l.NewLogEntry(l.req).(*LogEntry)
|
||||
fmt.Fprintf(e.buf, "panic: %#v", v)
|
||||
log.Print(e.buf.String())
|
||||
log.Print(string(stack))
|
||||
}
|
98
internal/middleware/request_id.go
Normal file
98
internal/middleware/request_id.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package middleware
|
||||
|
||||
// Derived from Goa project, MIT Licensed
|
||||
// https://github.com/goadesign/goa/blob/v3/http/middleware/requestid.go
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
// Key to use when setting the request ID.
|
||||
type ctxKeyRequestID int
|
||||
|
||||
// RequestIDKey is the key that holds the unique request ID in a request context.
|
||||
const RequestIDKey ctxKeyRequestID = 0
|
||||
|
||||
// RequestID is a middleware that injects a request ID into the context of each
|
||||
// request.
|
||||
func RequestID(options ...RequestIDOption) func(http.Handler) http.Handler {
|
||||
o := newRequestIDOptions(options...)
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var id string
|
||||
|
||||
if o.UseRequestID() {
|
||||
id = r.Header.Get("X-Request-Id")
|
||||
if o.requestIDLimit > 0 && len(id) > o.requestIDLimit {
|
||||
id = id[:o.requestIDLimit]
|
||||
}
|
||||
}
|
||||
|
||||
if id == "" {
|
||||
id = uuid.Must(uuid.NewV4()).String()[:6]
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, RequestIDKey, id)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GetReqID returns a request ID from the given context if one is present.
|
||||
// Returns the empty string if a request ID cannot be found.
|
||||
func GetReqID(ctx context.Context) string {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
}
|
||||
if reqID, ok := ctx.Value(RequestIDKey).(string); ok {
|
||||
return reqID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func UseXRequestIDHeaderOption(f bool) RequestIDOption {
|
||||
return func(o *RequestIDOptions) *RequestIDOptions {
|
||||
o.useXRequestID = f
|
||||
return o
|
||||
}
|
||||
}
|
||||
|
||||
func XRequestIDLimitOption(limit int) RequestIDOption {
|
||||
return func(o *RequestIDOptions) *RequestIDOptions {
|
||||
o.requestIDLimit = limit
|
||||
return o
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
RequestIDOption func(*RequestIDOptions) *RequestIDOptions
|
||||
|
||||
RequestIDOptions struct {
|
||||
// useXRequestID enabled the use of the X-Request-Id request header as
|
||||
// the request ID.
|
||||
useXRequestID bool
|
||||
|
||||
// requestIDLimit is the maximum length of the X-Request-Id header
|
||||
// allowed. Values longer than this value are truncated. Zero value
|
||||
// means no limit.
|
||||
requestIDLimit int
|
||||
}
|
||||
)
|
||||
|
||||
func newRequestIDOptions(options ...RequestIDOption) *RequestIDOptions {
|
||||
o := new(RequestIDOptions)
|
||||
for _, opt := range options {
|
||||
o = opt(o)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *RequestIDOptions) UseRequestID() bool {
|
||||
return o.useXRequestID
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue