node的出现,真是让用惯js的前端工程师碰见了爱情,进而大踏步的走向了后端,尽管有时候会被质疑,不被理解。那又有什么关系。javascript
本文是《一站到底 ---前端基础之网络》 代码的整理。但也是一篇独立的node零基础学习笔记。 首先你须要安装node环境。你们本身去看教程 就好。本文和函数式编程那篇文章是同样的思路。咱们先用先实现。若是有机会咱们回过头再来补理论,其实API也没啥须要补,有时间咱们写写node异步队列和DC的算法,但你有什么不明白的能够随着查看文档 。好的,老规矩,咱们看看,本文都完成了那些内容。css
本文代码在github html
const net = require('net');
let server = net.createServer((socket)=>{
socket.on('data',function (res) {
console.log(res.toString())
});
});
server.listen({
host: 'localhost',
port: 8080
});
复制代码
这里咱们讲一下node的事件机制:前端
//events 模块只提供了一个对象: events.EventEmitter
//EventEmitter 的核心就是事件触发与事件监听器功能的封装。
var EventEmitter = require('events').EventEmitter;
//一个socket对象
var socket = new EventEmitter();
//咱们在socket对象上绑定data事件,若是是多个函数会被前后调用
socket.on('data', function(res) {
console.log(res);
});
socket.on('data', function(res) {
console.log(res + '111');
});
//咱们用emit的方法去触发事件,在1秒后咱们出发,咱们触发事件时,能够传递参数。
setTimeout(function() {
socket.emit('data' , "hello" );
}, 1000);
复制代码
咱们会在控制台看到下面的信息。java
这时咱们会过头来看,浏览器左下角,是否是一直在显示等待响应,是由于咱们尚未返回数据啊,那咱们给 它返回一些数据。咱们知道要符合http格式。node
咱们将一段符合http格式的数据用socket.write(responseDataTpl)去返回数据git
let responseDataTpl = `HTTP/1.1 200 OK
Connection:keep-alive
Date: ${new Date()}
Content-Length: 12
Content-Type: text/plain
Hello world!
`;
复制代码
问题:咱们已经发现了写出固定格式的http响应报文杯仍是比较麻烦的,咱们为何不能封装一层呢?github
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('hello world'); // 发送响应数据
})
server.on('clientError', (err, socket) => {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
})
server.listen(10080)
复制代码
问题:那么这个时候,若是我但愿传进去是一个文件而不是字符串,改怎么办呢?ajax
node的文件模块是很是强大的,能够对文件进行读取,增删改查。这里咱们先讲如何读取的。读取分两种一种同步,一种异步。算法
const fs = require('fs');
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' });
// 同步
// let data = fs.readFileSync('index.html');
// res.write(data);
// res.end(); // 发送响应数据
// 异步
fs.readFile('index.html', function (err, data) {
res.write(data);
res.end(); // 发送响应数据
})
})
server.on('clientError', (err, socket) => {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
})
server.listen(8088)
复制代码
问题:那么如今有一个问题,不管是同步仍是异步,咱们都须要先读文件,再写入,那么文件很大时,对内存的压力就会很是大。咱们有没有什么办法,边读取边写入?
Stream 是一个抽象接口,做用就是能把文件,读一点写一点。这样不就不用占很大内存了。咱们来看看怎么实现的?
const fs = require('fs');
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' });
// let resStream = fs.createReadStream('index.html');
// resStream.pipe(res);
//流是能够支持链式操做的
fs.createReadStream('index.html').pipe(res)
})
server.on('clientError', (err, socket) => {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
})
server.listen(10080)
复制代码
问题:在咱们解决了内存问题后,你会发现,咱们index.html中是有一张图片没有加载出来的。缘由很简单。由于不管发送什么请求,咱们都只返回一样的操做。那么咱们能如何区分不一样的请求呢?
咱们知道在应用成协议中用URL来表示文件的位置。区分不一样请求的一个重要任务就是区分路径。那么对路径的处理node中提供了一个url模块,让咱们来看看吧。
const fs = require('fs');
const http = require('http');
const url = require("url");
const server = http.createServer((req, res) => {
//pathname是取到端口号后面的地址
let pathname = url.parse(req.url).pathname;
if(pathname === '/') pathname = '/index.html';
let resPath = '.' + pathname;
//判断路径是否存在
if(!fs.existsSync(resPath)){
res.writeHead(404, {'Content-Type': 'text/html'});
return res.end('<h1>404 Not Found</h1>');
}
//若是存在,将在路径下的文件返回给页面
res.writeHead(200, { 'Content-Type': 'text/html' });
fs.createReadStream(resPath).pipe(res)
})
server.on('clientError', (err, socket) => {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
})
server.listen(10080)
复制代码
问题:那么如今,咱们就能在浏览器上看见咱们美丽的大娟的图片了,可是咱们在学http的时候知道Content-Type是处理文件类型的,那么图片类型确定不会是'text/html' ,虽然浏览器很智能帮我显示出来了,可是咱们仍是要把这样的错误改过来。
咱们知道,只要改变 'Content-Type'的文件类型便可。
function getFileType(resPath){
const EXT_FILE_TYPES = {
'default': 'text/html',
'.js': 'text/javascript',
'.css': 'text/css',
'.json': 'text/json',
'.jpeg': 'image/jpeg',
'.jpg': 'image/jpg',
'.png': 'image/png',
//...
}
let path = require('path');
let mime_type = EXT_FILE_TYPES[path.extname(resPath)] || EXT_FILE_TYPES['default'];
return mime_type;
}
复制代码
sudo npm install supervisor -g
supervisor 02-http-fs-url.js
复制代码
问题:咱们大娟的图片才只有一百多K,若是是图片很大咱们是能够先压缩再传输的
(1)咱们先取出请求头中的accept-encoding参数,若是参数不存在,咱们赋值成''
let acceptEncoding = req.headers['accept-encoding'];
if (!acceptEncoding) { acceptEncoding = '';};
复制代码
(2)而后咱们用正则去判断acceptEncoding是否用了gzip压缩,固然这里能够有多个判断压缩格式。这里咱们只写一个。
if(/\bgzip\b/.test(acceptEncoding)){
//执行压缩,并在响应头中告诉浏览器压缩的格式
}else{
//不执行压缩
}
复制代码
(3)咱们须要引用zlib模块对文件进行压缩。这里咱们用Gzip,就调用Gzip的方法。 而后咱们对文件流先进行一步压缩,在写到响应体中。
const zlib = require('zlib');
let raw = fs.createReadStream(resPath);
raw.pipe(zlib.createGzip()).pipe(res);
复制代码
(4)最后咱们还须要在响应头中告诉浏览器个人文件已经给你压缩成什么格式啦
'Content-Encoding': gzip
复制代码
而后咱们开两个终端分别用启动有gzip和没有gzip压缩的
home文件中放了一张我在颐和园用相机拍的5M的图片
你能够打开多个浏览器窗口,分别先访问两个文件,能够多测几遍,你会发现有gzip压缩的明显要慢。
为何会这样呢,道理很简单,由于咱们的服务器和浏览器都在同一台电脑上,传输速度很快。因此压缩和解压的时间就被放大啦。这也告诉咱们并非什么场景都适合对文件进行压缩的。
这一节没有node的新知识,咱们对http浏览器缓存协议进行一个实现。咱们也不须要进行压缩,因此上一节压缩的内容不会加。
**(1)强缓存 ** 强缓存咱们在响应头中给一个一周的过时时间 参考代码cache.js
Cache-Control : max-age = 604800' 复制代码
**(2)弱缓存 ** 参考代码cache2.js
etag须要一个双引号的字符串,而后咱们把它写入响应头中
let etagStr = "dajuan"; //etag 要加双引号
res.writeHead(200, {
'Content-Type': getFileType(resPath),
'etag' : etagStr
});
复制代码
当再次访问的时候咱们须要判断一下,if-none-match带的值于如今etagStr值是否一致。若是一致直接返回304,不用在返回文件。浏览器看到304,就知道了要从缓存中拿。
let etagStr = "dajuan"; //etag 要加双引号
if(req.headers['if-none-match'] === etagStr){
res.writeHead(304, {
'Content-Type': getFileType(resPath),
'etag' : etagStr
});
res.end();
}
复制代码
(1)咱们首先分别用get 和 post 写一个表单提交,让其点击都跳转到form_result.html,有一行你好,name
//form.html
<form action="form_result.html" method="get">
<p> get: <input type="text" name="name" /></p>
<input type="submit" value="Submit" />
</form>
<form action="form_result.html" method="post">
<p> post: <input type="text" name="name" /></p>
<input type="submit" value="Submit" />
</form>
//form_result.html
<div>你好,name</div>
复制代码
(2)get方法去处理 参考代码method.js
let pathAll = url.parse(req.url);
let getArgument = pathAll.query; //取出参数 name=XXX
if(pathname === '/form_result.html' && getArgument != undefined){
let text = fs.readFileSync('form_result.html').toString().replace(/name/, getArgument)
fs.writeFileSync('form_result.html',text)
}
复制代码
这时候get提交的表单能够去处理啦,可是post的参数并无在URL中,因此对post没有影响
(3)post方法去处理 参考代码method2.js
req.on('data',(data)=>{
let text = fs.readFileSync('form_result.html').toString().replace(/name/, 'post'+ data)
fs.writeFileSync('form_result.html',text)
})
复制代码
这里咱们留一个问题,咱们在处理文件的时候是同步处理的,若是异步处理咱们改怎么作?
参考代码:cors.js cors2.js
if(req.headers['origin'] ) {
res.writeHead(200, {
'Access-Control-Allow-Origin': 'http://localhost:5000',
'Content-Type': 'text/html'
});
return fs.createReadStream(resPath).pipe(res)
};
复制代码
注: 这里仍是有点小问题,第一我只在第一次访问时,若是端口不符合提示报错了。我怀疑是否是浏览器給服务器地址加入白名单了。第二为何不是书上写的两次求情啊。我第一次即便不写数据,也不会发起第二次请求。不过跨域的效果仍是实现了的。
知道了原理后,咱们在终端生成证书和私钥吧。
(1)openssl genrsa -out server.key 1024 //生成服务器私钥
(2)openssl rsa -in server.key -pubout -out server.pem // 生成公钥
//本身扮演CA机构,给本身服务器颁发证书,CA机构也须要本身私钥,CSR文件(证书签名请求文件),和证书
(3) openssl genrsa -out ca.key 1024 //生成CA 私钥
openssl req -new -key ca.key -out ca.csr //生成CA CSR文件
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt //生成CA 证书
//生成证书签名请求文件
(4) openssl req -new -key server.key -out server.csr //生成server CSR文件
//向本身的机构请求生成证书
(5) openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt //生成server 证书
复制代码
注意:信息随便填,但提示里有格式要注意啊,宝宝们。。。
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('./key/server.key'),
cert: fs.readFileSync('./key/server.crt')
};
https.createServer(options, (req, res) => {
res.writeHead(200);
res.end('hello world\n');
}).listen(8000);
复制代码
服务器访问: https://localhost:8000/
node的http2是试验的API。若是node版本比较低,请先升级。个人是v8.11.3
const http2 = require('http2');
const fs = require('fs');
const server = http2.createSecureServer({
key: fs.readFileSync('./key/server.key'),
cert: fs.readFileSync('./key/server.crt')
});
server.on('error', (err) => console.error(err));
server.on('stream', (stream, headers) => {
// stream is a Duplex
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end('<h1>Hello World</h1>');
});
server.listen(8443);
复制代码
这样咱们就完成了一个最简单的http2的访问啦。