Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Download resource which response dynamic but valid instance-length for each-block #31

Closed
kerneltea opened this issue Apr 16, 2018 · 10 comments

Comments

@kerneltea
Copy link

kerneltea commented Apr 16, 2018

我之前浏览了历史issues,发现也有相关的问题:#17 (comment) 我不知道跟我遇到的问题是否相似?我尝试使用最新的1.0.1的SNAPSHOT还是会遇到这个问题,

taskEnd cause:ERROR realCause:The current offset on block-info isn't update correct, 4447894 != 4535848 on 2

这是我创建task的代码(kotlin)的
image

我使用的是下面这些下载链接,而且还有很多。
https://play.podtrac.com/npr-510318/npr.mc.tritondigital.com/NPR_510318/media/anon.npr-mp3/npr/upfirst/2018/04/20180406_upfirst_upfirstfacebookspecial.mp3?orgId=1&d=893&p=510318&story=600081084&t=podcast&e=600081084&ft=pod&f=510318

https://play.podtrac.com/npr-510318/npr.mc.tritondigital.com/NPR_510318/media/anon.npr-mp3/npr/upfirst/2018/03/20180329_upfirst_180329upfirst.mp3?orgId=1&d=803&p=510318&story=597870311&t=podcast&e=597870311&ft=pod&f=510318

我们做的是一款播客类的APP,会遇到各国的播客源,而且各个播客类的APP都是相同的源,所以如果别的APP是可以下载的,不应该我们这边的不能下,其实到也不是不能下,主要就是会很容易出现下面的错误

taskEnd cause:ERROR realCause:The current offset on block-info isn't update correct, 4944990 != 4949823 on 2

而且我发现,我之前使用FileDownloader-1.4.X的版本是不会遇到这类问题的,直到升到最新的FileDownloader后发现该问题出现非常多lingochamp/FileDownloader#974 ,而且在FileDownloader的ISSUES列表里也有很多用户遇到这个问题,我也照着FileDownloader的方法进行设置了:

使用单连接下载方案一

  1. 在filedownloader.properties中配置不使用独立进程: 实现FileDownloadHelper.ConnectionCountAdapter,并在里面配置需要单连接的连接缓存,当下载出现类似错误的时候,将连接加入缓存,然后重新下载。
  2. 通过FileDownloder.setupOnApplicationOnCreate注册该ConnectionCountAdapter

使用单连接下载方案二

  1. 实现FileDownloadHelper.ConnectionCountAdapter全局都该用单线程下载。

但针对上面的进行设置后,FileDownloader-1.5.+版本还是会有问题!!!

后来我看到lingochamp/FileDownloader#990 (comment) 你说在OkDownload中解决了该问题.

然后尝试使用OkDownload,但是问题好像还是存在。

我截了个LOG:

image

刚才用Charles截了一段HTTP的数据,不知道可以不可以给你一些线索

image

是一个302的地址转移,但是我用的是OKHTTP,应该当是可以自己处理302的问题的

之后我又在FileDownloader-1.4.3的版本中我又尝试下了同一个链接,正常的,试了10来次都是正常的,但是1.4.3的版本好像对okcat支持的不是很好,所以没有捉到什么有用的LOG:

之前我已经了解过你写的 TCP 窗口的文章

@Jacksgong

@Jacksgong
Copy link
Collaborator

Jacksgong commented Apr 27, 2018

我认真看了,和302无关,302无论是okhttp还是okdownload本身都有处理,那么问题出在哪里呢。

首先,你的这个问题与#17 也不一样,#17 中是因为我们给的Rangex-,这么一来由于没有明确的区间右值,所以后端返回的数据不符合预期的情况。

你的情况是,我们给了明确的Range了,但是后端返回的数据大小不符合协议,具体我下面说明下:

以下是测试链接: https://play.podtrac.com/npr-510318/npr.mc.tritondigital.com/NPR_510318/media/anon.npr-mp3/npr/upfirst/2018/04/20180406_upfirst_upfirstfacebookspecial.mp3?orgId=1&d=893&p=510318&story=600081084&t=podcast&e=600081084&ft=pod&f=510318

P.S. 测试下来,以下的情况对于该资源来说,该问题只第一次验证的时候,出现而后便没有再出现。第二个资源地址也是类似。

试探链接返回的数据总大小: 14545615

image

根据试探链接,我们去分块,最后一个分块是9697077-14545614,但是返回的与请求的却不一致:

image

导致最终,结束时,发现需要下载的大小,与后端给过来的大小不一致

image

并且我们还观察到你这个比较独特的,只有最后一个分块的区间大小发生了变化(分块0,分块1都是符合预期的,只有分块2不符合协议),前面的包括试探链接在内,显示的总大小都是14545615,但是最后一个分块居然突然变成14260149

image


介于这种是非常明显的服务端错误,我这边其实验证下来这两个资源也是在很少的情况下会出现,出现的概率极低。(第一个链接资源就第一次验证的时候确实是出现,后续就没有出现过;第二个资源链接没有出现过类似的服务端错误问题。),如果你实在想要忽略这种服务端错误,将只要最后流拉取完成就当作正确完成下载,你需要解决以下两大问题:

这里有两个大的矛盾点:

  1. 在试探链接结束后,默认如果支持的话,我们会对文件进行预分配空间,比如将文件大小设置为14545615
  2. 最终流结束时,我们会检测获取到的数据与请求的数据长度是否一致

第一个矛盾点,可以通过在1.0.2-SNAPSHOT中的DownloadTask.Builder#setPreAllocateLength(false)来关闭。
第二个矛盾点,这边确实是可以实现一个接口让下载结束后不再做这块的检测,如果是确实如此,可否再提供几个链接以便于验证这类链接的规律(由于你提供的两个链接我在1.0.2-SNAPSHOT中只出现了一次就没有再出现类似问题了)

@kerneltea
Copy link
Author

kerneltea commented Apr 28, 2018

下面是一些链接,这是现在全球比较大的播客类RSS源。下载的过程中会遇到这类问题。

还有一个是。我下载的时候连着VPN。不知道VPN会不会影响 这块的数据?

https://play.podtrac.com/npr-510318/npr.mc.tritondigital.com/NPR_510318/media/anon.npr-mp3/npr/upfirst/2018/04/20180427_upfirst_42718upfirst2.mp3?orgId=1&d=853&p=510318&story=606301138&t=podcast&e=606301138&ft=pod&f=510318"

https://play.podtrac.com/npr-500005/npr.mc.tritondigital.com/NPR_500005/media/anon.npr-mp3/npr/newscasts/2018/04/28/newscast030647.mp3?orgId=1&d=300&p=500005&story=606699149&t=podcast&e=606699149&ft=pod&f=500005

https://play.podtrac.com/npr-510318/npr.mc.tritondigital.com/NPR_510318/media/anon.npr-mp3/npr/upfirst/2018/04/20180426_upfirst_42618uf_speacial.mp3?orgId=1&d=653&p=510318&story=606004121&t=podcast&e=606004121&ft=pod&f=510318

https://play.podtrac.com/npr-500005/npr.mc.tritondigital.com/NPR_500005/media/anon.npr-mp3/npr/newscasts/2018/04/28/newscast020651.mp3?orgId=1&d=300&p=500005&story=606696138&t=podcast&e=606696138&ft=pod&f=500005

https://play.podtrac.com/npr-500005/npr.mc.tritondigital.com/NPR_500005/media/anon.npr-mp3/npr/newscasts/2018/04/28/newscast010645.mp3?orgId=1&d=300&p=500005&story=606691021&t=podcast&e=606691021&ft=pod&f=500005"

https://play.podtrac.com/npr-510318/npr.mc.tritondigital.com/NPR_510318/media/anon.npr-mp3/npr/upfirst/2018/04/20180426_upfirst_42618upfirst.mp3?orgId=1&d=730&p=510318&story=605949695&t=podcast&e=605949695&ft=pod&f=510318

Jacksgong added a commit that referenced this issue Apr 28, 2018
@Jacksgong
Copy link
Collaborator

Jacksgong commented Apr 28, 2018

这个与VPN无关,我发现这些资源都有一个特点,就是每个链接返回的content-range所表示的总大小都有所不同,这是非常不make sense的,如下面是针对该链接(https://play.podtrac.com/npr-510318/npr.mc.tritondigital.com/NPR_510318/media/anon.npr-mp3/npr/upfirst/2018/04/20180427_upfirst_42718upfirst2.mp3?orgId=1&d=853&p=510318&story=606301138&t=podcast&e=606301138&ft=pod&f=510318 )的测试所有可能的返回的总大小:

如下,对该资源做1次试探链接,与3个分块链接返回的该资源的总大小都不同(分别是: 13898193, 13887562, 13899265, 13907206):

image


这个其实看来应该是chunked动态大小的流式资源,但是返回的请求头中并没有告知是流式资源。因此这类资源按照服务端的设计来看,应该本身就不支持分块下载。
但是实际上与流式资源又略有不同;流式资源在响应头中我们无法获知当前链接可供获取的数据的总大小,而该响应头又支持。。


这种资源的特征就是

如果你简单的发一个请求来下载,应该都是正常的。
但是如果分块下载,需要多个链接协同下载就很容易出现各种问题,但是我分析其响应头来看内部并无法获知是否是该情况。


为了保证okdownload更加可靠我会采用这个策略修复这个问题: 在单链接情况,如果试探链接与实际单块链接不一致,将会采用单块链接为主以继续正常下载。

在上面这个修复策略的基础上,以上资源都可以通过DownloadTask.Builder#setConnectionCount(1)来正常下载; 但是由于其长度不断的在变化,因此如果下一次启动任务的时候发现总大小不同,断点续传便会被弃用,因此很容易无法断点续传,这个是无法避免的。

@kerneltea
Copy link
Author

Ok,非常感谢你抽空分析这类问题,无法断点续传没关系,只要可以正常下载就可以 。

@Jacksgong Jacksgong added this to the 1.0.3 milestone Apr 28, 2018
@Jacksgong
Copy link
Collaborator

该问题已经在1.0.3-SNAPSHOT中修复,你可以参考wiki引入SNAPSHOT版本进行使用。使用时正如上面提到的,需要通过DownloadTask.Builder#setConnectionCount(1)来保证该任务是单连接任务以确保这类资源可以正常下载。

@kerneltea
Copy link
Author

OK!非常感谢!我一会试下

@Jacksgong Jacksgong changed the title The current offset on block-info的错误在1.0.1中好像还存在? Download resource which response dynamic but valid instance-length for each-block May 7, 2018
@yatounini
Copy link

有些服务器首次未返回Content-Range为空,也会导致的下载失败。如果Content-Range等于空,可以把instanceLength赋值为Content-Length,来打补丁。T_T T_T T_T T_T

@Jacksgong
Copy link
Collaborator

Jacksgong commented May 11, 2018

@yatounini 这样直接赋值Content-Length是有严重的问题的,这将是严重违背HTTP协议 RFC 7233第五页所描述的,如果为了国内一些无节操后端,不符合协议的返回而采用违背HTTP协议的投机取巧的做法,将会导致所有的原本正确的instanceLength被设置为错误的问题。具体还请查看日志,如果觉得存在问题请单独提Issue。

具体原因我在很多地方都有做过描述,比如这里就是一个案例。

大概原因

首次试探连接采用range:0-0根据协议此时的Content-Length并不是instanceLength,而是大小为1,此时我们不得不通过Content-Range去取得instanceLength,而如果此时的Content-Length大小不为1,由于没有任何HTTP协议协定该大小就是总大小,因此我们无法进行使用。

其次如果是range:0-0的试探请求首次返回不存在Content-Range,但是存在Content-Length,okdownload还会做一次HEAD请求去获取Content-Length,但是如果后端连HEAD请求都不支持,那么此时okdownload才会放弃,此时也并不会下载失败,只是当做chunked资源下载,具体请参考日志,有问题请提交issue或PR。

如果是希望直接通过一次GET请求去获得有效的Content-Length来当做instanceLength,那么此时你将会面临一个更加严重的问题,那就是此时在TCP协议层已经打开了一个接收窗口,根据文件大小通常会缓存2M甚至更多的数据,而这个数据的下载我们完全是没有用的,因为后续我们单独进行请求进行分块下载了。具体TCP接收窗口的缓存,请参看这篇文章


推荐解决方案

  1. 当然这种策略问题,在架构设计本身,我觉得可以是open的,如果你觉得无所谓,按照你的经验,这种不符合协议的直接使用content-length也是允许的,需要okdownload提供接口进行快速定制,请单独提issue并表明原因,或者直接提PR。
  2. 如果你觉得在通过range:0-0GET请求,以及退而求其次的HEAD请求都无法获得instanceLength的情况下,你觉得此时我们再通过一个单独的GET去获得总大小,即便是可能导致额外的数据下载也是没问题的,那么我觉得这种方案也是可以探讨的,具体请单独提issue并表明原因,或直接提PR。

@yuqilin
Copy link

yuqilin commented Aug 24, 2018

N多人提出content-length的问题,作者坚持遵循协议,但下载本身就是一件苦脏累的填坑运动,感谢作者及团队的辛劳;考虑实际情况,range:0-0很多后台下载服务可能都不被考虑的,反倒HEAD更常见,尤其比较早些的网站,国内外皆如此。

@HachikoDream
Copy link

1.0.5 还是有这个问题啊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants