Răsfoiți Sursa

refactor(http): use explicit response body setters

Frédéric Guillot 3 săptămâni în urmă
părinte
comite
5c414f586b

+ 1 - 1
internal/googlereader/handler.go

@@ -206,7 +206,7 @@ func (h *handler) clientLoginHandler(w http.ResponseWriter, r *http.Request) {
 
 	builder := response.New(w, r)
 	builder.WithHeader("Content-Type", "text/plain; charset=UTF-8")
-	builder.WithBody(result.String())
+	builder.WithBodyAsString(result.String())
 	builder.Write()
 }
 

+ 14 - 2
internal/http/response/builder.go

@@ -39,8 +39,20 @@ func (b *Builder) WithHeader(key, value string) *Builder {
 	return b
 }
 
-// WithBody uses the given body to build the response.
-func (b *Builder) WithBody(body any) *Builder {
+// WithBodyAsBytes uses the given bytes to build the response.
+func (b *Builder) WithBodyAsBytes(body []byte) *Builder {
+	b.body = body
+	return b
+}
+
+// WithBodyAsString uses the given string to build the response.
+func (b *Builder) WithBodyAsString(body string) *Builder {
+	b.body = body
+	return b
+}
+
+// WithBodyAsReader uses the given reader to build the response.
+func (b *Builder) WithBodyAsReader(body io.Reader) *Builder {
 	b.body = body
 	return b
 }

+ 29 - 9
internal/http/response/builder_test.go

@@ -4,6 +4,7 @@
 package response // import "miniflux.app/v2/internal/http/response"
 
 import (
+	"bytes"
 	"net/http"
 	"net/http/httptest"
 	"strings"
@@ -113,7 +114,7 @@ func TestBuildResponseWithByteBody(t *testing.T) {
 	w := httptest.NewRecorder()
 
 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		New(w, r).WithBody([]byte("body")).Write()
+		New(w, r).WithBodyAsBytes([]byte("body")).Write()
 	})
 
 	handler.ServeHTTP(w, r)
@@ -135,7 +136,7 @@ func TestBuildResponseWithCachingEnabled(t *testing.T) {
 
 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		New(w, r).WithCaching("etag", 1*time.Minute, func(b *Builder) {
-			b.WithBody("cached body")
+			b.WithBodyAsString("cached body")
 			b.Write()
 		})
 	})
@@ -176,7 +177,7 @@ func TestBuildResponseWithCachingAndEtag(t *testing.T) {
 
 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		New(w, r).WithCaching("etag", 1*time.Minute, func(b *Builder) {
-			b.WithBody("cached body")
+			b.WithBodyAsString("cached body")
 			b.Write()
 		})
 	})
@@ -217,7 +218,7 @@ func TestBuildResponseWithBrotliCompression(t *testing.T) {
 	w := httptest.NewRecorder()
 
 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		New(w, r).WithBody(body).Write()
+		New(w, r).WithBodyAsString(body).Write()
 	})
 
 	handler.ServeHTTP(w, r)
@@ -241,7 +242,7 @@ func TestBuildResponseWithGzipCompression(t *testing.T) {
 	w := httptest.NewRecorder()
 
 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		New(w, r).WithBody(body).Write()
+		New(w, r).WithBodyAsString(body).Write()
 	})
 
 	handler.ServeHTTP(w, r)
@@ -265,7 +266,7 @@ func TestBuildResponseWithDeflateCompression(t *testing.T) {
 	w := httptest.NewRecorder()
 
 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		New(w, r).WithBody(body).Write()
+		New(w, r).WithBodyAsString(body).Write()
 	})
 
 	handler.ServeHTTP(w, r)
@@ -289,7 +290,7 @@ func TestBuildResponseWithCompressionDisabled(t *testing.T) {
 	w := httptest.NewRecorder()
 
 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		New(w, r).WithBody(body).WithoutCompression().Write()
+		New(w, r).WithBodyAsString(body).WithoutCompression().Write()
 	})
 
 	handler.ServeHTTP(w, r)
@@ -313,7 +314,7 @@ func TestBuildResponseWithDeflateCompressionAndSmallPayload(t *testing.T) {
 	w := httptest.NewRecorder()
 
 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		New(w, r).WithBody(body).Write()
+		New(w, r).WithBodyAsString(body).Write()
 	})
 
 	handler.ServeHTTP(w, r)
@@ -336,7 +337,7 @@ func TestBuildResponseWithoutCompressionHeader(t *testing.T) {
 	w := httptest.NewRecorder()
 
 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		New(w, r).WithBody(body).Write()
+		New(w, r).WithBodyAsString(body).Write()
 	})
 
 	handler.ServeHTTP(w, r)
@@ -348,3 +349,22 @@ func TestBuildResponseWithoutCompressionHeader(t *testing.T) {
 		t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
 	}
 }
+
+func TestBuildResponseWithReaderBody(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		New(w, r).WithBodyAsReader(bytes.NewBufferString("body")).Write()
+	})
+
+	handler.ServeHTTP(w, r)
+
+	if actualBody := w.Body.String(); actualBody != "body" {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, "body")
+	}
+}

+ 11 - 6
internal/http/response/html.go

@@ -16,7 +16,12 @@ func HTML[T []byte | string](w http.ResponseWriter, r *http.Request, body T) {
 	builder := New(w, r)
 	builder.WithHeader("Content-Type", "text/html; charset=utf-8")
 	builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
-	builder.WithBody(body)
+	switch v := any(body).(type) {
+	case []byte:
+		builder.WithBodyAsBytes(v)
+	case string:
+		builder.WithBodyAsString(v)
+	}
 	builder.Write()
 }
 
@@ -40,7 +45,7 @@ func HTMLServerError(w http.ResponseWriter, r *http.Request, err error) {
 	builder.WithHeader("Content-Security-Policy", ContentSecurityPolicyForUntrustedContent)
 	builder.WithHeader("Content-Type", "text/plain; charset=utf-8")
 	builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
-	builder.WithBody(html.EscapeString(err.Error()))
+	builder.WithBodyAsString(html.EscapeString(err.Error()))
 	builder.Write()
 }
 
@@ -64,7 +69,7 @@ func HTMLBadRequest(w http.ResponseWriter, r *http.Request, err error) {
 	builder.WithHeader("Content-Security-Policy", ContentSecurityPolicyForUntrustedContent)
 	builder.WithHeader("Content-Type", "text/plain; charset=utf-8")
 	builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
-	builder.WithBody(html.EscapeString(err.Error()))
+	builder.WithBodyAsString(html.EscapeString(err.Error()))
 	builder.Write()
 }
 
@@ -86,7 +91,7 @@ func HTMLForbidden(w http.ResponseWriter, r *http.Request) {
 	builder.WithStatus(http.StatusForbidden)
 	builder.WithHeader("Content-Type", "text/html; charset=utf-8")
 	builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
-	builder.WithBody("Access Forbidden")
+	builder.WithBodyAsString("Access Forbidden")
 	builder.Write()
 }
 
@@ -108,7 +113,7 @@ func HTMLNotFound(w http.ResponseWriter, r *http.Request) {
 	builder.WithStatus(http.StatusNotFound)
 	builder.WithHeader("Content-Type", "text/html; charset=utf-8")
 	builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
-	builder.WithBody("Page Not Found")
+	builder.WithBodyAsString("Page Not Found")
 	builder.Write()
 }
 
@@ -136,6 +141,6 @@ func HTMLRequestedRangeNotSatisfiable(w http.ResponseWriter, r *http.Request, co
 	builder.WithHeader("Content-Type", "text/html; charset=utf-8")
 	builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
 	builder.WithHeader("Content-Range", contentRange)
-	builder.WithBody("Range Not Satisfiable")
+	builder.WithBodyAsString("Range Not Satisfiable")
 	builder.Write()
 }

+ 7 - 7
internal/http/response/json.go

@@ -24,7 +24,7 @@ func JSON(w http.ResponseWriter, r *http.Request, body any) {
 
 	builder := New(w, r)
 	builder.WithHeader("Content-Type", jsonContentTypeHeader)
-	builder.WithBody(responseBody)
+	builder.WithBodyAsBytes(responseBody)
 	builder.Write()
 }
 
@@ -39,7 +39,7 @@ func JSONCreated(w http.ResponseWriter, r *http.Request, body any) {
 	builder := New(w, r)
 	builder.WithStatus(http.StatusCreated)
 	builder.WithHeader("Content-Type", jsonContentTypeHeader)
-	builder.WithBody(responseBody)
+	builder.WithBodyAsBytes(responseBody)
 	builder.Write()
 }
 
@@ -77,7 +77,7 @@ func JSONServerError(w http.ResponseWriter, r *http.Request, err error) {
 	builder := New(w, r)
 	builder.WithStatus(http.StatusInternalServerError)
 	builder.WithHeader("Content-Type", jsonContentTypeHeader)
-	builder.WithBody(generateJSONError(err))
+	builder.WithBodyAsBytes(generateJSONError(err))
 	builder.Write()
 }
 
@@ -99,7 +99,7 @@ func JSONBadRequest(w http.ResponseWriter, r *http.Request, err error) {
 	builder := New(w, r)
 	builder.WithStatus(http.StatusBadRequest)
 	builder.WithHeader("Content-Type", jsonContentTypeHeader)
-	builder.WithBody(generateJSONError(err))
+	builder.WithBodyAsBytes(generateJSONError(err))
 	builder.Write()
 }
 
@@ -120,7 +120,7 @@ func JSONUnauthorized(w http.ResponseWriter, r *http.Request) {
 	builder := New(w, r)
 	builder.WithStatus(http.StatusUnauthorized)
 	builder.WithHeader("Content-Type", jsonContentTypeHeader)
-	builder.WithBody(generateJSONError(errors.New("access unauthorized")))
+	builder.WithBodyAsBytes(generateJSONError(errors.New("access unauthorized")))
 	builder.Write()
 }
 
@@ -141,7 +141,7 @@ func JSONForbidden(w http.ResponseWriter, r *http.Request) {
 	builder := New(w, r)
 	builder.WithStatus(http.StatusForbidden)
 	builder.WithHeader("Content-Type", jsonContentTypeHeader)
-	builder.WithBody(generateJSONError(errors.New("access forbidden")))
+	builder.WithBodyAsBytes(generateJSONError(errors.New("access forbidden")))
 	builder.Write()
 }
 
@@ -162,7 +162,7 @@ func JSONNotFound(w http.ResponseWriter, r *http.Request) {
 	builder := New(w, r)
 	builder.WithStatus(http.StatusNotFound)
 	builder.WithHeader("Content-Type", jsonContentTypeHeader)
-	builder.WithBody(generateJSONError(errors.New("resource not found")))
+	builder.WithBodyAsBytes(generateJSONError(errors.New("resource not found")))
 	builder.Write()
 }
 

+ 4 - 4
internal/http/response/xml.go

@@ -6,18 +6,18 @@ package response // import "miniflux.app/v2/internal/http/response"
 import "net/http"
 
 // XML writes a standard XML response with a status 200 OK.
-func XML[T []byte | string](w http.ResponseWriter, r *http.Request, body T) {
+func XML(w http.ResponseWriter, r *http.Request, body string) {
 	builder := New(w, r)
 	builder.WithHeader("Content-Type", "text/xml; charset=utf-8")
-	builder.WithBody(body)
+	builder.WithBodyAsString(body)
 	builder.Write()
 }
 
 // XMLAttachment forces the XML document to be downloaded by the web browser.
-func XMLAttachment[T []byte | string](w http.ResponseWriter, r *http.Request, filename string, body T) {
+func XMLAttachment(w http.ResponseWriter, r *http.Request, filename string, body string) {
 	builder := New(w, r)
 	builder.WithHeader("Content-Type", "text/xml; charset=utf-8")
 	builder.WithAttachment(filename)
-	builder.WithBody(body)
+	builder.WithBodyAsString(body)
 	builder.Write()
 }

+ 1 - 1
internal/ui/feed_icon.go

@@ -27,7 +27,7 @@ func (h *handler) showFeedIcon(w http.ResponseWriter, r *http.Request) {
 	response.New(w, r).WithCaching(icon.Hash, 72*time.Hour, func(b *response.Builder) {
 		b.WithHeader("Content-Security-Policy", response.ContentSecurityPolicyForUntrustedContent)
 		b.WithHeader("Content-Type", icon.MimeType)
-		b.WithBody(icon.Content)
+		b.WithBodyAsBytes(icon.Content)
 		if icon.MimeType != "image/svg+xml" {
 			b.WithoutCompression()
 		}

+ 1 - 1
internal/ui/proxy.go

@@ -159,7 +159,7 @@ func (h *handler) mediaProxy(w http.ResponseWriter, r *http.Request) {
 				b.WithHeader(responseHeaderName, resp.Header.Get(responseHeaderName))
 			}
 		}
-		b.WithBody(resp.Body)
+		b.WithBodyAsReader(resp.Body)
 		b.WithoutCompression()
 		b.Write()
 	})

+ 1 - 1
internal/ui/share.go

@@ -60,7 +60,7 @@ func (h *handler) sharedEntry(w http.ResponseWriter, r *http.Request) {
 		view.Set("entry", entry)
 
 		b.WithHeader("Content-Type", "text/html; charset=utf-8")
-		b.WithBody(view.Render("entry"))
+		b.WithBodyAsBytes(view.Render("entry"))
 		b.Write()
 	})
 }

+ 1 - 1
internal/ui/static_app_icon.go

@@ -30,7 +30,7 @@ func (h *handler) showAppIcon(w http.ResponseWriter, r *http.Request) {
 		case ".svg":
 			b.WithHeader("Content-Type", "image/svg+xml")
 		}
-		b.WithBody(value.Data)
+		b.WithBodyAsBytes(value.Data)
 		b.Write()
 	})
 }

+ 1 - 1
internal/ui/static_favicon.go

@@ -22,7 +22,7 @@ func (h *handler) showFavicon(w http.ResponseWriter, r *http.Request) {
 	response.New(w, r).WithCaching(value.Checksum, 48*time.Hour, func(b *response.Builder) {
 		b.WithHeader("Content-Type", "image/x-icon")
 		b.WithoutCompression()
-		b.WithBody(value.Data)
+		b.WithBodyAsBytes(value.Data)
 		b.Write()
 	})
 }

+ 1 - 1
internal/ui/static_javascript.go

@@ -40,7 +40,7 @@ func (h *handler) showJavascript(w http.ResponseWriter, r *http.Request) {
 		contents = append(contents, []byte(licenseSuffix)...)
 
 		b.WithHeader("Content-Type", "text/javascript; charset=utf-8")
-		b.WithBody(contents)
+		b.WithBodyAsBytes(contents)
 		b.Write()
 	})
 }

+ 1 - 1
internal/ui/static_stylesheet.go

@@ -23,7 +23,7 @@ func (h *handler) showStylesheet(w http.ResponseWriter, r *http.Request) {
 
 	response.New(w, r).WithCaching(m.Checksum, 48*time.Hour, func(b *response.Builder) {
 		b.WithHeader("Content-Type", "text/css; charset=utf-8")
-		b.WithBody(m.Data)
+		b.WithBodyAsBytes(m.Data)
 		b.Write()
 	})
 }