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

foomo / contentserver / 12019324254

25 Nov 2024 09:43PM UTC coverage: 62.018%. First build
12019324254

push

github

web-flow
Merge pull request #31 from foomo/v1.11.x

Release v1.11.x

852 of 1452 new or added lines in 19 files covered. (58.68%)

1125 of 1814 relevant lines covered (62.02%)

12.7 hits per line

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

69.81
/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
32
func NewHTTP(l *zap.Logger, repo *repo.Repo, opts ...HTTPOption) http.Handler {
5✔
33
        inst := &HTTP{
5✔
34
                l:        l.Named("http"),
5✔
35
                basePath: "/contentserver",
5✔
36
                repo:     repo,
5✔
37
        }
5✔
38

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

43
        return inst
5✔
44
}
45

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

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

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

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

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

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

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

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

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

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

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

4✔
107
        return reply, err
4✔
108
}
109

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

4✔
125
        // handle and process
4✔
126
        switch route {
4✔
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
129
        case RouteGetURIs:
1✔
130
                getURIRequest := &requests.URIs{}
1✔
131
                processIfJSONIsOk(json.Unmarshal(jsonBytes, &getURIRequest), func() {
2✔
132
                        reply = r.GetURIs(getURIRequest.Dimension, getURIRequest.IDs)
1✔
133
                })
1✔
134
        case RouteGetContent:
1✔
135
                contentRequest := &requests.Content{}
1✔
136
                processIfJSONIsOk(json.Unmarshal(jsonBytes, &contentRequest), func() {
2✔
137
                        reply, apiErr = r.GetContent(contentRequest)
1✔
138
                })
1✔
139
        case RouteGetNodes:
1✔
140
                nodesRequest := &requests.Nodes{}
1✔
141
                processIfJSONIsOk(json.Unmarshal(jsonBytes, &nodesRequest), func() {
2✔
142
                        reply = r.GetNodes(nodesRequest)
1✔
143
                })
1✔
144
        case RouteUpdate:
1✔
145
                updateRequest := &requests.Update{}
1✔
146
                processIfJSONIsOk(json.Unmarshal(jsonBytes, &updateRequest), func() {
2✔
147
                        reply = r.Update()
1✔
148
                })
1✔
NEW
149
        default:
×
NEW
150
                reply = responses.NewError(1, "unknown route: "+string(route))
×
151
        }
152

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

162
        return h.encodeReply(reply)
4✔
163
}
164

165
// encodeReply takes an interface and encodes it as JSON
166
// it returns the resulting JSON and a marshalling error
167
func (h *HTTP) encodeReply(reply interface{}) (bytes []byte, err error) {
4✔
168
        bytes, err = json.Marshal(map[string]interface{}{
4✔
169
                "reply": reply,
4✔
170
        })
4✔
171
        if err != nil {
4✔
NEW
172
                h.l.Error("could not encode reply", zap.Error(err))
×
NEW
173
        }
×
174
        return
4✔
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