我们将像下面的例子那样,从一个基本的HTTP服务器开始。这已经可以基本足够处理大多数的浏览器请求了。首先,我们初始化我们需要用到的对象,并且用initFolder来代表文件的位置。为了生成Content-Type头部,我们列出文件扩展名和它们相对应的MIME名称来构成一个字典。在回调函数httpListener()中,我们将仅允许GET可用。如果出现其他方法,服务器将返回405 Method Not Allowed,在文件不存在于initFolder,服务器将返回404 Not Found。
// 初始化需要的对象 var http = require("http"); var fs = require("fs"); var path = require("path"); var url = require("url"); // 初始的目录,随时可以改成你希望的目录 var initFolder = "C:\\Users\\User\\Videos"; // 将我们需要的文件扩展名和MIME名称列出一个字典 var mimeNames = { ".css": "text/css", ".html": "text/html", ".js": "application/javascript", ".mp3": "audio/mpeg", ".mp4": "video/mp4", ".ogg": "application/ogg", ".ogv": "video/ogg", ".oga": "audio/ogg", ".txt": "text/plain", ".wav": "audio/x-wav", ".webm": "video/webm"; }; http.createServer(httpListener).listen(8000); function httpListener (request, response) { // 我们将只接受GET请求,否则返回405 'Method Not Allowed' if (request.method != "GET") { sendResponse(response, 405, {"Allow" : "GET"}, null); return null; } var filename = initFolder + url.parse(request.url, true, true).pathname.split('https://www.jb51.net/').join(path.sep); var responseHeaders = {}; var stat = fs.statSync(filename); // 检查文件是否存在,不存在就返回404 Not Found if (!fs.existsSync(filename)) { sendResponse(response, 404, null, null); return null; } responseHeaders["Content-Type"] = getMimeNameFromExt(path.extname(filename)); responseHeaders["Content-Length"] = stat.size; // 文件大小 sendResponse(response, 200, responseHeaders, fs.createReadStream(filename)); } function sendResponse(response, responseStatus, responseHeaders, readable) { response.writeHead(responseStatus, responseHeaders); if (readable == null) response.end(); else readable.on("open", function () { readable.pipe(response); }); return null; } function getMimeNameFromExt(ext) { var result = mimeNames[ext.toLowerCase()]; // 最好给一个默认值 if (result == null) result = "application/octet-stream"; return result; }
步骤 2 - 使用正则表达式捕获Range消息头
有了这个HTTP服务器做基础,我们现在就可以用如下代码处理Range消息头了. 我们使用正则表达式将消息头分割,以获取开始和结束字符串。然后使用 parseInt() 方法将它们转换成整形数. 如果返回值是 NaN (非数字not a number), 那么这个字符串就是没有在这个消息头中的. 参数totalLength展示了当前文件的总字节数. 我们将使用它计算开始和结束位置.
function readRangeHeader(range, totalLength) { /* * Example of the method 'split' with regular expression. * * Input: bytes=100-200 * Output: [null, 100, 200, null] * * Input: bytes=-200 * Output: [null, null, 200, null] */ if (range == null || range.length == 0) return null; var array = range.split(/bytes=([0-9]*)-([0-9]*)/); var start = parseInt(array[1]); var end = parseInt(array[2]); var result = { Start: isNaN(start) ? 0 : start, End: isNaN(end) ? (totalLength - 1) : end }; if (!isNaN(start) && isNaN(end)) { result.Start = start; result.End = totalLength - 1; } if (isNaN(start) && !isNaN(end)) { result.Start = totalLength - end; result.End = totalLength - 1; } return result; }
步骤 3 - 检查数据范围是否合理
回到函数 httpListener(), 在HTTP方法通过之后,现在我们来检查请求的数据范围是否可用. 如果浏览器没有发送 Range 消息头过来, 请求就会直接被当做一般的请求对待. 服务器会返回整个文件,HTTP状态将会是 200 OK. 另外我们还会看看开始和结束位置是否比文件长度更大或者相等. 只要有一个是这种情况,请求的数据范围就是不能被满足的. 返回的状态就将会是 416 Requested Range Not Satisfiable 而 Content-Range 也会被发送.
var responseHeaders = {}; var stat = fs.statSync(filename); var rangeRequest = readRangeHeader(request.headers['range'], stat.size); // If 'Range' header exists, we will parse it with Regular Expression. if (rangeRequest == null) { responseHeaders['Content-Type'] = getMimeNameFromExt(path.extname(filename)); responseHeaders['Content-Length'] = stat.size; // File size. responseHeaders['Accept-Ranges'] = 'bytes'; // If not, will return file directly. sendResponse(response, 200, responseHeaders, fs.createReadStream(filename)); return null; } var start = rangeRequest.Start; var end = rangeRequest.End; // If the range can't be fulfilled. if (start >= stat.size || end >= stat.size) { // Indicate the acceptable range. responseHeaders['Content-Range'] = 'bytes */' + stat.size; // File size. // Return the 416 'Requested Range Not Satisfiable'. sendResponse(response, 416, responseHeaders, null); return null; }
步骤 4 - 满足请求