本文总结一些自定义Server服务器时的相关业务,也包括一些值得关注的编程技巧
最简单的服务器,大概代码如下:
class Server {
constructor(options) {
this.port = options.port;
this.directory = options.directory
this.address = options.address;
// 启动服务
this.start()
}
handleRequest() {
console.log('handleRequest', this)
}
start() {
const server = http.createServer(this.handleRequest);
server.listen(this.port)
}
}
处理handleRequest方法内部的this指向的问题
但需要注意,handleRequest方法内部的this将不再指向new出来的Server实例,因为this.handleRequest这个回调会传给createServer,继续执行就进入了http模块的内部,它会有它自己的一个上下文环境,因此this将会指向这个上下文环境,该问题可以通过如下方式解决:
handleRequest() {
return () => {
console.log(this)
}
}
start() {
const server = http.createServer(this.handleRequest());
server.listen(this.port)
}
或者,如果运行的node版本支持在类中定义箭头函数的话,可以直接写成:
handleRequest = (req, res) => {
console.log(this)
}
当然,在constructor里面用bind处理一下也可以
class Server {
constructor(options) {
this.port = options.port;
this.directory = options.directory
this.address = options.address;
this.handleRequest = this.handleRequest.bind(this)
// 启动服务
this.start()
}
跨域支持
cors(req, res) {
// 这里运行跨域 cors 跨域资源部共享,服务端设置
if (req.headers.origin) {
// 任何人都可以访问此路径
res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
res.setHeader('Access-Control-Allow-Headers', 'authorization'); // 服务端需要允许自定义header
res.setHeader('Access-Control-Max-Age', 10); // 10s 内发一次预检请求
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,DELETE,PUT,OPTIONS')
// 普通请求 会变成复杂请求
if (req.method === 'OPTIONS') { // 允许预检请求后 真实的请求可以正常发出
res.end()
return true
}
}
}
handleRequest = async (req, res) => {
// 此逻辑 处理的是静态逻辑 我把vue项目启动起来了 , 想模拟接口数据
let { pathname, query } = url.parse(req.url, true); // /xxx
if (this.cors(req, res)) {
return
}
开启压缩
需要先取到客户端传过来的accept-encoding这个头,看浏览器客户端支持哪些压缩格式,同时需要注意,response也要设置Content-Encoding以告诉浏览器返回的是什么格式,否则浏览器不认识这个格式,在chrome中就会下载这个文件
compress(req, res) {
let encoding = req.headers['accept-encoding']; // 浏览器会主动给我一个压缩的方式
if (encoding) {
if (encoding.includes('br')) {
res.setHeader('Content-Encoding', 'br')
return zlib.createBrotliCompress()
} else if (encoding.includes('gzip')) {
res.setHeader('Content-Encoding', 'gzip')
return zlib.createGzip()
} else if (encoding.includes('deflate')) {
res.setHeader('Content-Encoding', 'deflate')
return zlib.createDeflate()
}
}
}
利用Referrer头来做防盗链
sendFile(filename, statObj, req, res) {
// 可独流 -》 可写流中 ws.write() ws.end()
res.setHeader('Content-Type', (mime.getType(filename) || 'text/plain') + ';charset=utf-8');
// 在发送图片的时候 对图片进行防盗链处理
if (/\.(jpeg)/.test(filename)) { // 如果是jpeg 则进行防盗链处理
let referer = req.headers['referer'] || req.headers['referrer'];
// 有人来引用这个图片了
if (referer) {
let host = 'http://' + req.headers['host'];
let r1 = url.parse(host).hostname;
let r2 = url.parse(referer).hostname
if (r1 !== r2 && !['http://a.zf.cn:8080/'].includes(referer)) { // 引用的人 和自己的host不一致
createReadStream(path.resolve(__dirname, 'error.jpeg')).pipe(res);
return
}
}
}
if (this.cache(statObj, req, res)) {
return
}
debugDev(filename);
let stream = this.compress(req, res)
if (stream) { // 如果支持压缩 则返回一个转化流
return createReadStream(filename).pipe(stream).pipe(res);
}
createReadStream(filename).pipe(res)
}
完整代码:/jiagouke07-3-node/12.http-server/src/main.js