A simple RESTful web service in nodejs (1)

这段时间突然对nodejs有点兴趣,虽然之前完全没有接触过,不过本着实践出真知的精神,四处查阅资料,做出了一个小轮子——videomerger,接收数个视频文件的url,在服务器端下载后合并(以及可能的转码),然后把合并后文件的链接返回给用户下载。配合分析各种视频网站真实下载地址的脚本,可以实现用户输入视频播放地址后,得到合并后的单文件视频下载(类似clipconverter.cc)。

做的东西时间长了容易忘掉,于是写个文章帮助记忆,也分享一下思路。
代码已开源:
https://github.com/youran/videomerger
代码写得很随意,基本功能实现了就好,等我有兴致了再整整。轮子再破也是轮子嘛,哈哈。
如果有什么问题、建议或者发现bug,可以在Github上发issue讨论。

首先是服务器环境的搭建:
安装nodejs。这里选择从官网直接下载二进制包安装:

安装MongoDB:

修改package.json,加入这次需要的module:

磨刀不误砍柴工,首先要确定整个程序的架构,否则各种返工(实际上开发过程中为了验证各种方法的可行性,确实也推翻重来了好几次orz)。
本项目使用下图所示的执行流程,由主进程(server.js)负责接受HTTP请求,根据POST请求中的url数据,fork合适数目的下载进程(wget.js)进行下载;当全部下载完成后,主进程fork一个负责转码及合并的子进程(ffmpeg.js),生成最终的单个视频文件。
download

运行流程定下来以后,定义数据库结构。这里给每个task(包含了下载及合并)定义了下述属性:

假设上述代码存放在models/videotask.js里。下面开搞主进程的代码。

把数据库连上,并且定义一个model实例,数据库查询就靠它了:

顺便写一段代码,让程序被kill或者Ctrl-C终止时,mongoose不要打出一堆错误信息,改为一段friendly message:

下面是服务器的HTTP请求处理部分。
先定义一个全局变量,后面用得上,便于任务状态管理:

以及使用HTTP request解析器:

再定义一个Express的Router,然后给它写一个处理GET请求的方法,用于取得MongoDB数据库里的所有条目,便于后面的调试。也就是说直接以HTTP GET方式访问http://xxx.xxx.xxx.xxx/tasks时,下面.get()里的逻辑得到执行:

可以用postman这个chrome app来发送HTTP请求做测试,例如:
restful-get-all

接着稍微复杂点,给router加一个处理POST请求的方法,根据客户端发来的url数据,分配下载进程。也就是说当以HTTP POST方式(body中带url信息)访问http://xxx.xxx.xxx.xxx/tasks时,下面.post()里的逻辑得到执行:

这段代码首先创建了一个videoTask的Document,填入初始化参数。其中,id作为每个task的识别子,我用javascript的Date.now()生成,基本保证了唯一性。
将新建的document写入数据库后,为当前task创建一个taskObj对象,push入之前定义的taskArray中。
接着,从POST数据中取得url信息,为每一个url分配一个wget子进程。这里使用了nodejs的fork方法来创建子进程,好处是由此建立了一条进程间信息通道,方便后面使用.on()方法获取子进程返回的信息。
新建一个taskObj写入当前wget子进程的状态信息,连同fork的返回值一起push到taskObj.wget数组中。万事OK后,就可以向客户端返回”成功接收请求”的json信息了。

接下来需要定义另一条route。前的GET方法返回了数据库中所有task的信息,而很多时候只需要取得某个特定id的task信息。因此有了下面这段:

这段的作用,是当以HTTP GET方式访问http://xxx.xxx.xxx.xxx/tasks/111111时(111111是taskID),服务器返回这个task的状态信息。
代码里先根据url中taskID信息,在数据库查找对应的task。根据数据库返回的document内容,解析task状态返回给客户端。结果示例:
restful-get-one

有了取单个task的方法,还可以定义一个删除单个task的方法:

现在HTTP request处理部分已经告一段落,但是有个问题:当所有wget进程完成下载后,怎样通知主进程进行下一步的转码合并操作?
最初定义的那个taskArray就是为此准备的。主进程中定义了一个interval函数,每隔几秒种就结合数据库检查一下当前taskArray中管理的task,如果发现某个task的wget部分已成功完成所有下载,就fork一个ffmpeg子进程,继续后面的转码合并操作。

其实还有一种更elegant的方法。之前fork那些wget子进程的时候不是监听了它们的message吗?可以在taskObj里再加些参数,用来记录当前已完成的下载数和总下载数,如果发现全部下载完成了,就fork一个ffmpeg子进程。这样就可以消除interval方式造成的数秒延迟,代码也更加易读。

到这里,主进程的内容已经基本完成了,指定个端口号,以及当前app所在的url目录,然后listen一下就大功告成:

请移步下一篇文章:wget子进程的实现。

References:
[1] https://scotch.io/tutorials/build-a-restful-api-using-node-and-express-4
[2] http://www.tutorialspoint.com/nodejs/nodejs_restful_api.htm
[3] https://nodejs.org/api/child_process.html
[4] http://javascript.ruanyifeng.com/nodejs/child-process.html
[5] https://docs.nodejitsu.com/articles/command-line/how-to-parse-command-line-arguments
[6] http://mongoosejs.com/docs/schematypes.html
[7] http://mongoosejs.com/docs/2.7.x/docs/finding-documents.html
[8] https://gist.github.com/pasupulaphani/9463004

本文是悠然居(wordpress.youran.me)原创文章,如转载必须保留此告示。

本文为悠然居(https://wordpress.youran.me/)的原创文章,转载请注明出处!

声明: 本文采用 BY-NC-SA 协议进行授权. 转载请注明转自: A simple RESTful web service in nodejs (1)
  1. No comments yet.