Node-http-网站

超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议

  • 接收请求
  • 处理请求
  • 发送响应

net 为什么不能处理浏览器的响应呢?

http 模块和 net 模块之间的区别

http 模块的简单使用

(1). 创建服务器,得到一个 Server 实例对象

  • 任何请求都会触发该 request 请求事件,然后执行事件处理函数
  • 也就是说所有的请求入口就是这个 request 事件
  • 如何区分不同的请求
    • 每个请求有请求报文: 请求头、请求路径、请求方法等信息
  • Node 将每一个请求中的请求报文信息解析为一个对象:Request ,挂载给请求处理函数的第一个参数
    • 也就是说可以通过 Request 请求对象拿到一些请求报文信息,例如请求方法、请求路径、请求头部字段等信息
  • 同时,Node 还提供了一个接口对象:Response
    • 该对象可以用来给当前请求发送响应数据

(2). 监听服务器 Server 对象的 Request 请求事件,设置请求处理函数

(3). 绑定监听端口,启动服务器,设置启动成功之后的回调处理函数

1
2
3
4
5
6
7
8
9
10
11
12
// 导入 http 模块
const http = require('http');
// 创建 http 服务器
const server = http.createServer();
// 监听 request 事件
server.on('request', (req, res) => {
res.end('hello world');
});
// 开启服务端口
server.listen(3000, () => {
console.log('Server is running at port 3000.');
});

对不同的请求发送不同的响应

  • 当用户访问 '/' 的时候,返回 index page
  • 当用户访问 '/add' 的时候,返回 add page
  • 当用户访问 '/about' 的时候,返回 about page
  • 当用户访问 '/xxx' 的时候,返回 404

(1). 获取当前的请求路径(通过 Request 请求对象的 url 属性获取)

这里的请求路径永远都是以 / 开头的

例如你在浏览器地址中输入的是:

  • http://127.0.0.1:3000 则 url 就是 /
  • http://127.0.0.1:3000/ 则 url 就是 /
  • http://127.0.0.1:3000/add 则 url 就是 /add

(2). 发送响应

2.1 请求之后,可以使用 res.write 方法发送响应

2.2 注意:res.write 可以向响应流中多次发送数据,

  • 但是一定要在写完响应流数据之后调用 res.end() 方法结束响应。
  • 否则客户端浏览器还认为你的数据没有发送完毕,一直等待接收。

2.3 一般发送响应数据的时候,很少有这种需要多次调用 write 方法来发送的数据

  • 就是说一般就是 res.write('响应数据'), res.end() 结束响应
  • 所以,可以使用 res.end('响应数据') 直接发送响应数据,同时结束响应
1
2
3
4
5
6
7
8
9
10
11
const http = require('http');
const server = http.createServer();
server.on('request', (req, res) => {
const url = req.url;
// res.write(url);
// res.end();
res.end(url);
});
server.listen(3000, () => {
console.log('Server is running at port 3000...');
});

根据不同请求响应不同内容

因此,可以这样来判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const http = require('http');
const server = http.createServer();
server.on('request', (req, res) => {
const url = req.url;
if (url === '/') {
res.end('<h1>index page</h1>');
} else if (url === '/add') {
res.end('<h1>add page</h1>');
} else if (url === '/about') {
res.end('about page');
} else {
res.end('404 Not Found.');
}
});
server.listen(3000, () => {
console.log('Server is running at port 3000...');
});

处理页面中的静态资源

上面这段代码表示可以解析 html 字符串,那也可以响应页面。

如果要处理一些读取出来的 html 字符串,那读文件的时候就指定编码或者调用 data.toString() 方法转为字符。

  • 这里因为不处理字符串,所以就不转字符
  • res.end() 只能接收 二进制数据或者 字符串,其它都报错
  • 如果传递的字符串,则发送响应的时候,还会自动将字符串转为二进制再发送
  • 如果直接就传递的是二进制数据,则直接发送

当客户端浏览器收到发送的响应数据的时:

  • 浏览器会先查看响应报文头中的 Content-Type 中的 charset 编码,然后根据该编码解析数据
  • 如果响应报文头中没有 Content-Type 那么浏览器则根据 HTML 结构中的 <meta charset="UTF-8"> 来解析数据
  • 可以通过 res.writeHead 方法在结束响应之前,写响应头
  • 查询网址:http://tool.oschina.net/commons
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
const http = require('http');
const fs = require('fs');
const server = http.createServer();
server.on('request', (req, res) => {
const url = req.url;
if (url === '/') {
fs.readFile('.data/index.html', (err, data) => {
if (err) {
throw err;
}
});
// 在结束响应之前,写响应头,指定charset 编码
res.writeHead(202, 'OK', {
'Content-Type': 'text/html; charset=utf-8'
});
res.end(data);
} else if (url === '/add') {
fs.readFile('.data/add.html', (err, data) => {
if (err) {
throw err;
}
});
res.writeHead(202, 'OK', {
'Content-Type': 'text/html; charset=utf-8'
});
res.end(data);
} else if (url === '/about') {
res.writeHead(200, 'OK', {
'Content-Type': 'text/plain; charset=utf-8'
})
res.end('about page');
} else {
fs.readFile('.data/404.html', (err, data) => {
if (err) {
throw err;
}
});
res.writeHead(404, 'Not Found', {
'Content-Type': 'text/html; charset=utf-8'
})
res.end(data);
}
});
server.listen(3000, () => {
console.log('Server is running at port 3000...');
});

处理页面中的动态资源和静态资源

同一个页面中有多个外链,不是指 a 标签。

当浏览器获取到当前响应的 HTML 格式字符串之后,浏览器从上到下依次解析字符串(HTML 结构文档)。

在解析的过程中,如果发现有 link img script iframe 等具有 src 或 href 的标签:a 标签和他们不一样,a 标签是用来跳转的,资源在另一个页面。则,浏览器主动对该资源指向的地址发起请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
const http = require('http');
const fs = require('fs');
const path = require('path');
http
.createServer()
.on('request', (req, res) => {
const url = req.url;
if (url === '/') {
fs.readFile('./data/static/index.html', (err, data) => {
if (err) {
throw err;
}
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
});
res.end(data);
});
} else if (url === '/css/main.css') {
fs.readFile('./data/static/css/main.css', (err, data) => {
if (err) {
throw err;
}
res.writeHead(200, {
'Content-Type': 'text/css; charset=utf-8'
});
res.end(data);
});
} else if (url === '/img/96102-106.jpg') {
fs.readFile('./data/static/img/96102-106.jpg', (err, data) => {
if (err) {
throw err;
}
res.writeHead(200, {
'Content-Type': 'image/jpeg'
});
res.end(data);
});
} else if (url === '/js/main.js') {
fs.readFile('./data/static/js/main.js', (err, data) => {
if (err) {
throw err;
}
res.writeHead(200, {
'Content-Type': 'application/x-javascript; charset=utf-8'
});
res.end(data);
});
}
})
.listen(3000, () => {
console.log('Server is running at port 3000.');
});

index.html 文档内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- src: ./data/static/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- http://127.0.0.1:3000/css/main.css -->
<link rel="stylesheet" href="css/main.css">
<link rel="stylesheet" href="css/a.css">
</head>
<body>
<h1>首页</h1>
<!-- http://127.0.0.1:3000/img/96102-106.jpg -->
<img src="img/96102-106.jpg" alt="">
<!-- http://127.0.0.1:3000/js/main.js -->
<script src="js/main.js"></script>
</body>
</html>

解决静态资源的 mime 类型

  • ‘Content-Type’: mime.lookup(url);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
const http = require('http');
const fs = require('fs');
const path = require('path');
const mime = require('mime');
http
.createServer()
.on('request', (req, res) => {
// / index.html
// /add add.html
// /404 404.html
// http://127.0.0.1:3000/public/css/main.css
// http://127.0.0.1:3000/public/js/main.js
const url = req.url
if (url === '/') {
fs.readFile('./views/index.html', (err, data) => {
if (err) {
throw err;
}
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
});
res.end(data);
});
} else if (url.startsWith('/public/')) {
// /public/css/main.css
// /public/js/main.js
fs.readFile(`.${url}`, (err, data) => {
if (err) {
throw err;
}
// Content-Type ?
// 无论是任何资源,都最好要有 Content-Type
res.writeHead(200, {
'Content-Type': mime.lookup(url);
});
res.end(data);
});
} else if (url === '/add') {
fs.readFile('./views/add.html', (err, data) => {
if (err) {
throw err;
}
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
});
res.end(data);
});
} else {
fs.readFile('./views/404.html', (err, data) => {
if (err) {
throw err;
}
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
});
res.end(data);
});
}
})
.listen(3000, () => {
console.log('Server is running at port 3000.');
});

./views/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="public/css/main.css">
<link rel="stylesheet" href="public/css/a.css">
</head>
<body>
<h1>首页</h1>
<div class="box"></div>
<script src="public/js/main.js"></script>
</body>
</html>

http 和 net 的区别与联系

解释浏览器和服务器的交互本质:

  • 本质上就是浏览器通过 Socket 和 服务器 Socket 进行通信
  • 双方都通过 HTTP 协议进行交流
  • net 模块就是传输层的一个模块,只是为了纯粹的收发数据
  • http 模块构建与 net 模块之上,只不过对于收发的数据会进行解析和包装
  • 所有的BS模型都是使用的 HTTP 协议进行数据的解析和包装

一下代码仅仅用来说明 net 和 HTTP 之间的联系与区别,没有实际应用意义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const net = require('net');
net
.createServer();
.on('connection', socket => {
socket.on('data', data => {
// console.log(data.toString())
// 自己动手解析请求报文
// 1. 按照空行将请求体和请求报文分割
// 2. 按照换行将请求头分割
// 数组中第 0 项就是请求首行
// 其它所有项都是请求首部字段
console.log(socket.remoteAddress, socket.remotePort);
data = data.toString();
const requestContext = data.split('\r\n\r\n');
const requestHead = requestContext[0];
const requestBody = requestContext[1];
const req = {};
// url method httpVersion
// headers
// { key: value, key:value } 给在给 req 对象的 headers
const requestHeadFirst = requestHead.split('\r\n')[0].split(' ');
req.method = requestHeadFirst[0];
req.url = requestHeadFirst[1];
req.httpVersion = requestHeadFirst[2];
req.headers = {};
const requestHeadFileds = requestHead.split('\r\n').slice(1);
for (let item of requestHeadFileds) {
const tmp = item.split(': ');
req.headers[tmp[0]] = tmp[1];
}
// net 模块中发送数据只是纯粹的发送你传入的字符串,有一行空白行。
socket.write(`
HTTP/1.1 200 OK
Server: Itcast
Connection: keep-alive
foo: bar
Content-Type: text/html; charset=utf-8
<h1>hello world</h1>`);
socket.end();
});
})
.listen(3000, () => {
console.log('server is running at port 3000.')
});

当在浏览器地址栏输入了一个地址:http://127.0.0.1:3000/

浏览器按照 HTTP 协议将你输入的地址包装成 HTTP 请求报文,内容如下:

1
2
3
4
5
6
7
8
9
GET / HTTP/1.1
Host: 127.0.0.1:3000
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8

请求报文格式如下:

  • 请求头
    • 请求首行
      • 请求方法 请求路径 HTTP协议版本
    • 请求首部字段
      • 首都字段中放一些额外的附加信息
      • 例如 User-Agent 表示告诉服务器我这个客户端是什么
        • 这里为什么有各种浏览器的标识
        • 原因在早期的网页有各种各样兼容性问题
        • 这个字段还可以用来统计浏览器的使用量占比情况
      • Accept
        • 早期的 HTTP 0.9 中,只能收发普通字符数据 不支持图片等富文本信息
        • 历史原因,现代的服务器和客户端浏览器已经不需要这个东西
  • 空行
  • 请求体
    • 如果是 post 请求才有请求体
    • 如果有请求体,则请求体是在请求头的回车换行之后
    • 如果没有,也会有一个空行存在

响应报文:

  • 响应头
    • 响应首行
      • HTTP协议版本 状态码 状态短语
    • 响应首部字段
  • 空行
  • 响应体
    • 所有的响应数据都在响应头之后的空行之后

net 和 http 模块的关系:

  • http 模块是构建与 net 模块之上的
  • http 中的收发数据还是通过 net 模块中的 Socket 收发数据的
  • http 会将收发的数据按照 HTTP 协议自动帮你解析和包装
    • 例如 http 模块自动将请求报文解析出来,然后挂载给了 req 请求对象
    • 你可以通过 req 请求对象去拿到你想要的信息
  • 为什么既有 net 又有 http 呢?
    • http 只是一个基于 net 之上的一个模块,该模块遵循的 http 协议
    • 会对收发的数据进行 协议格式解析和包装
    • HTTP 协议只是适用于B/S模型
  • 有的业务功能使用的是别的协议
    • 例如 一些智能终端,就用的是别的协议,而不是 HTTP
    • 但是他们都是基于最基本的 Socket 网络编程模型而构建的

浏览器的本质

  • Socket 客户端
    • 收发数据
  • 渲染 HTML、CSS
  • 解析和执行 JavaScript 代码
感谢您的支持!