个人总结:读完这篇文章需要30分钟

http2部分很有学习价值,可以好好看。 

  • 用node搭建TCP服务器
  • 用node搭建HTTP服务器
  • 用node文件fs模块对文件读取,并用流的方式写入
  • 用url路径模块,完成了node路由
  • path模块判断文件类型
  • 用gzip对文件进行压缩
  • 浏览器缓存协议的实现
  • node处理跨域
  • https的node服务器的搭建
  • http2的node服务器的搭建

*n*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);

 

1 node创建TCP服务器

const net = require('net');

let server = net.createServer((socket)=>{
socket.on('data',function (res) {
console.log(res.toString())
});
}); server.listen({
host: 'localhost',
port: 8080
});

访问localhost:8080,

在终端看到下面信息:

 
 
 
那我们给它返回一些数据,要符合http格式。

将一段符合http格式的数据用socket.write(responseDataTpl)返回数据

let responseDataTpl = `HTTP/1.1 200 OK
Connection:keep-alive
Date: ${new Date()}
Content-Length: 12
Content-Type: text/plain Hello world!
`;

,在浏览器中,看到返回的Hello world!

问题:写出固定格式的http响应报文比较麻烦的,给他封装一层

2 node创建HTTP服务器

2.1 创建HTTP服务器

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)

在浏览器里面访问localhost:10080,看到浏览器上显示'hello world'

问题:这个时候,如果我希望传进去是一个文件而不是字符串

2.2 node文件模块(fs)

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)
  • 我们引入文件模块,const fs = require('fs');
  • 同步的时候,我们先读取,执行后边的写入和发送函数
  • 异步的时候,我们在异步读取的回调函数中执行写入和发送

问题:那么现在有一个问题,无论是同步还是异步,我们都需要先读文件,再写入,那么文件很大时,对内存的压力就会非常大。有没有什么办法,边读取边写入

2.3 node流(Stream)

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)
  • 用fs.createReadStream('index.html')创建一个可读流。
  • 用resStream.pipe(res);管道读写操作,写入响应报文
  • 上面代码中我们并没有用res.end(); 发送数据 。因为默认情况下,当数据传送完毕,会自动触发'end'事件
  • 流是支持链式操作的

问题:在我们解决了内存问题后,你会发现,我们index.html中是有一张图片没有加载出来的。原因很简单。因为无论发送什么请求,我们都只返回同样的操作。

2.4 node路由

我们知道在应用成协议中用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)
  • 我们引入了一个url模块,帮助我们去处理路径
  • url.parse(req.url)是将一个路径,帮我们处理成对象,它包含我们常用的路径属性
  • 其中有一个属性是pathname,就是URL端口号和参数之间的路径,也就是我们访问的路径
  • 如果我们直接访问网站后面不加路径,我们给默认指向/index.html
  • 相对路径访问我们给前面加一个'.'
  • 然后我们用文件模块提供的existsSync方法去判断服务器上是否有这个文件
  • 如果没有我们返回404,告诉没有找到文件。有就将文件返回。

问题:Content-Type是处理文件类型的,那么图片类型肯定不会是'text/html' ,虽然浏览器很智能帮我显示出来了,但是我们还是要把这样的错误改过来。

2.5 path模块判断文件类型

我们知道,只要改变 '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;
}
  • 定义了一个getFileType函数,并给出常用的文件类型和它们Content-Type的值
  • 应用了path模块,用path模块上的extname方法取出扩展名
  • 然后跟定义的对象去匹配,如果没有找到,就给一个默认的值

2.5 用gzip对文件进行压缩

(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压缩的

  • http://localhost:8088/home.html
  • http://localhost:10080/home.html

home文件中放了一张我在颐和园用相机拍的5M的图片

你可以打开多个浏览器窗口,分别先访问两个文件,可以多测几遍,你会发现有gzip压缩的明显要慢

为什么会这样呢,道理很简单,因为我们的服务器和浏览器都在同一台电脑上,传输速度很快。所以压缩和解压的时间就被放大了。这也告诉我们并不是什么场景都适合对文件进行压缩的。

2.6 浏览器缓存协议的实现

对http浏览器缓存协议进行一个实现。

**(1)强缓存 ** 强缓存我们在响应头中给一个一周的过期时间 参考代码cache.js

Cache-Control : max-age = 604800'
  • 我们可以看到在第二次刷新的时候,文件中的资源就会从浏览的缓存中取。
  • 如果不想从缓存中取,可以强制刷新,或打开Disable Cache
  • 强刷的时候,你再看localhost请求头中会带上 Cache-Control: no-cache
  • 普通刷新资源文件会有Cache-Control: no-cache,这是因为资源文件是从缓存中取的,而Cache-Control: no-cache是你上次强刷的时候带上去的。
  • 如果新打开一个窗口,再次访问同一个网页,不用从缓存中取

**(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();
}

这里只是举了一个最简单的例子,真实项目中是不可能把所有的文件都返回同一个字符串的。

2.7 node处理post和get请求

(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)
}
  • 我们知道url.parsl()能读取url,query就是get方法带的的参数
  • 当要跳转的路径是是'/form_result.html'并且getArgument有值时
  • 我们用文件模块同步读取出'form_result.html'的内容
  • 转换成字符串之后,在将表单中的name替换成name=XXX

这时候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)
})
  • post方法是在请求头中监听data事件的,请求报文中,有请求体时,被触发
  • 所以我们在监听到‘data’事件被触发时,我们也是执行上面操作
  • 而这个时候如果发送get请求,就不会被响应
  • 我们学事件知道,我们可以给‘data’绑定多个事件,而每次post请求必然会触发。这就是对服务器造成的副作用。

2.8 node处理跨域

参考代码: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)
};
  • 我们分别在本地启动了两个服务
  • 让一个端口是5000,另一个端口是9088
  • 我们在5000的端口访问,cors.html
  • 在html中,我们ajax调用9088端口的data.json
  • 这样就形成了跨域,我们允许5000端口访问,就会返回数据
  • 如果我们把不填,或者不写5000端口,你会看到收不到数据

3 https与http2

3.1 https的node服务器的搭建

openssl win32(64)下载:http://slproweb.com/products/Win32OpenSSL.html

配置环境变量path

知道了原理后,我们在终端生成证书和私钥吧。

(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模块,填好我们证书和私钥
  • 剩下的代码现在看起来是不是很简单

服务器访问: https://localhost:8000/

  • 这样我们访问https就能请求到网页了
  • 当然会提示我们不安全,继续就好啦
  • 为啥会提示我们不安全,刚才自己怎么填的证书,心里没数嘛

3.2 http2的node服务器的搭建

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);
  • 我们还是引入https时创建的私钥和证书
  • 我们创建http2的服务
  • 在http2流的概念。所以我们写入请求头。并返回请求体
  • 我们在浏览器上访问:https://localhost:8443/

这样我们就完成了一个最简单的http2的访问。

node.学习笔记(关于http2的讲解)的更多相关文章

  1. node学习笔记(二)(ajax方式向node后台提交数据)

    通过ajax向node后台提交数据过程(附手写前后台代码),并总结post与get的区别 POST 前台代码 //CSS简单给点样式 <style> form{ width: 200px; ...

  2. node学习笔记第一天

    ES6---* JavaScript语言随着使用的人越来越多,ECMA语法规范:if/else* 为了让js语言更适应大型应用的开发.旨在消除一些怪异的行为 ### 包含内容(strict严格模式)- ...

  3. Nodejs全站开发学习系列 & 深入浅出Node学习笔记 & Spider抓取

    https://course.tianmaying.com/node 这个系列的文章看起来很不错,值得学习一下. /Users/baidu/Documents/Data/Interview/Web-S ...

  4. Node学习笔记(四):gulp+express+io.socket部署angularJs2(填坑篇)

    这篇就先暂停下上篇博客--你画我猜的进度,因为在做这个游戏的时候,想采用最新的ng2技术,奈何坑是一片又一片,这边就先介绍下环境部署和填坑史 既然要用ng2,首先要拿到资源,我这边用的是angular ...

  5. Node学习笔记(三):基于socket.io web版你画我猜(一)

    经过惨淡的面试,也是知道了自己的不足,刚好最近在学习node,心中便有了做一个web版的你画我猜的想法 首先说下思路,在做准备工作的时候,有两个大概的思路: 1.规定一块div,捕捉鼠标事件,动态生成 ...

  6. node 学习笔记 - Modules 模块加载系统 (1)

    本文同步自我的个人博客:http://www.52cik.com/2015/12/11/learn-node-modules-path.html 用了这么久的 require,但却没有系统的学习过 n ...

  7. node 学习笔记 - path 处理

    本文同步自我的个人博客:http://www.52cik.com/2015/12/04/learn-node-path.html path 模块是 node 用于整理.转换.合并路径的神器,只要是路径 ...

  8. node 学习笔记 - fs 文件操作

    本文同步自我的个人博客:http://www.52cik.com/2015/12/03/learn-node-fs.html 最近看到群里不少大神都开始玩 node 了,我感觉跟他们步伐越来越大了, ...

  9. node 学习笔记

    以下笔记默认安装完成node 及npm 1.安装express 新版本的express-generator已经独立出来,全局安装这个包就ok. npm install express-generato ...

随机推荐

  1. hadoop-08-关闭THP服务

    hadoop-08-关闭THP服务 #查看THP服务cat /sys/kernel/mm/redhat_transparent_hugepage/enabledcat /sys/kernel/mm/r ...

  2. iOS6和iOS7处理push不同之处,解决反复push,-(void) application: didReceiveRemoteNotification: fetchCompletionHandl

    如果读者已经知道push的基本知识,本文仅仅是解决一些适配,兼容问题.如果对push 不甚了解,參考以下的文章 1.[iOS push全方位解析](一) push的概述 2.[iOS push全方位解 ...

  3. HDU 1114 Piggy-Bank(一维背包)

    题目地址:HDU 1114 把dp[0]初始化为0,其它的初始化为INF.这样就能保证最后的结果一定是满的,即一定是从0慢慢的加上来的. 代码例如以下: #include <algorithm& ...

  4. Google代码规范工具Cpplint的使用

    Cpplint是一个python脚本,Google使用它作为自己的C++代码规范检查工具. 假设你所在的公司也使用Google C++代码规范,那么你有必要了解下Cpplint. 以下说一下Cppli ...

  5. eclipse软件快捷键的使用

    [Ct rl+T] 搜索当前接口的实现类 1. [ALT +/]    此快捷键为用户编辑的好帮手,能为用户提供内容的辅助,不要为记不全方法和属性名称犯愁,当记不全类.方法和属性的名字时,多体验一下[ ...

  6. 最短路径----SPFA算法

    求最短路径的算法有许多种,除了排序外,恐怕是ACM界中解决同一类问题算法最多的了.最熟悉的无疑是Dijkstra,接着是Bellman-Ford,它们都可以求出由一个源点向其他各点的最短路径:如果我们 ...

  7. 陈-朱-兴- js写法【案例】:

    ajax请求: 一.从服务器端请求数据: var url = '';url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid='+ ...

  8. OGG切换步骤

    步骤描述 提前准备好切换方案:以及其他相关人员的配合 切换至容灾数据库: (1)停止前端业务,确认目标端数据已经追平 (2)数据校验,确认数据一致 (3)停止生产库OGG进程(停止后可以直接删除) ( ...

  9. caffe(7) solver及其配置

    solver算是caffe的核心的核心,它协调着整个模型的运作.caffe程序运行必带的一个参数就是solver配置文件.运行代码一般为 # caffe train --solver=*_slover ...

  10. vue分页组件火狐中出现样式问题

    分页的操作到了火狐浏览器会样式 怎么解决? 其实就是将input的type属性变成了text,因为number属性会变成上下的小箭头