initial commit
This commit is contained in:
95
middleware/httplog/log.go
Normal file
95
middleware/httplog/log.go
Normal file
@ -0,0 +1,95 @@
|
||||
package httplog
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.jeffthecoder.xyz/public/lazyhandler/magic"
|
||||
)
|
||||
|
||||
type ctxKey int
|
||||
|
||||
const (
|
||||
loggerKey ctxKey = iota
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotHijackable = errors.New("not a hijacker")
|
||||
)
|
||||
|
||||
type Log struct {
|
||||
LogStart bool
|
||||
LogStartLevel slog.Level
|
||||
|
||||
LogFinish bool
|
||||
LogFinishLevel slog.Level
|
||||
}
|
||||
|
||||
type responseRecorder struct {
|
||||
http.ResponseWriter
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
func (recorder *responseRecorder) WriteHeader(statusCode int) {
|
||||
recorder.StatusCode = statusCode
|
||||
|
||||
recorder.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
func (recorder *responseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hijacker, ok := recorder.ResponseWriter.(http.Hijacker); ok {
|
||||
recorder.ResponseWriter = nil
|
||||
return hijacker.Hijack()
|
||||
}
|
||||
|
||||
return nil, nil, ErrNotHijackable
|
||||
}
|
||||
|
||||
var (
|
||||
_ http.Hijacker = &responseRecorder{}
|
||||
)
|
||||
|
||||
func (log Log) WrapHandler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
args := []any{
|
||||
"remote_addr", r.RemoteAddr,
|
||||
"host", r.Host,
|
||||
"path", r.URL.Path,
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
if log.LogStart {
|
||||
slog.With(append(args, "time", startTime)...).Log(r.Context(), log.LogStartLevel, "request")
|
||||
}
|
||||
recorder := &responseRecorder{ResponseWriter: w, StatusCode: 200}
|
||||
next.ServeHTTP(recorder, r.WithContext(context.WithValue(r.Context(), loggerKey, slog.With(args...))))
|
||||
if log.LogFinish && recorder.ResponseWriter != nil {
|
||||
finishTime := time.Now()
|
||||
slog.With(append(args, "time", finishTime, "duration", finishTime.Sub(startTime), "status", recorder.StatusCode)...).Log(r.Context(), log.LogFinishLevel, "response")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Logger(r *http.Request) *slog.Logger {
|
||||
if logger, ok := r.Context().Value(loggerKey).(*slog.Logger); ok {
|
||||
return logger.With("time", time.Now())
|
||||
}
|
||||
|
||||
return slog.With(
|
||||
"remote_addr", r.RemoteAddr,
|
||||
"host", r.Host,
|
||||
"path", r.URL.Path,
|
||||
"time", time.Now(),
|
||||
)
|
||||
}
|
||||
|
||||
func RegisterExtractor() {
|
||||
magic.RegisterExtractorGeneric[*slog.Logger](func(r *http.Request) (any, error) {
|
||||
return Logger(r), nil
|
||||
})
|
||||
}
|
13
middleware/middleware.go
Normal file
13
middleware/middleware.go
Normal file
@ -0,0 +1,13 @@
|
||||
package middleware
|
||||
|
||||
import "net/http"
|
||||
|
||||
type Middleware interface {
|
||||
WrapHandler(next http.Handler) http.Handler
|
||||
}
|
||||
|
||||
type WrapFunc func(next http.Handler) http.Handler
|
||||
|
||||
func (wrap WrapFunc) WrapHandler(next http.Handler) http.Handler {
|
||||
return wrap(next)
|
||||
}
|
44
middleware/recover/recover.go
Normal file
44
middleware/recover/recover.go
Normal file
@ -0,0 +1,44 @@
|
||||
package recover
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"git.jeffthecoder.xyz/public/lazyhandler/middleware"
|
||||
"git.jeffthecoder.xyz/public/lazyhandler/middleware/httplog"
|
||||
)
|
||||
|
||||
type PanicResponseFunc func(http.ResponseWriter, *http.Request, any)
|
||||
|
||||
func recoverer(w http.ResponseWriter, r *http.Request, fn PanicResponseFunc) {
|
||||
if err := recover(); err != nil {
|
||||
httplog.Logger(r).With("panic", err).Error("request panicked")
|
||||
|
||||
if fn != nil {
|
||||
fn(w, r, err)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Recover(responseFunc PanicResponseFunc) middleware.Middleware {
|
||||
return middleware.WrapFunc(func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer recoverer(w, r, responseFunc)
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func DebugPanicHandler(w http.ResponseWriter, r *http.Request, err any) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]any{
|
||||
"error": err,
|
||||
})
|
||||
}
|
||||
|
||||
func Debug() middleware.Middleware {
|
||||
return Recover(DebugPanicHandler)
|
||||
}
|
55
middleware/session/session.go
Normal file
55
middleware/session/session.go
Normal file
@ -0,0 +1,55 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"git.jeffthecoder.xyz/public/lazyhandler/magic"
|
||||
"git.jeffthecoder.xyz/public/lazyhandler/middleware"
|
||||
|
||||
"github.com/go-session/session/v3"
|
||||
)
|
||||
|
||||
type ctxKey int
|
||||
|
||||
const (
|
||||
sessionKey ctxKey = iota
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSessionStoreNotInitialized = errors.New("no session store in request context")
|
||||
)
|
||||
|
||||
func Session(sessionStore session.ManagerStore) middleware.Middleware {
|
||||
manager := session.NewManager(
|
||||
session.SetStore(sessionStore),
|
||||
)
|
||||
|
||||
return middleware.WrapFunc(func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
store, err := manager.Start(r.Context(), w, r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer store.Save()
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), sessionKey, store)))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func GetSession(r *http.Request) (session.Store, error) {
|
||||
if store, ok := r.Context().Value(sessionKey).(session.Store); ok {
|
||||
return store, nil
|
||||
}
|
||||
return nil, ErrSessionStoreNotInitialized
|
||||
}
|
||||
|
||||
func RegisterExtractor() {
|
||||
magic.RegisterExtractorGeneric[session.Store](func(r *http.Request) (any, error) {
|
||||
store, err := GetSession(r)
|
||||
return store, err
|
||||
})
|
||||
}
|
18
middleware/slash/slash.go
Normal file
18
middleware/slash/slash.go
Normal file
@ -0,0 +1,18 @@
|
||||
package slash
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.jeffthecoder.xyz/public/lazyhandler/middleware"
|
||||
)
|
||||
|
||||
func StripSlash() middleware.Middleware {
|
||||
return middleware.WrapFunc(func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path[len(r.URL.Path)-1] == '/' && len(r.URL.Path) > 1 {
|
||||
r.URL.Path = r.URL.Path[:len(r.URL.Path)-1]
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
})
|
||||
}
|
16
middleware/use.go
Normal file
16
middleware/use.go
Normal file
@ -0,0 +1,16 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"slices"
|
||||
)
|
||||
|
||||
func Use(handler http.Handler, middlewares ...Middleware) http.Handler {
|
||||
slices.Reverse(middlewares)
|
||||
|
||||
for _, middleware := range middlewares {
|
||||
handler = middleware.WrapHandler(handler)
|
||||
}
|
||||
|
||||
return handler
|
||||
}
|
Reference in New Issue
Block a user