feat: Charts 与 LoadFeed 支持 context 透传取消
- 新增 ChartsContext / LoadFeedContext(原方法转发 context.Background(),兼容不变) - Charts 榜单请求与逐项 lookup 全部改 NewRequestWithContext,循环顶检查 ctx.Err() 提前中止剩余 lookup - 修复 resp 在 err 检查前解引用(网络错误/ctx 取消时必 panic) - 修复 lookup 循环 User-Agent 误设在外层请求上 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -63,13 +63,19 @@ type Podcast struct {
|
||||
Medium Medium `json:"medium,omitempty"`
|
||||
}
|
||||
|
||||
// LoadFeed 从URL加载播客RSS订阅,timeout为超时时间,如果timeout<=0则使用默认值30秒
|
||||
// LoadFeed 从URL加载播客RSS订阅,timeout为超时时间,如果timeout<=0则使用默认值30秒。
|
||||
// 需要透传取消时请用 LoadFeedContext。
|
||||
func LoadFeed(feedURL, proxy string, timeout time.Duration) (*Podcast, error) {
|
||||
return LoadFeedContext(context.Background(), feedURL, proxy, timeout)
|
||||
}
|
||||
|
||||
// LoadFeedContext 同 LoadFeed,但请求生命周期挂在调用方 ctx 上:ctx 取消/超时即中断下载。
|
||||
func LoadFeedContext(ctx context.Context, feedURL, proxy string, timeout time.Duration) (*Podcast, error) {
|
||||
if timeout <= 0 {
|
||||
timeout = 30 * time.Second
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
fp := gofeed.NewParser()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -216,17 +217,23 @@ func (p *ITunesProvider) SearchPodcasts(term string, options *SearchOptions) (*m
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Charts 获取热门播客列表
|
||||
// Charts 获取热门播客列表(无取消语义,等价于 ChartsContext(context.Background(), options))。
|
||||
func (p *ITunesProvider) Charts(options *ChartsOptions) (*model.SearchResult2, error) {
|
||||
return p.ChartsContext(context.Background(), options)
|
||||
}
|
||||
|
||||
// ChartsContext 获取热门播客列表,榜单抓取与逐项 lookup 的全部请求都挂在调用方 ctx 上:
|
||||
// ctx 取消即中断当前请求并停止剩余 lookup。
|
||||
// 可选参数包括国家、语言、结果数量限制、是否包含限制内容和流派
|
||||
// 由于热门播客列表以feed形式返回,为了与SearchResult兼容,需要解析feed并获取每个项目的底层结果
|
||||
// 这会导致每个结果都有一个HTTP调用,鉴于热门播客列表更新不频繁,建议客户端缓存结果
|
||||
func (p *ITunesProvider) Charts(options *ChartsOptions) (*model.SearchResult2, error) {
|
||||
func (p *ITunesProvider) ChartsContext(ctx context.Context, options *ChartsOptions) (*model.SearchResult2, error) {
|
||||
// 构建URL
|
||||
url := p.buildChartsURL(options)
|
||||
p.Logger.Infof("url: %s", url)
|
||||
|
||||
// 发送请求
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建请求失败: %w", err)
|
||||
}
|
||||
@@ -237,12 +244,11 @@ func (p *ITunesProvider) Charts(options *ChartsOptions) (*model.SearchResult2, e
|
||||
// 发送请求
|
||||
p.Limiter.Take()
|
||||
resp, err := p.Client.Do(req)
|
||||
p.Logger.Infof("response code: %d", resp.StatusCode)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("发送请求失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
p.Logger.Infof("response code: %d", resp.StatusCode)
|
||||
|
||||
// 检查响应状态
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
@@ -270,6 +276,11 @@ func (p *ITunesProvider) Charts(options *ChartsOptions) (*model.SearchResult2, e
|
||||
if chartResult.Feed.Entry != nil {
|
||||
cnt := 1
|
||||
for _, entry := range chartResult.Feed.Entry {
|
||||
// 调用方取消:停止剩余 lookup,立即返回。
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
// 获取播客ID
|
||||
id := entry.ID.Attributes.ID
|
||||
|
||||
@@ -281,18 +292,18 @@ func (p *ITunesProvider) Charts(options *ChartsOptions) (*model.SearchResult2, e
|
||||
lookupURL := fmt.Sprintf("%s/lookup?id=%s", p.FeedAPIEndpoint, id)
|
||||
p.Logger.Infof("%03d lookup url: %s", cnt, lookupURL)
|
||||
cnt++
|
||||
lookupReq, err := http.NewRequest("GET", lookupURL, nil)
|
||||
lookupReq, err := http.NewRequestWithContext(ctx, "GET", lookupURL, nil)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", "podcast-search-go/0.1")
|
||||
lookupReq.Header.Set("User-Agent", "podcast-search-go/0.1")
|
||||
p.Limiter.Take()
|
||||
lookupResp, err := p.Client.Do(lookupReq)
|
||||
p.Logger.Infof("lookup response code: %d", lookupResp.StatusCode)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Logger.Infof("lookup response code: %d", lookupResp.StatusCode)
|
||||
|
||||
if lookupResp.StatusCode != http.StatusOK {
|
||||
lookupResp.Body.Close()
|
||||
|
||||
Reference in New Issue
Block a user