Created
May 30, 2013 06:39
-
-
Save maddemon/5676087 to your computer and use it in GitHub Desktop.
NodeJs实现可续传的上传协议(tus resumable upload protocol)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Tus协议地址http://www.tus.io/protocols/resumable-upload.html,大家可以看看英文原文介绍,我这里也简单介绍下。 | |
这是一个基于http的可续传的协议,主要是不同的HttpMethod和相关的自定义header来实现续传功能。 | |
具体的Method和header介绍: | |
1、HEAD | |
head请求是获取文件已上传的状态,服务端返回该文件哪些范围已写入,哪些没写入。 | |
(PS,我刚发现协议已经变了,以前不是offset) | |
但是我这个实现和tus也略有不同,我支持一个文件可以多线程同时上传,所以不能完全追寻他的协议。 | |
客户端接收到的header例子: | |
range:bytes=0-9,20-29 | |
意思表名0-9和20-29这俩个块已被写入,所以续传的时候不需要上传了。 | |
如果接收到的是 range:bytes=0-1023(假如文件就1024长度,那么说明文件已上传完毕) | |
2、POST | |
post请求是用于创建文件,header里必须要有声明文件的大小,例如 | |
content-range:*/1023 这说明文件的大小是1024。 | |
返回结果:告诉客户端这个文件的地址,好让客户端知道往什么地址PUT数据。例如: | |
Location: http://tus.example.org/files/24e533e02ec3bc40c387f1a0e460e216 | |
3、PUT | |
put请求是上传文件用的,如果想实现续传,那么就要把文件拆分为N个小块,按块上传,这样如果断掉,下次可以通过HEAD请求看到哪些块已经上传。 | |
4、GET | |
get请求就是下载文件,这里不必多说了。 | |
协议大致如此,只是相对于tus原本的协议有所变化(可以多块同时上传,所以并非是顺序上传,在Head请求的返回自然有所变化)。 | |
协议实现思路: | |
客户端分块上传,服务端接收到每个块之后存储到服务端。如何记住哪些块被上传了呢? | |
我的实现思路是需要一个metadata的辅助元数据文件来继续这个文件的上传状态、文件大小、文件原名称等等。 | |
现在是用文件存储的metadata,这只是一个demo,如果是真正使用,我想用数据库更合适一些。 | |
我走的弯路: | |
第一次: | |
如何实现块能够写入到目标文件里?这个问题也和以前的同事交流过,当时没有找到解决办法。除非顺序写入然后用append的模式。 | |
但是客户端上传确实多线程非顺序上传,服务端该如何保证顺序? | |
最后的解决办法是把客户端上传的每一个块保存为单个小文件,每次上传完一个块之后判断是不是所有的块上传完毕,如果上传完毕做一次合并。 | |
判断文件块是否写完,我就要遍历每一个小文件,判断他们是否连续。由于nodejs的文件操作需要异步,所以实现起来需要写各种递归,异常痛苦,大家可以看我的GitHub提交的历史版本,最老的版本里面有很多递归。代码非常丑陋,而且难以维护。 | |
第二次: | |
为了减少递归,我使用global全局缓存,而且限定了文件上传的块的大小必须一样,没上传完一个块,我就把他存储到global缓存里。这样就不需要遍历小文件,减少了很多异步操作,代码清晰了很多。 | |
第三次: | |
小文件合并的方案虽然可行,但毕竟感觉有些愚蠢,如果是特别大的文件,那可以想象合并和遍历都很慢,管理也不方便。所以如果能解决Nodejs的按offset写入stream的问题就可以解决。我又翻了下Nodejs的文档,发现以前使用的都是w和w+的打开方式,这种打开方式会清空文件的内容,而r和r+不会。 | |
于是我尝试着修改了代码,由于是并发写入,实际上文件写入是不能并发的,所以需要加锁,当块写入的时候判断是否锁住就可以了。 | |
这一下去掉了好多的代码,代码更加的明亮起来。 | |
为什么用NodeJS? 因为这是一个面试题的作业,所以必须用Nodejs。 | |
我在FileMetadata初始化是依然用的fs.readSync,我以前同事告诉我nodejs不可以使用sync方法,这样会hold住整个进程。这是现实太恐怖了。 | |
所以我个人觉得nodejs的弊端也很明显,对代码复杂度的增加显而易见。 至于Javascript的非阻塞的先天优势,其他的语言也可以做到。那为什么还用Nodejs呢?而且Javascript的语法比较弱,很多实现都比较困难。我对nodejs没有太多好感。 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment