Skip to content

Commit

Permalink
Support HEAD requests for artwork endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
ironsmile committed May 19, 2024
1 parent 6df6236 commit 1f78d72
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 9 deletions.
8 changes: 6 additions & 2 deletions src/webserver/apiv1_endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ const (
// It is an uri_path => list of HTTP methods map.
var APIv1Methods map[string][]string = map[string][]string{
APIv1EndpointFile: {http.MethodGet},
APIv1EndpointAlbumArtwork: {http.MethodGet, http.MethodPut, http.MethodDelete},
APIv1EndpointDownloadAlbum: {http.MethodGet},
APIv1EndpointArtistImage: {http.MethodGet, http.MethodPut, http.MethodDelete},
APIv1EndpointBrowse: {http.MethodGet},
APIv1EndpointSearchWithPath: {http.MethodGet},
APIv1EndpointSearch: {http.MethodGet},
APIv1EndpointLoginToken: {http.MethodPost},
APIv1EndpointRegisterToken: {http.MethodPost},
APIv1EndpointArtistImage: {
http.MethodGet, http.MethodHead, http.MethodPut, http.MethodDelete,
},
APIv1EndpointAlbumArtwork: {
http.MethodGet, http.MethodHead, http.MethodPut, http.MethodDelete,
},
}
11 changes: 10 additions & 1 deletion src/webserver/handler_album_artwork.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ func (aah AlbumArtworkHandler) Find(

if err == library.ErrArtworkNotFound || os.IsNotExist(err) {
writer.WriteHeader(http.StatusNotFound)
if req.Method == http.MethodHead {
return nil
}

notFoundImage, err := aah.rootFS.Open(aah.notFoundPath)
if err == nil {
defer notFoundImage.Close()
Expand All @@ -96,8 +100,13 @@ func (aah AlbumArtworkHandler) Find(
defer imgReader.Close()

writer.Header().Set("Cache-Control", "max-age=604800")
_, err = io.Copy(writer, imgReader)
if req.Method == http.MethodHead {
n, _ := io.Copy(io.Discard, imgReader)
writer.Header().Set("Content-Length", strconv.FormatInt(n, 10))
return nil
}

_, err = io.Copy(writer, imgReader)
if err != nil {
log.Printf("еrror sending HTTP data for artwork %d: %s", id, err)
}
Expand Down
25 changes: 23 additions & 2 deletions src/webserver/handler_album_artwork_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"testing/fstest"
Expand All @@ -19,9 +20,9 @@ import (
"github.com/ironsmile/euterpe/src/webserver"
)

// TestAlbumArtworkHandlerGET makes sure the artwork handler is processing the HTTP
// TestAlbumArtworkHandler makes sure the artwork handler is processing the HTTP
// request correctly and sending the expected arguments to its artwork manager.
func TestAlbumArtworkHandlerGET(t *testing.T) {
func TestAlbumArtworkHandler(t *testing.T) {
imgBytesOriginal := []byte("album 321 image original")
imgBytesSmall := []byte("album 321 image small")

Expand Down Expand Up @@ -155,6 +156,26 @@ func TestAlbumArtworkHandlerGET(t *testing.T) {
respString,
)
}

// Test the HEAD request. It should return the Content-Length and 200 for
// images which are present.
resp = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodHead, "/v1/album/321/artwork?size=small", nil)
handler.ServeHTTP(resp, req)

if resp.Code != http.StatusOK {
t.Errorf("HEAD small: expected code %d but got %d", http.StatusOK, resp.Code)
}

clExpected := strconv.FormatInt(int64(len(imgBytesSmall)), 10)
if cl := resp.Result().Header.Get("Content-Length"); clExpected != cl {
t.Errorf("HEAD small: expected Content-Length `%s` but got `%s`",
clExpected, cl)
}
respBodySize, err := io.Copy(io.Discard, resp.Result().Body)
if err != nil || respBodySize != 0 {
t.Errorf("HEAD response should have empty body")
}
}

// TestAlbumArtworkHandlerDELETE tests what happens when artwork is removed.
Expand Down
11 changes: 10 additions & 1 deletion src/webserver/handler_artist_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ func (aih ArtstImageHandler) Find(

if err == library.ErrArtworkNotFound || os.IsNotExist(err) {
writer.WriteHeader(http.StatusNotFound)
if req.Method == http.MethodHead {
return nil
}

fmt.Fprintln(writer, "404 image not found")
return nil
}
Expand All @@ -86,8 +90,13 @@ func (aih ArtstImageHandler) Find(
defer imgReader.Close()

writer.Header().Set("Cache-Control", "max-age=604800")
_, err = io.Copy(writer, imgReader)
if req.Method == http.MethodHead {
n, _ := io.Copy(io.Discard, imgReader)
writer.Header().Set("Content-Length", strconv.FormatInt(n, 10))
return nil
}

_, err = io.Copy(writer, imgReader)
if err != nil {
log.Printf("еrror sending HTTP data for artwork %d: %s", id, err)
}
Expand Down
24 changes: 22 additions & 2 deletions src/webserver/handler_artist_images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"

Expand All @@ -17,9 +18,9 @@ import (
"github.com/ironsmile/euterpe/src/webserver"
)

// TestArtstImageHandlerGET checks that the HTTP handler for GET requests is parsing
// TestArtstImageHandler checks that the HTTP handler for GET requests is parsing
// its arguments as expected and responds with the image found by its image manager.
func TestArtstImageHandlerGET(t *testing.T) {
func TestArtstImageHandler(t *testing.T) {
imgBytesOriginal := []byte("artist 321 image original")
imgBytesSmall := []byte("artist 321 image small")

Expand Down Expand Up @@ -128,6 +129,25 @@ func TestArtstImageHandlerGET(t *testing.T) {
respString,
)
}

// Test the HEDE reqeuest.
resp = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodHead, "/v1/artist/321/image?size=small", nil)
handler.ServeHTTP(resp, req)

if resp.Code != http.StatusOK {
t.Errorf("HEAD small: expected code %d but got %d", http.StatusOK, resp.Code)
}

clExpected := strconv.FormatInt(int64(len(imgBytesSmall)), 10)
if cl := resp.Result().Header.Get("Content-Length"); clExpected != cl {
t.Errorf("HEAD Dsmall: expected image `%s` but got `%s`",
clExpected, cl)
}
respBodySize, err := io.Copy(io.Discard, resp.Result().Body)
if err != nil || respBodySize != 0 {
t.Errorf("HEAD response should have empty body")
}
}

// TestArtstImageHandlerDELETE tests what happens when artwork is removed.
Expand Down
2 changes: 1 addition & 1 deletion src/webserver/subsonic/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (s *subsonic) initRouter() {
setUpHandler("/getArtist", s.getArtist)
setUpHandler("/getArtistInfo", s.getArtistInfo)
setUpHandler("/getArtistInfo2", s.getArtistInfo2)
setUpHandler("/getCoverArt", s.getCoverArt)
setUpHandler("/getCoverArt", s.getCoverArt, "GET", "HEAD")
setUpHandler("/stream", s.stream, "GET", "HEAD")
setUpHandler("/download", s.stream, "GET", "HEAD")
setUpHandler("/getSong", s.getSong)
Expand Down

0 comments on commit 1f78d72

Please sign in to comment.