04月11, 2018

node深入笔记(5)

第五篇(HTTP)。。。

基本知识

  • 请求的一方叫客户端,响应的一方叫服务器端
  • 通过请求和响应达成通信
  • HTTP是一种不保存状态的协议

请求报文

alt

常见的方法有:

  • GET 获取资源
  • POST 向服务器端发送数据,传输实体主体
  • PUT 传输文件
  • HEAD 获取报文首部
  • DELETE 删除文件
  • OPTIONS 询问支持的方法
  • TRACE 追踪路径

响应报文

alt

状态码

类别 说明
1XX Informational(信息性状态码
2XX Success(成功状态码)
3XX Redirection(重定向)
4XX Client Error(客户端错误状态码)
5XX Server Error(服务器错误状态吗)

2XX 成功

  • 200(OK 客户端发过来的数据被正常处理
  • 204(Not Content 正常响应,没有实体
  • 206(Partial Content 范围请求,返回部分数据,响应报文中由Content-Range指定实体内容

3XX 重定向

  • 301(Moved Permanently) 永久重定向
  • 302(Found) 临时重定向,规范要求方法名不变,但是都会改变
  • 303(See Other) 和302类似,但必须用GET方法
  • 304(Not Modified) 状态未改变 配合(If-Match、If-- Modified-Since、If-None_Match、If-Range、If-Unmodified-Since)
  • 307(Temporary Redirect) 临时重定向,不该改变请求方法

4XX 客户端错误

  • 400(Bad Request) 请求报文语法错误
  • 401 (unauthorized) 需要认证
  • 403(Forbidden) 服务器拒绝访问对应的资源
  • 404(Not Found) 服务器上无法找到资源

5XX 服务器端错误

  • 500(Internal Server Error)服务器故障
  • 503(Service Unavailable) 服务器处于超负载或正在停机维护

headers

通用首部字段

首部字段名 说明
Cache-Control 控制缓存行为
Connection 链接的管理
Date 报文日期
Pragma 报文指令
Trailer 报文尾部的首部
Trasfer-Encoding 指定报文主体的传输编码方式
Upgrade 升级为其他协议
Via 代理服务器信息
Warning 错误通知

请求首部字段

首部字段名 说明
Accept 用户代理可处理的媒体类型
Accept-Charset 优先的字符集
Accept-Encoding 优先的编码
Accept-Langulage 优先的语言
Authorization Web认证信息
Expect 期待服务器的特定行为
From 用户的电子邮箱地址
Host 请求资源所在的服务器
If-Match 比较实体标记
If-Modified-Since 比较资源的更新时间
If-None-Match 比较实体标记
If-Range 资源未更新时发送实体Byte的范围请求
If-Unmodified-Since 比较资源的更新时间(和If-Modified-Since相反)
Max-Forwards 最大传输跳数
Proxy-Authorization 代理服务器需要客户端认证
Range 实体字节范围请求
Referer 请求中的URI的原始获取方
TE 传输编码的优先级
User-Agent HTTP客户端程序的信息

响应首部字段

首部字段名 说明
Accept-Ranges 是否接受字节范围
Age 资源的创建时间
ETag 资源的匹配信息
Location 客户端重定向至指定的URI
Proxy-Authenticate 代理服务器对客户端的认证信息
Retry-After 再次发送请求的时机
Server 服务器的信息
Vary 代理服务器缓存的管理信息
www-Authenticate 服务器对客户端的认证

实体首部字段

首部字段名 说明
Allow 资源可支持的HTTP方法
Content-Encoding 实体的编码方式
Content-Language 实体的自然语言
Content-Length 实体的内容大小(字节为单位)
Content-Location 替代对应资源的URI
Content-MD5 实体的报文摘要
Content-Range 实体的位置范围
Content-Type 实体主体的媒体类型
Expires 实体过期时间
Last-Modified 资源的最后修改时间

个人觉得只要了解请求首部及响应首部即可。其中响应首部中的range(可以做断点续传,会在下文提及),还有缓存(ETag),这些是必须要掌握的知识。

创建http

  • 方式一:
const http = require('http');
const server = http.createServer(function(req,res){
    res.end(123);
});
server.listen(8080);
  • 方式二:
const http = require('http'); 
const server = http.createServer();
// req是请求 是一个可读流 = socket
// res是响应 是一个可写流
server.on('request',function(req,res){
    let method = req.method;
    let httpVersion  = req.httpVersion;
    let url = req.url;
    let headers = req.headers; // 请求头的名字都是小写的
    console.log(method,httpVersion,url,headers);
    // 如果数据 大于64k data事件可能会触发多次
    let buffers = [];
    // 如果没有请求体 不会走on('data'),没有请求体也会触发end事件
    req.on('data',function(data){
        console.log(1)
        buffers.push(data);
    });

    req.on('end',function(){
        console.log(Buffer.concat(buffers).toString());
        // socket.write socket.end
        res.write('hello');
        res.end('world');
    });
});
// 监听请求的到来
server.on('connection',function(socket){
    console.log('建立连接');
});
server.on('close',function(){
    console.log('服务端关闭')
})
server.on('error',function(err){
    console.log(err);
});
server.listen(8080);

我们通过翻源码,会发现上面两种写法的一致性:

alt

alt

url模块

可以通过它来处理请求的url,简单demo如下:

let url = require('url');
let u = 'http://www.baidu.com:80/abc/index.html?a=1&b=2#hash';

// 可以将查询字符串转化成对象
let urlObj = url.parse(u,true);
console.log(urlObj.query); // 查询字符串
console.log(urlObj.pathname); //  路径

res

在上一篇的最后内容中,有提及req的一些属性,那么我们来看一下res有哪些方法吧。

write && end

可以使用write方法发送响应内容

response.write(chunk,[encoding]);
response.end([chunk],[encoding]);

writeHead

res.writeHead(statusCode, [headers]);

setHeader

res.setHeader(key, value);

它与writeHead的区别是:它不会真正的把响应头写给客户端。

即在writeHeader之后,再执行setHeader是会报错的。

应用

客户端连接

// server端
const http = require('http');
const server = http.createServer(function(req,res){
    let contentType = req.headers['content-type'];
    let buffers = [];
    req.on('data',function(chunk){
        buffers.push(chunk);
    });
    req.on('end',function(){
        let content = Buffer.concat(buffers).toString();
        if(contentType === 'application/json'){
            console.log(JSON.parse(content).name)
        }else if(contentType === 'application/x-www-form-urlencoded'){
            let queryString = require('querystring');
            console.log(queryString.parse(content).age)
        }
        res.end('hello');
    });
});
server.listen(4000);
// client
const http = require('http');
const options = {
    hostname:'localhost',
    port:4000,
    path: '/',
    method:'get',
    // 告诉服务端我当前要给你发什么样的数据
    headers:{
        'Content-Type':'application/x-www-form-urlencoded',
        'Content-Length':5
    }
}
const req = http.request(options, function(res) {
    res.on('data',function(chunk){
        console.log(chunk.toString());
    });
});
req.end('age=1');

需要注意的是客户端请求需要传递Content-Length,不然会有问题。

当然在实际工作中,可能直接就用下面两个npm包了(通常用promise的那个):

alt

多语言切换

可以通过Accept-Language检测浏览器的语言

  • 请求头格式 Accept-Language: Accept-Language:zh-CN,zh;q=0.9
  • 响应头格式 Content-Language:zh-CN
const pack = {
    'en': { title: 'english' },
    'zh-CN': { title: '中文' }
}
const http = require('http');
const server = http.createServer(function (req, res) {
    let lan = 'en';
    let language = req.headers['accept-language'];
    if (language) {
        lan = language.split(',').map(function (item) {
            let values = item.split(';');
            return {
                name: values[0],
                q: values[1] ? parseInt(values[1]) : 1
            }
        }).sort((lang1, lang2) => lang2.q - lang1.q).shift().name;
        console.log(lan)
    }
    res.end(pack[lan] ? pack[lan].title : pack['en'].title);
}).listen(4000);

图片防盗链

这个QQ空间图片比较常见,引用过去之后会变成裂图。

实现原理:

  • 从一个网站跳转,或者网页引用到某个资源文件时,HTTP请求中带有Referer表示来源网页的URL
  • 通过检查请求头中的Referer来判断来源网页的域名
  • 如果来源域名不在白名单内,则返回错误提示 用浏览器直接访问图片地址是没有referer的
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const root = path.join(__dirname, 'public');

function removePort(host) {
    return host.split(':')[0]
}

function getHostName(urlAddr) {
    const { host } = url.parse(urlAddr);
    return removePort(host);
}

function request(req, res) {
    let refer = req.headers['referer'] || req.headers['referrer'];
    if (refer) {
        let referHost = getHostName(refer);
        let host = removePort(req.headers['host']);
        if (referHost != host) {
            sendForbidden(req, res);
        } else {
            serve(req, res);
        }
    } else {
        serve(req, res);
    }
}

function serve(req, res) {
    let {
        pathname
    } = url.parse(req.url);
    let filepath = path.join(root, pathname);
    console.log(req.url, filepath);

    fs.stat(filepath, (err, stat) => {
        if (err) {
            res.end('Not Found');
        } else {
            fs.createReadStream(filepath).pipe(res);
        }
    });
}

function sendForbidden(req, res) {
    res.end('防盗链');
}
const server = http.createServer();
server.on('request', request);
server.listen(8080);

代理服务器

正向代理与反向代理【总结】

let httpProxy = require('http-proxy');
let http = require('http');
let proxy = httpProxy.createProxyServer();

http.createServer(function (req, res) {
    proxy.web(req, res, {
        target: 'http://localhost:8000'
    });
    proxy.on('error', function (err) {
        console.log('出错了');
        res.end(err.toString());
    });
}).listen(8080);

上面代码表示的是请求localhost:8080时,转发到http://localhost:8000。像webpack-dev-server的转发请求模块:http-proxy-middleware,就是使用到了http-proxy

虚拟主机

通过Host实现多个网站共用一个端口,多个网站共用一个服务器

const http = require('http');
const httpProxy = require('http-proxy');
const proxy = httpProxy.createProxyServer();

let hosts = {
    'www.test1.com': 'http://localhost:8000',
    'www.test2.com': 'http://localhost:9000'
}
http.createServer(function (req, res) {
    let host = req.headers['host'];
    host = host.split(':')[0];
    let target = hosts[host];
    proxy.web(req, res, {
        target
    });
}).listen(80);

Range

当用户在听一首歌的时候,如果听到一半(网络下载了一半),网络断掉了,用户需要继续听的时候,文件服务器不支持断点的话,则用户需要重新下载这个文件。而Range支持的话,客户端应该记录了之前已经读取的文件范围,网络恢复之后,则向服务器发送读取剩余Range的请求,服务端只需要发送客户端请求的那部分内容,而不用整个文件发送回客户端,以此节省网络带宽。

获取部分内容的范围请求

为了实现中断恢复下载的需求,需要能下载指定下载的实体范围

  • 请求头中的Range来指定 资源的byte范围
  • 响应会返回状态码206响应报文
  • 对于多重范围的范围请求,响应会在首部字段Content-Type中标明multipart/byteranges

alt

alt

如何应用

// 服务端
const http = require('http');
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const stat = promisify(fs.stat);
// 客户端要发一个头Range:bytes=0-10
// 服务端返回一个头
// Accept-Ranges:bytes
// Content-Range:0-10/总大小
const server = http.createServer(async function (req, res) {

    let p = path.join(__dirname, 'content.txt');
    // 判断当前文件的大小
    let statObj = await stat(p);
    let start = 0;
    let end = statObj.size - 1; // 读流是包前又包后的
    let total = end
    let range = req.headers['range'];
    if (range) {
        // 告诉它支持范围请求
        res.setHeader('Accept-Ranges','bytes');
        // ['匹配的字符串','第一个分组']
        let result = range.match(/bytes=(\d*)-(\d*)/);
        start = result[1]?parseInt(result[1]):start;
        end = result[2]?parseInt(result[2])-1:end;
        // 获取成功并且文件总大小是多少
        res.setHeader('Content-Range',`${start}-${end}/${total}`)
    }
    res.setHeader('Content-Type', 'text/plain;charset=utf8');
    fs.createReadStream(p, { start, end }).pipe(res);
});
server.listen(3000);
// 客户端
const options = {
    hostname: 'localhost',
    port: 3000,
    path: '/',
    method: 'GET'
}
const fs = require('fs');
const path = require('path');
const http = require('http');
const ws = fs.createWriteStream('./download.txt');
let pause = false;
let start = 0;
// 监听键盘事件,如果有输入p,则暂停
process.stdin.on('data', function (chunk) {
    chunk = chunk.toString();
    if (chunk.includes('p')) {
        pause = true
    } else {
        pause = false;
        download();
    }
});
function download() {
    options.headers = {
        Range: `bytes=${start}-${start + 10}`
    }
    start += 10;
    // 发请求
    // 0-10
    http.get(options, function (res) {
        let range = res.headers['content-range'];
        let total = range.split('/')[1];
        let buffers = [];
        res.on('data', function (chunk) {
            buffers.push(chunk);
        });
        res.on('end', function () {
            //将获取的数据写入到文件中
            ws.write(Buffer.concat(buffers));
            setTimeout(function () {
                if (pause === false && start < total) {
                    download();
                }
            }, 1000)
        })
    })
}
download();

压缩

node的压缩文档

const fs = require('fs');
const path = require('path')
const zlib = require('zlib');

// 压缩
function zip(src){
    // 压缩流 转化流
    const gzip = zlib.createGzip();
    fs.createReadStream(src).pipe(gzip).pipe(fs.createWriteStream(src+'.gz'))
}
// zip(path.join(__dirname,'./1.txt'));

// 解压
function unzip(src){
    const gunzip = zlib.createGunzip();
    fs.createReadStream(src)
    .pipe(gunzip)
    .pipe(fs.createWriteStream(path.basename(src,'.gz')));
}
// unzip(path.join(__dirname,'./1.txt.gz'));

在HTTP中,我们可以根据请求头来判断要不要对传输的内容进行压缩。

const http = require('http');
const path = require('path');
const fs = require('fs');
const zlib = require('zlib');
http.createServer(function (req, res) {
    const p = path.join(__dirname, '1.txt');
    // Accept-Encoding: gzip, deflate, br 客户端
    const header = req.headers['accept-encoding'];
    res.setHeader('Content-Type','text/html;charset=utf8');
    if (header) {
        if (header.match(/\bgzip\b/)) {
            const gzip = zlib.createGzip();
            res.setHeader('Content-Encoding','gzip');
            fs.createReadStream(p).pipe(gzip).pipe(res);
        } else if (header.match(/\bdeflate\b/)) {
            const deflate = zlib.createDeflate();
            res.setHeader('Content-Encoding','deflate')
            fs.createReadStream(p).pipe(deflate).pipe(res);
        }else{
            fs.createReadStream(p).pipe(res);
        }
    }else{
        fs.createReadStream(p).pipe(res);
    }
}).listen(8080);
`

加密

crypto

crypto是node.js中实现加密和解密的模块 在node.js中,使用OpenSSL类库作为内部实现加密解密的手段 OpenSSL是一个经过严格测试的可靠的加密与解密算法的实现工具

散列(哈希)算法

散列算法也叫哈希算法,用来把任意长度的输入变换成固定长度的输出,常见的有md5,sha1等,它有以下特点:

  • 相同的输入会产生相同的输出
  • 不同的输出会产生不同的输出
  • 任意的输入长度输出长度是相同的
  • 不能从输出推算出输入的值

示例

var crypto = require('crypto');
var md5 = crypto.createHash('md5');//返回哈希算法
var md5Sum = md5.update('hello');//指定要摘要的原始内容,可以在摘要被输出之前使用多次update方法来添加摘要内容
var result = md5Sum.digest('hex');//摘要输出,在使用digest方法之后不能再向hash对象追加摘要内容。
console.log(result);

多次update

const fs = require('fs');
const shasum = crypto.createHash('sha1');//返回sha1哈希算法
const rs = fs.createReadStream('./readme.txt');
rs.on('data', function (data) {
    shasum.update(data);//指定要摘要的原始内容,可以在摘要被输出之前使用多次update方法来添加摘要内容
});
rs.on('end', function () {
    const result = shasum.digest('hex');//摘要输出,在使用digest方法之后不能再向hash对象追加摘要内容。
    console.log(result);
})

HMAC算法

HMAC算法将散列算法与一个密钥结合在一起,以阻止对签名完整性的破坏。

const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const m = crypto.createHmac('sha1', fs.readFileSync(path.join(__dirname,'./content.txt'))); //将content文件中的内容作为一个密钥
m.update('ok'); 
console.log(m.digest('hex'));

对应加密

blowfish算法是一种对称的加密算法,对称的意思就是加密和解密使用的是同一个密钥。

alt

私钥生成方法:

openssl genrsa -out rsa_private.key 1024
const crypto = require('crypto');
const fs = require('fs');
let str = 'hello';
const cipher = crypto.createCipher('blowfish', fs.readFileSync(path.join(__dirname, 'rsa_private.key')));
let encry = cipher.update(str, 'utf8','hex');
encry += cipher.final('hex');
console.log(encry);

// 对称解密
const deciper = crypto.createDecipher('blowfish', fs.readFileSync(path.join(__dirname, 'rsa_private.key')));
let deEncry = deciper.update(encry, 'hex','utf8');
deEncry += deciper.final('utf8');
console.log(deEncry);

非对称加密

  • 非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)
  • 公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密,如果私钥加密,只能公钥解密
  • 因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法

alt

为私钥创建公钥

openssl rsa -in rsa_private.key -pubout -out rsa_public.key
const crypto = require('crypto');
const fs = require('fs');
let key = fs.readFileSync(path.join(__dirname, 'rsa_private.key'));
let cert = fs.readFileSync(path.join(__dirname, 'rsa_public.key'));
let secret = crypto.publicEncrypt(cert, buffer);//公钥加密
let result = crypto.privateDecrypt(key, secret);//私钥解密
console.log(result.toString());

签名

在网络中,私钥的拥有者可以在一段数据被发送之前先对数据进行签名得到一个签名 通过网络把此数据发送给数据接收者之后,数据的接收者可以通过公钥来对该签名进行验证,以确保这段数据是私钥的拥有者所发出的原始数据,且在网络中的传输过程中未被修改。

alt

let private = fs.readFileSync(path.join(__dirname, 'rsa_private.key'), 'ascii');
let public = fs.readFileSync(path.join(__dirname, 'rsa_public.key'), 'ascii');
let str = 'hello';
let sign = crypto.createSign('RSA-SHA256');
sign.update(str);
let signed = sign.sign(private, 'hex');
let verify = crypto.createVerify('RSA-SHA256');
verify.update(str);
let verifyResult = verify.verify(public,signed,'hex'); //true

缓存

缓存分类

强制缓存

强制缓存,在缓存数据未失效的情况下,可以直接使用缓存数据,那么浏览器是如何判断缓存数据是否失效呢? 我们知道,在没有缓存数据的时候,浏览器向服务器请求数据时,服务器会将数据和缓存规则一并返回,缓存规则信息包含在响应header中。

// 当访问 localhost:8080/a.js
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');
const server = http.createServer(function(req,res){
   let {pathname} =  url.parse(req.url);
   console.log(pathname);
   let p = path.join(__dirname,'public','.'+pathname);
   // 这里可能会存在问题,p有可能是个目录
   fs.stat(p,function(err,stat){
        if(!err){
            sendFile(req,res,p);
        }else{
            sendError(res);
        }
   })
});
function sendError(res){
    res.statusCode = 404;
    res.end();
}
function sendFile(req,res,p){
    let date = new Date(Date.now()+10*1000);
   // res.setHeader('Expires',date.toUTCString());
    res.setHeader('Cache-Control','max-age=10');
    res.setHeader('Content-Type',mime.getType(p)+';charset=utf8')
    fs.createReadStream(p).pipe(res);
}
server.listen(8080);

这种缓存结果一般有两种:memory cache 与 disk cache。前者是从内存中读,后者是从磁盘读,相比之下,后者会稍微花点时间。

在查阅了一些资料之后,得到的结论是:

Simple Test: Open Chrome Developper Tools / Network. Reload a page multiple times. The table column "Size" will tell you that some files are loaded "from memory cache". Now close the browser, open Developper Tools / Network again and load that page again. All cached files are loaded "from disk cache" now, because your memory cache is empty.

这篇文章的结论:在命中强缓存的情况下,进程初次渲染会从磁盘读取缓存资源。Chrome会将部分资源保存到内存中

对比缓存

  • 对比缓存,顾名思义,需要进行比较判断是否可以使用缓存。
  • 浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中。
  • 再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据。

通过最后修改时间来对比:

const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');
const server = http.createServer(function (req, res) {
    let { pathname } = url.parse(req.url);
    let p = path.join(__dirname, 'public', '.' + pathname);
    fs.stat(p, function (err, stat) {
        // 根据修改时间判断
        // if-modified-since  Last-Modified
        if (!err) {
            let since = req.headers['if-modified-since'];
            if(since){
                if(since === stat.ctime.toUTCString()){
                    res.statusCode = 304;
                    res.end();
                }else{
                    sendFile(req,res,p,stat);
                }
            }else{
                sendFile(req,res,p,stat);
            }
        } else {
            sendError(res);
        }
    })
});
function sendError(res) {
    res.statusCode = 404;
    res.end();
}
function sendFile(req, res, p,stat) {
    res.setHeader('Cache-Control','no-cache')
    res.setHeader('Last-Modified',stat.ctime.toUTCString());
    res.setHeader('Content-Type', mime.getType(p) + ';charset=utf8')
    fs.createReadStream(p).pipe(res);
}
server.listen(8080);

最后修改时间这个方案并不是太靠谱,它存在下面这些问题:

  • 某些服务器不能精确得到文件的最后修改时间, 这样就无法通过最后修改时间来判断文件是否更新了。
  • 某些文件的修改非常频繁,在秒以下的时间内进行修改. Last-Modified只能精确到秒。
  • 一些文件的最后修改时间改变了,但是内容并未改变。 我们不希望客户端认为这个文件修改了。
  • 如果同样的一个文件位于多个CDN服务器上的时候内容虽然一样,修改时间不一样。

对此,我们可以采用ETag的方案。它是根据实体内容生成的一段hash字符串,可以标识资源的状态。当资源发生改变时,ETag也随之发生变化。 ETag是Web服务端产生的,然后发给浏览器客户端。

const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');
const crypto = require('crypto');
// 根据的是最新修改时间  这回根据的是文件的内容 
// ETag:md5加密 / if-none-match
const server = http.createServer(function (req, res) {
    let { pathname } = url.parse(req.url);
    let p = path.join(__dirname, 'public', '.' + pathname);
    fs.stat(p, function (err, stat) {
        let md5 = crypto.createHash('md5');
        let rs = fs.createReadStream(p);
        rs.on('data',function(data){
            md5.update(data);
        });
        rs.on('end',function(){
            let r = md5.digest('hex'); // 当前文件的唯一标识
            // 下次再拿最新文件的加密值 和客户端请求来比较
            let ifNoneMatch = req.headers['if-none-match'];
            if(ifNoneMatch){
                if(ifNoneMatch === r){
                    res.statusCode = 304;
                    res.end();
                }else{
                    sendFile(req,res,p,r);
                }
            }else{
                sendFile(req,res,p,r);
            }
        });
    })
});
function sendError(res) {
    res.statusCode = 404;
    res.end();
}
function sendFile(req, res, p,r) {
    res.setHeader('Cache-Control','no-cache')
    res.setHeader('Etag',r);
    res.setHeader('Content-Type', mime.getType(p) + ';charset=utf8')
    fs.createReadStream(p).pipe(res);
}
server.listen(8080);

当然如果文件比较大,比如说1G,每次这样操作,性能比较低,所以也可以考虑stat.ctime+stat.size来对比,或者把前面两个方案一起加上。

请求流程

第一次请求:

alt

第二次请求:

alt

Cache-Control

  • private 客户端可以缓存
  • public 客户端和代理服务器都可以缓存
  • max-age=60 缓存内容将在60秒后失效
  • no-cache 需要使用对比缓存验证数据,强制向源服务器再次验证
  • no-store 所有内容都不会缓存,强制缓存和对比缓存都不会触发

实现一个静态服务器的脚手架

根据上面的代码,其实差不多可以撸一个简单版本出来。大概是有以下几个部分:

  • 判断URL是文件夹还是文件,如果是文件,则跳到下一步,文件夹的话,显示一个列表即可
  • 判断该文件是否缓存过,如果有缓存,直接读取缓存内容,没有(需要设置一个头,如etag等),则跳到下一步
  • gzip压缩文件
  • 支持Range的功能

在写脚手架的过程中,有两个工具,不得不提一下。

上面一个是生成option,help的一些选项,能用使用者快速明白这个脚手架是干嘛的,下面一个则是交互的问答式命令行。

当然交互的问答式命令行,在node中也有一个模块:readline,能简单地简单上面的代码:

const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

rl.question('你认为 Node.js 中文网怎么样?', (answer) => {
  // 对答案进行处理
  console.log(`多谢你的反馈:${answer}`);

  rl.close();
});

alt

本文链接:www.my-fe.pub/post/node-deep-note-5.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。