{"record":{"id":"bb0434f927cc8b55","repo":"AlistGo/alist","slug":"can-t-create-rangereadcloser-since-url-is-empty-in","errorCode":null,"errorMessage":"can't create RangeReadCloser since URL is empty in link","messagePattern":"can't create RangeReadCloser since URL is empty in link","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/stream/util.go","lineNumber":19,"sourceCode":"package stream\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/alist-org/alist/v3/internal/model\"\n\t\"github.com/alist-org/alist/v3/internal/net\"\n\t\"github.com/alist-org/alist/v3/pkg/http_range\"\n\t\"github.com/alist-org/alist/v3/pkg/utils\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nfunc GetRangeReadCloserFromLink(size int64, link *model.Link) (model.RangeReadCloserIF, error) {\n\tif len(link.URL) == 0 {\n\t\treturn nil, fmt.Errorf(\"can't create RangeReadCloser since URL is empty in link\")\n\t}\n\trangeReaderFunc := func(ctx context.Context, r http_range.Range) (io.ReadCloser, error) {\n\t\tif link.Concurrency != 0 || link.PartSize != 0 {\n\t\t\theader := net.ProcessHeader(nil, link.Header)\n\t\t\tdown := net.NewDownloader(func(d *net.Downloader) {\n\t\t\t\td.Concurrency = link.Concurrency\n\t\t\t\td.PartSize = link.PartSize\n\t\t\t})\n\t\t\treq := &net.HttpRequestParams{\n\t\t\t\tURL:       link.URL,\n\t\t\t\tRange:     r,\n\t\t\t\tSize:      size,\n\t\t\t\tHeaderRef: header,\n\t\t\t}\n\t\t\trc, err := down.Download(ctx, req)\n\t\t\treturn rc, err\n\n\t\t}","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/internal/stream/util.go#L1-L37","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use a driver/mode that returns a direct URL link (e.g. enable direct link / disable force proxy for that storage)","Before calling, check len(link.URL) > 0 and fall back to a full-download/seekable-cache strategy","Fix the driver's Link() implementation to populate URL when the backend supports ranged GETs"],"exampleFix":"// before\nrrc, err := stream.GetRangeReadCloserFromLink(size, link) // error: URL is empty\n\n// after\nif len(link.URL) == 0 {\n    // fall back: buffer whole file to a temp file for random access\n    f, err := stream.CacheFullInTempFile(link.Data)\n    ...\n}","handlingStrategy":"type-guard","validationCode":"if len(link.URL) == 0 {\n    // take the full-download path instead of ranged reads\n    return downloadWhole(link.Data)\n}","typeGuard":"func hasURLLink(link *model.Link) bool {\n    return link != nil && len(link.URL) > 0\n}","tryCatchPattern":null,"preventionTips":["Check link.URL before requesting range-based features (seek/preview)","Prefer drivers/modes that expose direct URLs when range access is required","Cache a seekable copy for Data-only links before random access"],"tags":["go","stream","http","range-request","validation"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}