boulder/web/context.go

107 lines
2.7 KiB
Go

package web
import (
"encoding/json"
"fmt"
"net/http"
"time"
"golang.org/x/net/context"
blog "github.com/letsencrypt/boulder/log"
)
type RequestEvent struct {
RealIP string `json:",omitempty"`
Endpoint string `json:",omitempty"`
Slug string `json:",omitempty"`
Method string `json:",omitempty"`
Errors []string `json:",omitempty"`
Requester int64 `json:",omitempty"`
Contacts *[]string `json:",omitempty"`
UserAgent string `json:",omitempty"`
Latency float64
Code int
Payload string `json:",omitempty"`
Extra map[string]interface{} `json:",omitempty"`
}
func (e *RequestEvent) AddError(msg string, args ...interface{}) {
e.Errors = append(e.Errors, fmt.Sprintf(msg, args...))
}
type WFEHandlerFunc func(context.Context, *RequestEvent, http.ResponseWriter, *http.Request)
func (f WFEHandlerFunc) ServeHTTP(e *RequestEvent, w http.ResponseWriter, r *http.Request) {
ctx := context.TODO()
f(ctx, e, w, r)
}
type wfeHandler interface {
ServeHTTP(e *RequestEvent, w http.ResponseWriter, r *http.Request)
}
type TopHandler struct {
wfe wfeHandler
log blog.Logger
}
func NewTopHandler(log blog.Logger, wfe wfeHandler) *TopHandler {
return &TopHandler{
wfe: wfe,
log: log,
}
}
// responseWriterWithStatus satisfies http.ResponseWriter, but keeps track of the
// status code for logging.
type responseWriterWithStatus struct {
http.ResponseWriter
code int
}
// WriteHeader stores a status code for generating stats.
func (r *responseWriterWithStatus) WriteHeader(code int) {
r.code = code
r.ResponseWriter.WriteHeader(code)
}
func (th *TopHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
logEvent := &RequestEvent{
RealIP: r.Header.Get("X-Real-IP"),
Method: r.Method,
UserAgent: r.Header.Get("User-Agent"),
Extra: make(map[string]interface{}, 0),
}
begin := time.Now()
rwws := &responseWriterWithStatus{w, 0}
defer func() {
logEvent.Code = rwws.code
logEvent.Latency = float64(time.Since(begin)) / float64(time.Second)
th.logEvent(logEvent)
}()
th.wfe.ServeHTTP(logEvent, rwws, r)
}
func (th *TopHandler) logEvent(logEvent *RequestEvent) {
var msg string
jsonEvent, err := json.Marshal(logEvent)
if err != nil {
th.log.AuditErr(fmt.Sprintf("failed to marshal logEvent - %s - %#v", msg, err))
return
}
th.log.Info(fmt.Sprintf("JSON=%s", jsonEvent))
}
// Comma-separated list of HTTP clients involved in making this
// request, starting with the original requestor and ending with the
// remote end of our TCP connection (which is typically our own
// proxy).
func GetClientAddr(r *http.Request) string {
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
return xff + "," + r.RemoteAddr
}
return r.RemoteAddr
}