aboutsummaryrefslogtreecommitdiff
path: root/internal/node/handler/handler.go
blob: 2871c5d16e0aff71ccb493cb8960cc586e5a016c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package handler

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"git.sigsum.org/sigsum-go/pkg/log"
	"git.sigsum.org/sigsum-go/pkg/types"
)

type Config interface {
	Prefix() string
	LogID() string
	Deadline() time.Duration
}

// Handler implements the http.Handler interface
type Handler struct {
	Config
	Fun      func(context.Context, Config, http.ResponseWriter, *http.Request) (int, error)
	Endpoint types.Endpoint
	Method   string
}

// Path returns a path that should be configured for this handler
func (h Handler) Path() string {
	if len(h.Prefix()) == 0 {
		return h.Endpoint.Path("", "sigsum", "v0")
	}
	return h.Endpoint.Path("", h.Prefix(), "sigsum", "v0")
}

// ServeHTTP is part of the http.Handler interface
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	start := time.Now()
	code := 0
	defer func() {
		end := time.Now().Sub(start).Seconds()
		sc := fmt.Sprintf("%d", code)

		rspcnt.Inc(h.LogID(), string(h.Endpoint), sc)
		latency.Observe(end, h.LogID(), string(h.Endpoint), sc)
	}()
	reqcnt.Inc(h.LogID(), string(h.Endpoint))

	code = h.verifyMethod(w, r)
	if code != 0 {
		return
	}
	h.handle(w, r)
}

// verifyMethod checks that an appropriate HTTP method is used and
// returns 0 if so, or an HTTP status code if not.  Error handling is
// based on RFC 7231, see Sections 6.5.5 (Status 405) and 6.5.1
// (Status 400).
func (h Handler) verifyMethod(w http.ResponseWriter, r *http.Request) int {
	checkHTTPMethod := func(m string) bool {
		return m == http.MethodGet || m == http.MethodPost
	}

	if h.Method == r.Method {
		return 0
	}

	code := http.StatusBadRequest
	if ok := checkHTTPMethod(r.Method); ok {
		w.Header().Set("Allow", h.Method)
		code = http.StatusMethodNotAllowed
	}

	http.Error(w, fmt.Sprintf("error=%s", http.StatusText(code)), code)
	return code
}

// handle handles an HTTP request for which the HTTP method is already verified
func (h Handler) handle(w http.ResponseWriter, r *http.Request) {
	deadline := time.Now().Add(h.Deadline())
	ctx, cancel := context.WithDeadline(r.Context(), deadline)
	defer cancel()

	code, err := h.Fun(ctx, h.Config, w, r)
	if err != nil {
		log.Debug("%s/%s: %v", h.Prefix(), h.Endpoint, err)
		http.Error(w, fmt.Sprintf("error=%s", err.Error()), code)
	} else if code != 200 {
		w.WriteHeader(code)
	}
}