Search

3/12/2013

用NodeJS打造你的静态文件服务器 - CNode

用NodeJS打造你的静态文件服务器 - CNode

1. response 要帶 expire header + cache control, 同時出現時 cache control 優先權高, 在 expire 之前瀏覽器不會來問 2. 每個 request 要帶上 Etag + Last-Modified header, 第二次 request 的時候 browser 會把這兩個資訊帶過來問, request with 'if-modified-since' header, 這也要處理
var ext = path.extname(realPath);
ext = ext ? ext.slice(1) : 'unknown';

if (ext.match(config.Expires.fileMatch)) {
    var expires = new Date();
    expires.setTime(expires.getTime() + config.Expires.maxAge * 1000);
    response.setHeader("Expires", expires.toUTCString()); // HTTP 1.0
    response.setHeader("Cache-Control", "max-age=" + config.Expires.maxAge); // HTTP 1.1
}
3. 為所有 response 都加上 Last-Modified header
fs.stat(realPath, function (err, stat) {

    var lastModified = stat.mtime.toUTCString();

    response.setHeader("Last-Modified", lastModified);

});
4. 檢查 If-Modified-Since header
if (request.headers[ifModifiedSince] && lastModified == request.headers[ifModifiedSince]) {
    response.writeHead(304, "Not Modified");
    response.end();
}
5. 加上 gzip
var raw = fs.createReadStream(realPath);
var acceptEncoding = request.headers['accept-encoding'] || "";
var matched = ext.match(config.Compress.match);

if (matched && acceptEncoding.match(/\bgzip\b/)) {
    response.writeHead(200, "Ok", {'Content-Encoding': 'gzip'});
    raw.pipe(zlib.createGzip()).pipe(response);
} else if (matched && acceptEncoding.match(/\bdeflate\b/)) {
    response.writeHead(200, "Ok", {'Content-Encoding': 'deflate'});
    raw.pipe(zlib.createDeflate()).pipe(response);
} else {
    response.writeHead(200, "Ok");
    raw.pipe(response);
}
6. range: bytes=0-99,从0到99之间的数据字节。 bytes=-100,文件的最后100个字节。 bytes=100-,第100个字节开始之后的所有字节。 bytes=0-99,200-299,从0到99之间的数据字节和200到299之间的数据字节。
exports.parseRange = function (str, size) {
    if (str.indexOf(",") != -1) {
        return;
    }

    var range = str.split("-"),
        start = parseInt(range0], 10),
        end = parseInt(range[1], 10);


    // Case: -100
    if (isNaN(start)) {
        start = size - end;
        end = size - 1;
    // Case: 100-
    } else if (isNaN(end)) {
        end = size - 1;
    }

    // Invalid
    if (isNaN(start) || isNaN(end) || start > end || end > size) {
        return;
    }

    return {start: start, end: end};
};
var compressHandle = function (raw, statusCode, reasonPhrase) {
        var stream = raw;
        var acceptEncoding = request.headers['accept-encoding'] || "";
        var matched = ext.match(config.Compress.match);

        if (matched && acceptEncoding.match(/\bgzip\b/)) {
            response.setHeader("Content-Encoding", "gzip");
            stream = raw.pipe(zlib.createGzip());
        } else if (matched && acceptEncoding.match(/\bdeflate\b/)) {
            response.setHeader("Content-Encoding", "deflate");
            stream = raw.pipe(zlib.createDeflate());
        }
        response.writeHead(statusCode, reasonPhrase);
        stream.pipe(response);
    };

if (request.headers["range"]) {
    var range = utils.parseRange(request.headers["range"], stats.size);
    if (range) {
        response.setHeader("Content-Range", "bytes " + range.start + "-" + range.end + "/" + stats.size);
        response.setHeader("Content-Length", (range.end - range.start + 1));
        var raw = fs.createReadStream(realPath, {"start": range.start, "end": range.end});
        compressHandle(raw, 206, "Partial Content");
    } else {
        response.removeHeader("Content-Length");
        response.writeHead(416, "Request Range Not Satisfiable");
        response.end();
    }
} else {
    var raw = fs.createReadStream(realPath);
    compressHandle(raw, 200, "Ok");
}
test with curl:
curl --header "Range:0-20" -i http://localhost:8000/index.html

沒有留言: