AlistGo/alist · error

can't create RangeReadCloser since URL is empty in link

Error message

can't create RangeReadCloser since URL is empty in link

What it means

GetRangeReadCloserFromLink requires a URL-based link to construct a ranged HTTP reader. The provided model.Link has an empty URL (typically because the driver returned a Link carrying only a Data io.ReadCloser, e.g. for local or proxy-style drivers), so no RangeReadCloser can be built.

Source

Thrown at internal/stream/util.go:19

package stream

import (
	"context"
	"encoding/hex"
	"fmt"
	"io"
	"net/http"

	"github.com/alist-org/alist/v3/internal/model"
	"github.com/alist-org/alist/v3/internal/net"
	"github.com/alist-org/alist/v3/pkg/http_range"
	"github.com/alist-org/alist/v3/pkg/utils"
	log "github.com/sirupsen/logrus"
)

func GetRangeReadCloserFromLink(size int64, link *model.Link) (model.RangeReadCloserIF, error) {
	if len(link.URL) == 0 {
		return nil, fmt.Errorf("can't create RangeReadCloser since URL is empty in link")
	}
	rangeReaderFunc := func(ctx context.Context, r http_range.Range) (io.ReadCloser, error) {
		if link.Concurrency != 0 || link.PartSize != 0 {
			header := net.ProcessHeader(nil, link.Header)
			down := net.NewDownloader(func(d *net.Downloader) {
				d.Concurrency = link.Concurrency
				d.PartSize = link.PartSize
			})
			req := &net.HttpRequestParams{
				URL:       link.URL,
				Range:     r,
				Size:      size,
				HeaderRef: header,
			}
			rc, err := down.Download(ctx, req)
			return rc, err

		}

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Use a driver/mode that returns a direct URL link (e.g. enable direct link / disable force proxy for that storage)
  2. Before calling, check len(link.URL) > 0 and fall back to a full-download/seekable-cache strategy
  3. Fix the driver's Link() implementation to populate URL when the backend supports ranged GETs

Example fix

// before
rrc, err := stream.GetRangeReadCloserFromLink(size, link) // error: URL is empty

// after
if len(link.URL) == 0 {
    // fall back: buffer whole file to a temp file for random access
    f, err := stream.CacheFullInTempFile(link.Data)
    ...
}
Defensive patterns

Strategy: type-guard

Validate before calling

if len(link.URL) == 0 {
    // take the full-download path instead of ranged reads
    return downloadWhole(link.Data)
}

Type guard

func hasURLLink(link *model.Link) bool {
    return link != nil && len(link.URL) > 0
}

Prevention

When it happens

Trigger: Calling a code path that needs random access (preview, video seek, archive listing) on a file whose driver Link exposes Data instead of URL, or the URL field was never populated before the call.

Common situations: Using a driver that only supports full-stream proxying with a feature that requires range requests; upstream changes where a driver stopped returning direct URLs.

Related errors


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/bb0434f927cc8b55. Report an issue: GitHub.