• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

foomo / contentserver / 15108131092

19 May 2025 08:29AM UTC coverage: 41.461% (-20.9%) from 62.396%
15108131092

push

github

web-flow
Merge pull request #49 from foomo/feature/build-with-safe-tag

feat: build with safe tag

15 of 43 new or added lines in 5 files covered. (34.88%)

376 existing lines in 11 files now uncovered.

755 of 1821 relevant lines covered (41.46%)

9.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/pkg/handler/http.go
1
package handler
2

3
import (
4
        "io"
5
        "net/http"
6
        "strings"
7
        "time"
8

9
        "github.com/foomo/contentserver/pkg/metrics"
10
        "github.com/foomo/contentserver/pkg/repo"
11
        "github.com/foomo/contentserver/requests"
12
        "github.com/foomo/contentserver/responses"
13
        httputils "github.com/foomo/keel/utils/net/http"
14
        "github.com/pkg/errors"
15
        "go.uber.org/zap"
16
)
17

18
type (
19
        HTTP struct {
20
                l        *zap.Logger
21
                repo     *repo.Repo
22
                basePath string
23
        }
24
        HTTPOption func(*HTTP)
25
)
26

27
// ------------------------------------------------------------------------------------------------
28
// ~ Constructor
29
// ------------------------------------------------------------------------------------------------
30

31
// NewHTTP returns a shiny new web server
UNCOV
32
func NewHTTP(l *zap.Logger, repo *repo.Repo, opts ...HTTPOption) http.Handler {
×
UNCOV
33
        inst := &HTTP{
×
UNCOV
34
                l:        l.Named("http"),
×
UNCOV
35
                basePath: "/contentserver",
×
UNCOV
36
                repo:     repo,
×
UNCOV
37
        }
×
UNCOV
38

×
UNCOV
39
        for _, opt := range opts {
×
40
                opt(inst)
×
41
        }
×
42

UNCOV
43
        return inst
×
44
}
45

46
// ------------------------------------------------------------------------------------------------
47
// ~ Options
48
// ------------------------------------------------------------------------------------------------
49

50
func WithBasePath(v string) HTTPOption {
×
51
        return func(o *HTTP) {
×
52
                o.basePath = v
×
53
        }
×
54
}
55

56
// ------------------------------------------------------------------------------------------------
57
// ~ Public methods
58
// ------------------------------------------------------------------------------------------------
59

UNCOV
60
func (h *HTTP) ServeHTTP(w http.ResponseWriter, r *http.Request) {
×
UNCOV
61
        if r.Method != http.MethodPost {
×
62
                httputils.ServerError(h.l, w, r, http.StatusMethodNotAllowed, errors.New("method not allowed"))
×
63
                return
×
64
        }
×
UNCOV
65
        if r.Body == nil {
×
66
                httputils.BadRequestServerError(h.l, w, r, errors.New("empty request body"))
×
67
                return
×
68
        }
×
69

UNCOV
70
        bytes, err := io.ReadAll(r.Body)
×
UNCOV
71
        if err != nil {
×
72
                httputils.BadRequestServerError(h.l, w, r, errors.Wrap(err, "failed to read incoming request"))
×
73
                return
×
74
        }
×
75

UNCOV
76
        route := Route(strings.TrimPrefix(r.URL.Path, h.basePath+"/"))
×
UNCOV
77
        if route == RouteGetRepo {
×
UNCOV
78
                h.repo.WriteRepoBytes(w)
×
UNCOV
79
                w.Header().Set("Content-Type", "application/json")
×
UNCOV
80
                return
×
UNCOV
81
        }
×
82

UNCOV
83
        reply, errReply := h.handleRequest(h.repo, route, bytes, "webserver")
×
UNCOV
84
        if errReply != nil {
×
85
                http.Error(w, errReply.Error(), http.StatusInternalServerError)
×
86
                return
×
87
        }
×
UNCOV
88
        _, _ = w.Write(reply)
×
89
}
90

91
// ------------------------------------------------------------------------------------------------
92
// ~ Private methods
93
// ------------------------------------------------------------------------------------------------
94

UNCOV
95
func (h *HTTP) handleRequest(r *repo.Repo, route Route, jsonBytes []byte, source string) ([]byte, error) {
×
UNCOV
96
        start := time.Now()
×
UNCOV
97

×
UNCOV
98
        reply, err := h.executeRequest(r, route, jsonBytes, source)
×
UNCOV
99
        result := "success"
×
UNCOV
100
        if err != nil {
×
101
                result = "error"
×
102
        }
×
103

UNCOV
104
        metrics.ServiceRequestCounter.WithLabelValues(string(route), result, source).Inc()
×
UNCOV
105
        metrics.ServiceRequestDuration.WithLabelValues(string(route), result, source).Observe(time.Since(start).Seconds())
×
UNCOV
106

×
UNCOV
107
        return reply, err
×
108
}
109

UNCOV
110
func (h *HTTP) executeRequest(r *repo.Repo, route Route, jsonBytes []byte, source string) (replyBytes []byte, err error) {
×
UNCOV
111
        var (
×
UNCOV
112
                reply             interface{}
×
UNCOV
113
                apiErr            error
×
UNCOV
114
                jsonErr           error
×
UNCOV
115
                processIfJSONIsOk = func(err error, processingFunc func()) {
×
UNCOV
116
                        if err != nil {
×
117
                                jsonErr = err
×
118
                                return
×
119
                        }
×
UNCOV
120
                        processingFunc()
×
121
                }
122
        )
UNCOV
123
        metrics.ContentRequestCounter.WithLabelValues(source).Inc()
×
UNCOV
124

×
UNCOV
125
        // handle and process
×
UNCOV
126
        switch route {
×
127
        // case HandlerGetRepo: // This case is handled prior to handleRequest being called.
128
        // since the resulting bytes are written directly in to the http.ResponseWriter / net.Connection
UNCOV
129
        case RouteGetURIs:
×
UNCOV
130
                getURIRequest := &requests.URIs{}
×
UNCOV
131
                processIfJSONIsOk(json.Unmarshal(jsonBytes, &getURIRequest), func() {
×
UNCOV
132
                        reply = r.GetURIs(getURIRequest.Dimension, getURIRequest.IDs)
×
UNCOV
133
                })
×
UNCOV
134
        case RouteGetContent:
×
UNCOV
135
                contentRequest := &requests.Content{}
×
UNCOV
136
                processIfJSONIsOk(json.Unmarshal(jsonBytes, &contentRequest), func() {
×
UNCOV
137
                        reply, apiErr = r.GetContent(contentRequest)
×
UNCOV
138
                })
×
UNCOV
139
        case RouteGetNodes:
×
UNCOV
140
                nodesRequest := &requests.Nodes{}
×
UNCOV
141
                processIfJSONIsOk(json.Unmarshal(jsonBytes, &nodesRequest), func() {
×
UNCOV
142
                        reply = r.GetNodes(nodesRequest)
×
UNCOV
143
                })
×
UNCOV
144
        case RouteUpdate:
×
UNCOV
145
                updateRequest := &requests.Update{}
×
UNCOV
146
                processIfJSONIsOk(json.Unmarshal(jsonBytes, &updateRequest), func() {
×
UNCOV
147
                        reply = r.Update()
×
UNCOV
148
                })
×
149
        default:
×
150
                reply = responses.NewError(1, "unknown route: "+string(route))
×
151
        }
152

153
        // error handling
UNCOV
154
        if jsonErr != nil {
×
155
                h.l.Error("could not read incoming json", zap.Error(jsonErr))
×
156
                reply = responses.NewError(2, "could not read incoming json "+jsonErr.Error())
×
UNCOV
157
        } else if apiErr != nil {
×
158
                h.l.Error("an API error occurred", zap.Error(apiErr))
×
159
                reply = responses.NewError(3, "internal error "+apiErr.Error())
×
160
        }
×
161

UNCOV
162
        return h.encodeReply(reply)
×
163
}
164

165
// encodeReply takes an interface and encodes it as JSON
166
// it returns the resulting JSON and a marshalling error
UNCOV
167
func (h *HTTP) encodeReply(reply interface{}) (bytes []byte, err error) {
×
UNCOV
168
        bytes, err = json.Marshal(map[string]interface{}{
×
UNCOV
169
                "reply": reply,
×
UNCOV
170
        })
×
UNCOV
171
        if err != nil {
×
172
                h.l.Error("could not encode reply", zap.Error(err))
×
173
        }
×
UNCOV
174
        return
×
175
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc