@@ -517,7 +517,15 @@ func (p *Proxy) cacheMetadataBlob(ctx context.Context, ecosystem, cacheKey, stor
517517
518518// ProxyCached fetches metadata from upstream (with optional caching for offline fallback)
519519// and writes it to the response. Optional acceptHeaders specify the Accept header to send.
520+ // When metadata caching is disabled, the response is streamed directly to avoid buffering
521+ // large metadata responses (e.g. npm packages with many versions) in memory.
520522func (p * Proxy ) ProxyCached (w http.ResponseWriter , r * http.Request , upstreamURL , ecosystem , cacheKey string , acceptHeaders ... string ) {
523+ if ! p .CacheMetadata {
524+ // Stream directly without buffering when caching is off.
525+ p .proxyMetadataStream (w , r , upstreamURL , acceptHeaders ... )
526+ return
527+ }
528+
521529 body , contentType , err := p .FetchOrCacheMetadata (r .Context (), ecosystem , cacheKey , upstreamURL , acceptHeaders ... )
522530 if err != nil {
523531 if errors .Is (err , ErrUpstreamNotFound ) {
@@ -534,6 +542,44 @@ func (p *Proxy) ProxyCached(w http.ResponseWriter, r *http.Request, upstreamURL,
534542 _ , _ = w .Write (body )
535543}
536544
545+ // proxyMetadataStream forwards an upstream metadata response by streaming it to the client
546+ // without buffering the full body in memory.
547+ func (p * Proxy ) proxyMetadataStream (w http.ResponseWriter , r * http.Request , upstreamURL string , acceptHeaders ... string ) {
548+ req , err := http .NewRequestWithContext (r .Context (), http .MethodGet , upstreamURL , nil )
549+ if err != nil {
550+ http .Error (w , "failed to create request" , http .StatusInternalServerError )
551+ return
552+ }
553+
554+ accept := contentTypeJSON
555+ if len (acceptHeaders ) > 0 && acceptHeaders [0 ] != "" {
556+ accept = acceptHeaders [0 ]
557+ }
558+ req .Header .Set ("Accept" , accept )
559+
560+ for _ , header := range []string {"Accept-Encoding" , "If-Modified-Since" , "If-None-Match" } {
561+ if v := r .Header .Get (header ); v != "" {
562+ req .Header .Set (header , v )
563+ }
564+ }
565+
566+ resp , err := p .HTTPClient .Do (req )
567+ if err != nil {
568+ http .Error (w , "failed to fetch from upstream" , http .StatusBadGateway )
569+ return
570+ }
571+ defer func () { _ = resp .Body .Close () }()
572+
573+ for _ , header := range []string {"Content-Type" , "Content-Length" , "Last-Modified" , "ETag" } {
574+ if v := resp .Header .Get (header ); v != "" {
575+ w .Header ().Set (header , v )
576+ }
577+ }
578+
579+ w .WriteHeader (resp .StatusCode )
580+ _ , _ = io .Copy (w , resp .Body )
581+ }
582+
537583// GetOrFetchArtifactFromURL retrieves an artifact from cache or fetches from a specific URL.
538584// This is useful for registries where download URLs are determined from metadata.
539585func (p * Proxy ) GetOrFetchArtifactFromURL (ctx context.Context , ecosystem , name , version , filename , downloadURL string ) (* CacheResult , error ) {
0 commit comments