mirror of
https://github.com/navidrome/navidrome.git
synced 2026-02-26 03:46:18 -05:00
Compare commits
2 Commits
master
...
feat/disab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cccbbfa723 | ||
|
|
b74db6c2c0 |
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
@@ -13,6 +12,7 @@ import (
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/utils/cache"
|
||||
"github.com/navidrome/navidrome/utils/httpclient"
|
||||
"github.com/navidrome/navidrome/utils/slice"
|
||||
)
|
||||
|
||||
@@ -34,9 +34,7 @@ func deezerConstructor(dataStore model.DataStore) agents.Interface {
|
||||
dataStore: dataStore,
|
||||
languages: conf.Server.Deezer.Languages,
|
||||
}
|
||||
httpClient := &http.Client{
|
||||
Timeout: consts.DefaultHttpClientTimeOut,
|
||||
}
|
||||
httpClient := httpclient.New(consts.DefaultHttpClientTimeOut)
|
||||
cachedHttpClient := cache.NewHTTPClient(httpClient, consts.DefaultHttpClientTimeOut)
|
||||
agent.client = newClient(cachedHttpClient)
|
||||
return agent
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/utils/cache"
|
||||
"github.com/navidrome/navidrome/utils/httpclient"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
@@ -59,9 +60,7 @@ func lastFMConstructor(ds model.DataStore) *lastfmAgent {
|
||||
secret: conf.Server.LastFM.Secret,
|
||||
sessionKeys: &agents.SessionKeys{DataStore: ds, KeyName: sessionKeyProperty},
|
||||
}
|
||||
hc := &http.Client{
|
||||
Timeout: consts.DefaultHttpClientTimeOut,
|
||||
}
|
||||
hc := httpclient.New(consts.DefaultHttpClientTimeOut)
|
||||
chc := cache.NewHTTPClient(hc, consts.DefaultHttpClientTimeOut)
|
||||
l.httpClient = chc
|
||||
l.client = newClient(l.apiKey, l.secret, chc)
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
"github.com/navidrome/navidrome/server"
|
||||
"github.com/navidrome/navidrome/utils/httpclient"
|
||||
"github.com/navidrome/navidrome/utils/req"
|
||||
)
|
||||
|
||||
@@ -41,9 +42,7 @@ func NewRouter(ds model.DataStore) *Router {
|
||||
sessionKeys: &agents.SessionKeys{DataStore: ds, KeyName: sessionKeyProperty},
|
||||
}
|
||||
r.Handler = r.routes()
|
||||
hc := &http.Client{
|
||||
Timeout: consts.DefaultHttpClientTimeOut,
|
||||
}
|
||||
hc := httpclient.New(consts.DefaultHttpClientTimeOut)
|
||||
r.client = newClient(r.apiKey, r.secret, hc)
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package listenbrainz
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/consts"
|
||||
@@ -12,6 +11,7 @@ import (
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/utils/cache"
|
||||
"github.com/navidrome/navidrome/utils/httpclient"
|
||||
"github.com/navidrome/navidrome/utils/slice"
|
||||
)
|
||||
|
||||
@@ -33,9 +33,7 @@ func listenBrainzConstructor(ds model.DataStore) *listenBrainzAgent {
|
||||
sessionKeys: &agents.SessionKeys{DataStore: ds, KeyName: sessionKeyProperty},
|
||||
baseURL: conf.Server.ListenBrainz.BaseURL,
|
||||
}
|
||||
hc := &http.Client{
|
||||
Timeout: consts.DefaultHttpClientTimeOut,
|
||||
}
|
||||
hc := httpclient.New(consts.DefaultHttpClientTimeOut)
|
||||
chc := cache.NewHTTPClient(hc, consts.DefaultHttpClientTimeOut)
|
||||
l.client = newClient(l.baseURL, chc)
|
||||
return l
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
"github.com/navidrome/navidrome/server"
|
||||
"github.com/navidrome/navidrome/utils/httpclient"
|
||||
)
|
||||
|
||||
type sessionKeysRepo interface {
|
||||
@@ -37,9 +38,7 @@ func NewRouter(ds model.DataStore) *Router {
|
||||
sessionKeys: &agents.SessionKeys{DataStore: ds, KeyName: sessionKeyProperty},
|
||||
}
|
||||
r.Handler = r.routes()
|
||||
hc := &http.Client{
|
||||
Timeout: consts.DefaultHttpClientTimeOut,
|
||||
}
|
||||
hc := httpclient.New(consts.DefaultHttpClientTimeOut)
|
||||
r.client = newClient(conf.Server.ListenBrainz.BaseURL, hc)
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -14,6 +13,7 @@ import (
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/utils/cache"
|
||||
"github.com/navidrome/navidrome/utils/httpclient"
|
||||
"github.com/xrash/smetrics"
|
||||
)
|
||||
|
||||
@@ -35,9 +35,7 @@ func spotifyConstructor(ds model.DataStore) agents.Interface {
|
||||
id: conf.Server.Spotify.ID,
|
||||
secret: conf.Server.Spotify.Secret,
|
||||
}
|
||||
hc := &http.Client{
|
||||
Timeout: consts.DefaultHttpClientTimeOut,
|
||||
}
|
||||
hc := httpclient.New(consts.DefaultHttpClientTimeOut)
|
||||
chc := cache.NewHTTPClient(hc, consts.DefaultHttpClientTimeOut)
|
||||
l.client = newClient(l.id, l.secret, chc)
|
||||
return l
|
||||
|
||||
@@ -46,6 +46,7 @@ type configOptions struct {
|
||||
EnableTranscodingCancellation bool
|
||||
EnableDownloads bool
|
||||
EnableExternalServices bool
|
||||
EnablePostQuantumTLS bool
|
||||
EnableInsightsCollector bool
|
||||
EnableMediaFileCoverArt bool
|
||||
TranscodingCacheSize string
|
||||
@@ -631,6 +632,7 @@ func setViperDefaults() {
|
||||
viper.SetDefault("smartPlaylistRefreshDelay", 5*time.Second)
|
||||
viper.SetDefault("enabledownloads", true)
|
||||
viper.SetDefault("enableexternalservices", true)
|
||||
viper.SetDefault("enablepostquantumtls", false)
|
||||
viper.SetDefault("enablemediafilecoverart", true)
|
||||
viper.SetDefault("autotranscodedownload", false)
|
||||
viper.SetDefault("defaultdownsamplingformat", consts.DefaultDownsamplingFormat)
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/resources"
|
||||
"github.com/navidrome/navidrome/utils/httpclient"
|
||||
"go.senan.xyz/taglib"
|
||||
)
|
||||
|
||||
@@ -227,7 +228,7 @@ func fromAlbumExternalSource(ctx context.Context, al model.Album, provider exter
|
||||
}
|
||||
|
||||
func fromURL(ctx context.Context, imageUrl *url.URL) (io.ReadCloser, string, error) {
|
||||
hc := http.Client{Timeout: 5 * time.Second}
|
||||
hc := httpclient.New(5 * time.Second)
|
||||
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, imageUrl.String(), nil)
|
||||
req.Header.Set("User-Agent", consts.HTTPUserAgent)
|
||||
resp, err := hc.Do(req) //nolint:gosec
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
"github.com/navidrome/navidrome/plugins"
|
||||
"github.com/navidrome/navidrome/server/events"
|
||||
"github.com/navidrome/navidrome/utils/httpclient"
|
||||
"github.com/navidrome/navidrome/utils/singleton"
|
||||
)
|
||||
|
||||
@@ -94,9 +95,7 @@ func (c *insightsCollector) sendInsights(ctx context.Context) {
|
||||
log.Trace(ctx, "No users found, skipping Insights data collection")
|
||||
return
|
||||
}
|
||||
hc := &http.Client{
|
||||
Timeout: consts.DefaultHttpClientTimeOut,
|
||||
}
|
||||
hc := httpclient.New(consts.DefaultHttpClientTimeOut)
|
||||
data := c.collect(ctx)
|
||||
if data == nil {
|
||||
return
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/plugins/host"
|
||||
"github.com/navidrome/navidrome/utils/httpclient"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -40,7 +41,7 @@ func newHTTPService(pluginName string, permission *HTTPPermission) *httpServiceI
|
||||
requiredHosts: requiredHosts,
|
||||
}
|
||||
svc.client = &http.Client{
|
||||
Transport: http.DefaultTransport,
|
||||
Transport: httpclient.New(0).Transport,
|
||||
// Timeout is set per-request via context deadline, not here.
|
||||
// CheckRedirect validates hosts and enforces redirect limits.
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/navidrome/navidrome/consts"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/utils/cache"
|
||||
"github.com/navidrome/navidrome/utils/httpclient"
|
||||
"github.com/navidrome/navidrome/utils/random"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -35,7 +36,7 @@ type Handler struct {
|
||||
|
||||
func NewHandler() *Handler {
|
||||
h := &Handler{}
|
||||
h.httpClient = cache.NewHTTPClient(&http.Client{Timeout: 5 * time.Second}, imageListTTL)
|
||||
h.httpClient = cache.NewHTTPClient(httpclient.New(5*time.Second), imageListTTL)
|
||||
h.cache = cache.NewFileCache(imageCacheDir, imageCacheSize, imageCacheDir, imageCacheMaxItems, h.serveImage)
|
||||
go func() {
|
||||
_, _ = h.getImageList(log.NewContext(context.Background()))
|
||||
@@ -78,7 +79,7 @@ func (h *Handler) serveImage(ctx context.Context, item cache.Item) (io.Reader, e
|
||||
if image == "" {
|
||||
return nil, errors.New("empty image name")
|
||||
}
|
||||
c := http.Client{Timeout: imageRequestTimeout}
|
||||
c := httpclient.New(imageRequestTimeout)
|
||||
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, imageURL(image), nil)
|
||||
resp, err := c.Do(req) //nolint:bodyclose,gosec // No need to close resp.Body, it will be closed via the CachedStream wrapper
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
|
||||
29
utils/httpclient/httpclient.go
Normal file
29
utils/httpclient/httpclient.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
)
|
||||
|
||||
// New returns an HTTP client suitable for external service calls.
|
||||
// When EnablePostQuantumTLS is false (the default), it disables post-quantum
|
||||
// key exchange (Kyber/ML-KEM) to avoid "connection reset by peer" errors with
|
||||
// servers that can't handle the larger TLS ClientHello.
|
||||
// See https://github.com/golang/go/issues/70139
|
||||
func New(timeout time.Duration) *http.Client {
|
||||
if conf.Server.EnablePostQuantumTLS {
|
||||
return &http.Client{Timeout: timeout}
|
||||
}
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384},
|
||||
}
|
||||
return &http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: transport,
|
||||
}
|
||||
}
|
||||
17
utils/httpclient/httpclient_suite_test.go
Normal file
17
utils/httpclient/httpclient_suite_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package httpclient_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestHTTPClient(t *testing.T) {
|
||||
tests.Init(t, false)
|
||||
log.SetLevel(log.LevelFatal)
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "HTTPClient Suite")
|
||||
}
|
||||
71
utils/httpclient/httpclient_test.go
Normal file
71
utils/httpclient/httpclient_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package httpclient_test
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/utils/httpclient"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("New", func() {
|
||||
When("EnablePostQuantumTLS is false (default)", func() {
|
||||
BeforeEach(func() {
|
||||
DeferCleanup(configtest.SetupConfig())
|
||||
conf.Server.EnablePostQuantumTLS = false
|
||||
})
|
||||
|
||||
It("returns a client with classic curve preferences", func() {
|
||||
client := httpclient.New(10 * time.Second)
|
||||
Expect(client).ToNot(BeNil())
|
||||
Expect(client.Timeout).To(Equal(10 * time.Second))
|
||||
|
||||
transport, ok := client.Transport.(*http.Transport)
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(transport.TLSClientConfig).ToNot(BeNil())
|
||||
Expect(transport.TLSClientConfig.MinVersion).To(Equal(uint16(tls.VersionTLS12)))
|
||||
Expect(transport.TLSClientConfig.CurvePreferences).To(Equal([]tls.CurveID{
|
||||
tls.X25519, tls.CurveP256, tls.CurveP384,
|
||||
}))
|
||||
})
|
||||
|
||||
It("does not modify http.DefaultTransport", func() {
|
||||
_ = httpclient.New(5 * time.Second)
|
||||
|
||||
defaultTransport := http.DefaultTransport.(*http.Transport)
|
||||
if defaultTransport.TLSClientConfig != nil {
|
||||
Expect(defaultTransport.TLSClientConfig.CurvePreferences).To(BeNil())
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
When("EnablePostQuantumTLS is true", func() {
|
||||
BeforeEach(func() {
|
||||
DeferCleanup(configtest.SetupConfig())
|
||||
conf.Server.EnablePostQuantumTLS = true
|
||||
})
|
||||
|
||||
It("returns a client with default transport (no custom TLS config)", func() {
|
||||
client := httpclient.New(10 * time.Second)
|
||||
Expect(client).ToNot(BeNil())
|
||||
Expect(client.Timeout).To(Equal(10 * time.Second))
|
||||
Expect(client.Transport).To(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
It("respects the timeout parameter", func() {
|
||||
DeferCleanup(configtest.SetupConfig())
|
||||
client := httpclient.New(30 * time.Second)
|
||||
Expect(client.Timeout).To(Equal(30 * time.Second))
|
||||
})
|
||||
|
||||
It("works with zero timeout", func() {
|
||||
DeferCleanup(configtest.SetupConfig())
|
||||
client := httpclient.New(0)
|
||||
Expect(client.Timeout).To(Equal(time.Duration(0)))
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user