Skip to content

Commit e77101d

Browse files
authored
remote: simplified Store interface; renamed Handler to WriteHandler; encapsulated write response (#1855)
* Re store: We don't need to pass context - you have it in http.Request.Context() * Re rename: Technically we have API and Handler is only for remote write, not read, thus making space for "read" one day. * Re write response: I made read flow private, otherwise it's confusing how to use this structure from outside. Signed-off-by: bwplotka <[email protected]>
1 parent f48ff67 commit e77101d

File tree

4 files changed

+57
-62
lines changed

4 files changed

+57
-62
lines changed

exp/api/remote/remote_api.go

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,7 @@ var defaultAPIOpts = &apiOpts{
6464
Max: 10 * time.Second,
6565
MaxRetries: 10,
6666
},
67-
client: http.DefaultClient,
68-
// Hardcoded for now.
67+
client: http.DefaultClient,
6968
retryOnRateLimit: true,
7069
compression: SnappyBlockCompression,
7170
path: "api/v1/write",
@@ -96,7 +95,7 @@ func WithAPIPath(path string) APIOption {
9695
}
9796
}
9897

99-
// WithAPIRetryOnRateLimit returns APIOption that disables retrying on rate limit status code.
98+
// WithAPINoRetryOnRateLimit returns APIOption that disables retrying on rate limit status code.
10099
func WithAPINoRetryOnRateLimit() APIOption {
101100
return func(o *apiOpts) error {
102101
o.retryOnRateLimit = false
@@ -373,45 +372,46 @@ type writeStorage interface {
373372
// Other headers might be trimmed, depending on the configured middlewares
374373
// e.g. a default SnappyMiddleware trims "Content-Encoding" and ensures that
375374
// encoded body bytes are already decompressed.
376-
Store(ctx context.Context, msgType WriteMessageType, req *http.Request) (_ *WriteResponse, _ error)
375+
Store(req *http.Request, msgType WriteMessageType) (_ *WriteResponse, _ error)
377376
}
378377

379-
type handler struct {
378+
type writeHandler struct {
380379
store writeStorage
381380
acceptedMessageTypes MessageTypes
382-
opts handlerOpts
381+
opts writeHandlerOpts
383382
}
384383

385-
type handlerOpts struct {
384+
type writeHandlerOpts struct {
386385
logger *slog.Logger
387386
middlewares []func(http.Handler) http.Handler
388387
}
389388

390-
// HandlerOption represents an option for the handler.
391-
type HandlerOption func(o *handlerOpts)
389+
// WriteHandlerOption represents an option for the write handler.
390+
type WriteHandlerOption func(o *writeHandlerOpts)
392391

393-
// WithHandlerLogger returns HandlerOption that allows providing slog logger.
392+
// WithWriteHandlerLogger returns WriteHandlerOption that allows providing slog logger.
394393
// By default, nothing is logged.
395-
func WithHandlerLogger(logger *slog.Logger) HandlerOption {
396-
return func(o *handlerOpts) {
394+
func WithWriteHandlerLogger(logger *slog.Logger) WriteHandlerOption {
395+
return func(o *writeHandlerOpts) {
397396
o.logger = logger
398397
}
399398
}
400399

401-
// WithHandlerMiddleware returns HandlerOption that allows providing middlewares.
400+
// WithWriteHandlerMiddlewares returns WriteHandlerOption that allows providing middlewares.
402401
// Multiple middlewares can be provided and will be applied in the order they are passed.
403-
// When using this option, SnappyDecompressorMiddleware is not applied by default so
404-
// it (or any other decompression middleware) needs to be added explicitly.
405-
func WithHandlerMiddlewares(middlewares ...func(http.Handler) http.Handler) HandlerOption {
406-
return func(o *handlerOpts) {
402+
// This option replaces the default middlewares (SnappyDecompressorMiddleware), so if
403+
// you want to have handler that works with the default Remote Write 2.0 protocol,
404+
// SnappyDecompressorMiddleware (or any other decompression middleware) needs to be added explicitly.
405+
func WithWriteHandlerMiddlewares(middlewares ...func(http.Handler) http.Handler) WriteHandlerOption {
406+
return func(o *writeHandlerOpts) {
407407
o.middlewares = middlewares
408408
}
409409
}
410410

411-
// SnappyDecompressorMiddleware returns a middleware that checks if the request body is snappy-encoded and decompresses it.
411+
// SnappyDecodeMiddleware returns a middleware that checks if the request body is snappy-encoded and decompresses it.
412412
// If the request body is not snappy-encoded, it returns an error.
413-
// Used by default in NewRemoteWriteHandler.
414-
func SnappyDecompressorMiddleware(logger *slog.Logger) func(http.Handler) http.Handler {
413+
// Used by default in NewHandler.
414+
func SnappyDecodeMiddleware(logger *slog.Logger) func(http.Handler) http.Handler {
415415
bufPool := sync.Pool{
416416
New: func() any {
417417
return bytes.NewBuffer(nil)
@@ -455,18 +455,18 @@ func SnappyDecompressorMiddleware(logger *slog.Logger) func(http.Handler) http.H
455455
}
456456
}
457457

458-
// NewHandler returns HTTP handler that receives Remote Write 2.0
458+
// NewWriteHandler returns HTTP handler that receives Remote Write 2.0
459459
// protocol https://prometheus.io/docs/specs/remote_write_spec_2_0/.
460-
func NewHandler(store writeStorage, acceptedMessageTypes MessageTypes, opts ...HandlerOption) http.Handler {
461-
o := handlerOpts{
460+
func NewWriteHandler(store writeStorage, acceptedMessageTypes MessageTypes, opts ...WriteHandlerOption) http.Handler {
461+
o := writeHandlerOpts{
462462
logger: slog.New(nopSlogHandler{}),
463-
middlewares: []func(http.Handler) http.Handler{SnappyDecompressorMiddleware(slog.New(nopSlogHandler{}))},
463+
middlewares: []func(http.Handler) http.Handler{SnappyDecodeMiddleware(slog.New(nopSlogHandler{}))},
464464
}
465465
for _, opt := range opts {
466466
opt(&o)
467467
}
468468

469-
h := &handler{
469+
h := &writeHandler{
470470
opts: o,
471471
store: store,
472472
acceptedMessageTypes: acceptedMessageTypes,
@@ -513,7 +513,7 @@ func ParseProtoMsg(contentType string) (WriteMessageType, error) {
513513
return WriteV1MessageType, nil
514514
}
515515

516-
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
516+
func (h *writeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
517517
if r.Method != http.MethodPost {
518518
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
519519
return
@@ -538,20 +538,25 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
538538
return
539539
}
540540

541-
writeResponse, storeErr := h.store.Store(r.Context(), msgType, r)
541+
writeResponse, storeErr := h.store.Store(r, msgType)
542+
if writeResponse == nil {
543+
// User could forget to return write response; in this case we assume 0 samples
544+
// were written.
545+
writeResponse = NewWriteResponse()
546+
}
542547

543-
// Set required X-Prometheus-Remote-Write-Written-* response headers, in all cases, alongwith any user-defined headers.
544-
writeResponse.SetHeaders(w)
548+
// Set required X-Prometheus-Remote-Write-Written-* response headers, in all cases, along with any user-defined headers.
549+
writeResponse.writeHeaders(w)
545550

546551
if storeErr != nil {
547-
if writeResponse.StatusCode() == 0 {
552+
if writeResponse.statusCode == 0 {
548553
writeResponse.SetStatusCode(http.StatusInternalServerError)
549554
}
550-
if writeResponse.StatusCode()/100 == 5 { // 5xx
551-
h.opts.logger.Error("Error while storing the remote write request", "err", storeErr.Error())
555+
if writeResponse.statusCode/100 == 5 { // 5xx
556+
h.opts.logger.Error("Error while storing the remote write request", "err", storeErr)
552557
}
553-
http.Error(w, storeErr.Error(), writeResponse.StatusCode())
558+
http.Error(w, storeErr.Error(), writeResponse.statusCode)
554559
return
555560
}
556-
w.WriteHeader(writeResponse.StatusCode())
561+
w.WriteHeader(writeResponse.statusCode)
557562
}

exp/api/remote/remote_api_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ type mockStorage struct {
7171
mockErr error
7272
}
7373

74-
func (m *mockStorage) Store(_ context.Context, msgType WriteMessageType, req *http.Request) (*WriteResponse, error) {
75-
w := &WriteResponse{}
74+
func (m *mockStorage) Store(req *http.Request, msgType WriteMessageType) (*WriteResponse, error) {
75+
w := NewWriteResponse()
7676
if m.mockErr != nil {
7777
if m.mockCode != nil {
7878
w.SetStatusCode(*m.mockCode)
@@ -147,7 +147,7 @@ func TestRemoteAPI_Write_WithHandler(t *testing.T) {
147147
t.Run("success", func(t *testing.T) {
148148
tLogger := slog.Default()
149149
mStore := &mockStorage{}
150-
srv := httptest.NewServer(NewHandler(mStore, MessageTypes{WriteV2MessageType}, WithHandlerLogger(tLogger)))
150+
srv := httptest.NewServer(NewWriteHandler(mStore, MessageTypes{WriteV2MessageType}, WithWriteHandlerLogger(tLogger)))
151151
t.Cleanup(srv.Close)
152152

153153
client, err := NewAPI(srv.URL,
@@ -182,7 +182,7 @@ func TestRemoteAPI_Write_WithHandler(t *testing.T) {
182182
mockErr: errors.New("storage error"),
183183
mockCode: &mockCode,
184184
}
185-
srv := httptest.NewServer(NewHandler(mStore, MessageTypes{WriteV2MessageType}, WithHandlerLogger(tLogger)))
185+
srv := httptest.NewServer(NewWriteHandler(mStore, MessageTypes{WriteV2MessageType}, WithWriteHandlerLogger(tLogger)))
186186
t.Cleanup(srv.Close)
187187

188188
client, err := NewAPI(srv.URL,

exp/api/remote/remote_headers.go

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -125,29 +125,19 @@ func (w *WriteResponse) SetStatusCode(code int) {
125125
w.statusCode = code
126126
}
127127

128-
// StatusCode returns the current HTTP status code.
129-
func (w *WriteResponse) StatusCode() int {
130-
return w.statusCode
131-
}
132-
133128
// SetExtraHeader adds additional headers to be set in the response (apart from stats headers)
134129
func (w *WriteResponse) SetExtraHeader(key, value string) {
135130
w.extraHeaders.Set(key, value)
136131
}
137132

138-
// ExtraHeaders returns all additional headers to be set in the response (apart from stats headers).
139-
func (w *WriteResponse) ExtraHeaders() http.Header {
140-
return w.extraHeaders
141-
}
142-
143-
// SetHeaders sets response headers in a given response writer.
133+
// writeHeaders sets response headers in a given response writer.
144134
// Make sure to use it before http.ResponseWriter.WriteHeader and .Write.
145-
func (r *WriteResponse) SetHeaders(w http.ResponseWriter) {
146-
h := w.Header()
147-
h.Set(writtenSamplesHeader, strconv.Itoa(r.Samples))
148-
h.Set(writtenHistogramsHeader, strconv.Itoa(r.Histograms))
149-
h.Set(writtenExemplarsHeader, strconv.Itoa(r.Exemplars))
150-
for k, v := range r.ExtraHeaders() {
135+
func (w *WriteResponse) writeHeaders(rw http.ResponseWriter) {
136+
h := rw.Header()
137+
h.Set(writtenSamplesHeader, strconv.Itoa(w.Samples))
138+
h.Set(writtenHistogramsHeader, strconv.Itoa(w.Histograms))
139+
h.Set(writtenExemplarsHeader, strconv.Itoa(w.Exemplars))
140+
for k, v := range w.extraHeaders {
151141
for _, vv := range v {
152142
h.Add(k, vv)
153143
}

exp/api/remote/remote_headers_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@ import (
2525
func TestWriteResponse(t *testing.T) {
2626
t.Run("new response has empty headers", func(t *testing.T) {
2727
resp := NewWriteResponse()
28-
if len(resp.ExtraHeaders()) != 0 {
29-
t.Errorf("expected empty headers, got %v", resp.ExtraHeaders())
28+
if len(resp.extraHeaders) != 0 {
29+
t.Errorf("expected empty headers, got %v", resp.extraHeaders)
3030
}
3131
})
3232

33-
t.Run("setters and getters", func(t *testing.T) {
33+
t.Run("setters", func(t *testing.T) {
3434
resp := NewWriteResponse()
3535

3636
resp.SetStatusCode(http.StatusOK)
37-
if got := resp.StatusCode(); got != http.StatusOK {
37+
if got := resp.statusCode; got != http.StatusOK {
3838
t.Errorf("expected status code %d, got %d", http.StatusOK, got)
3939
}
4040

@@ -66,12 +66,12 @@ func TestWriteResponse(t *testing.T) {
6666
}
6767

6868
resp.SetExtraHeader("Test-Header", "test-value")
69-
if got := resp.ExtraHeaders().Get("Test-Header"); got != "test-value" {
69+
if got := resp.extraHeaders.Get("Test-Header"); got != "test-value" {
7070
t.Errorf("expected header value %q, got %q", "test-value", got)
7171
}
7272
})
7373

74-
t.Run("set headers on response writer", func(t *testing.T) {
74+
t.Run("writeHeaders", func(t *testing.T) {
7575
resp := NewWriteResponse()
7676
resp.Add(WriteResponseStats{
7777
Samples: 10,
@@ -82,7 +82,7 @@ func TestWriteResponse(t *testing.T) {
8282
resp.SetExtraHeader("Custom-Header", "custom-value")
8383

8484
w := httptest.NewRecorder()
85-
resp.SetHeaders(w)
85+
resp.writeHeaders(w)
8686

8787
expectedHeaders := map[string]string{
8888
"Custom-Header": "custom-value",

0 commit comments

Comments
 (0)