Nodejs http (一)

知识点

Http 状态码

  • 101 websocket 双向通信
  • 200 成功 204 没有响应体 206 断点续传
  • 301(永久重定向) 302(临时重向) 304(缓存)只能服务端设置
  • 401 (没登录没有权限) 403 (登录了没权限) 404 405(请求方法不存在、不支持,比如说发请求,服务器只支持get、post,但是我发了一个delete)
  • 502 负载均衡挂了 500服务器挂

请求方法 RestfulApi

根据不同的动作 做对应的处理

  • get 获取资源
  • post 新增资源
  • put 上传文件 修改
  • delete 删除资源
  • options 跨域出现 (复杂请求时出现) 只是get / post 都是简单请求 + 自定义的header

传输数据

  • 请求行 url

  • 请求头 自定header

  • 请求体 提交的数据

  • 响应行 状态码

  • 响应头 可以自定义

  • 响应体 返还给浏览器的结果

概念

通过node实现一个http服务,都是通过核心模块提供(http模块)

1
2
3
4
5
6
7
8
9
const http = require('http');

// 服务器要有特定的ip 和端口号
let server = http.createServer();

// 开启一个端口号
server.listen(3000, ()=>{
console.log('server start 3000');
})

每次服务端代码发生变化 都需要重启服务
可以安装nodemon node的监视器 监视文件变化的

sudo npm install nodemon -g 使用: nodemon 文件名(可以增加配置文件)

server

新建文件 1.http.js

如果端口被占用了 自动+ 1
1
2
3
4
5
6
7
8
9
10
11
12
const http = require('http');
let server = http.createServer();
let port = 3000;
server.listen(port, ()=>{
console.log('server start ' + port);
});
server.on('error', (err) => {
// console.log(err);
if(err.errno === 'EADDRINUSE'){
server.listen( ++port);
}
})
图解

如果别人请求我,我需要去解析请求
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
const http = require('http');
const url = require('url');

let server = http.createServer();

// 监听请求事件
server.on('request', (req,res)=>{
// req 代表的是客户端
// res 代表的是服务端
// 1)请求行 浏览器访问localhost:3000, nodemon运行的命令行里就会console.log出GET(我现在是服务端,所以nodemon就是服务端在接收数据,浏览器运行就是客户端在发起请求,所以现在每次需要接收到东西,都需要浏览器发起请求)
console.log(req.method); // GET 方法名大写
console.log(req.url); // 请求路径 / 表示首页 url不包含# hash
let {pathname, query} = url.parse(req.url, true);
console.log(pathname,query);
// 2) 请求头
console.log(req.headers); // 取header都是小写
// 3) 请求体
let arr = [];
req.on('data', function(chunk) { // data方法不一定触发,如果是get请求就没有请求体 流的原理 push(null) ,然后走end方法
arr.push(chunk);
});
req.on('end', function() { // end方法一定触发
console.log(Buffer.concat(arr).toString());
console.log('end');
});
// 这里用post请求可以 curl -v -X POST --data a=1 http://localhost:3000,运行,nodemon命令行就能打印出 a=1这个请求体

// 响应行 头 体
res.statusCode = 404; // 响应状态码
// res.setHeader('Content-Length', '1'); // 返回内容的长度 不写注释默然返回全部会自动计算个数
//res.end('end'); // 可写流原理 end方法里面有数据会调write写入,然后再关闭,这里和write('写入数据'); end(); 这样是一样的
// res.end('结束');// 直接写中文会乱码 如下
res.setHeader('Content-Type','text/plain;charset=utf-8');
// 百度 Content-Type对应表,其中 文本 .txt text/plain .js application/x-javascript .html text/html 后面再加上编码charset=utf-8 utf-8要加-不然ie不兼容
// res.end('结束');
res.end('结束');
})


let port = 3000;
server.listen(port, ()=>{
console.log('server start ' + port);
});
server.on('error', (err) => {
// console.log(err);
if(err.errno === 'EADDRINUSE'){
server.listen( ++port);
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 一个完整的url
const requestUrl = `http://username:password@www.baidu.com:80/s?offset=1&limit=30#app`

let result = url.parse(requestUrl,true);
console.log(result)
{
protocol: 'http:',
slashes: true, 是否有/
auth: 'username:password', 用户信息
host: 'www.baidu.com:80',
port: '80',
hostname: 'www.baidu.com', // 主机名
hash: '#app',
search: '?offset=1&limit=30',
query: [Object: null prototype] { offset: '1', limit: '30' }, // line4 true
query: 'offset=1&limit=30', // 不填输出这个
pathname: '/s', 请求路径 资源路由
path: '/s?offset=1&limit=30',
href:
'http://username:password@www.baidu.com:80/s?offset=1&limit=30#app'
}

client

新建文件 2.client.js
中间层
这里相当于是服务端,向1.http.js中的 localhost:3000发送请求

request

get

1
2
3
4
const http = require('http')
http.get('http://localhost:3000',function() {// 这样发送请求是不会有跨域问题的,因为这是服务器
console.log('发送成功');
})

Run Code运行 发送成功 在3000端口看到接收请求
get方法没有请求体

request 发送请求体

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
// 1.http.js
const http = require('http');
const url = require('url');
let server = http.createServer();

// 监听请求事件
server.on('request', (req,res)=>{
console.log(req.method); // POST
console.log(req.url); // /
let {pathname, query} = url.parse(req.url, true);
console.log(pathname,query); // / [Object: null prototype] {}
console.log(req.headers); // { host: 'localhost:3000', connection: 'close' }
let arr = [];
req.on('data', function(chunk) {
arr.push(chunk);
});
req.on('end', function() {
console.log(Buffer.concat(arr).toString());
console.log('end');
res.statusCode = 404;
res.setHeader('Content-Type','text/plain;charset=utf-8');
res.end(Buffer.concat(arr)); // 将你发给我的数据再发送给你,因为on 'data' 'end' 都是异步的,所以这里把line20-22放到end里面
});
})

let port = 3000;
server.listen(port, ()=>{
console.log('server start ' + port);
});
server.on('error', (err) => {
// console.log(err);
if(err.errno === 'EADDRINUSE'){
server.listen( ++port);
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1.client.js
const http = require('http');
let config ={
port: 3000,
hostname: 'localhost',
headers:{
a: 1
},
method: 'POST'
}
// 请求后 会将响应的结果放到函数中
let client = http.request(config, function(res) {
res.on('data', function(chunk){
console.log('接收到服务端返回的数据:', chunk.toString()); // 接收到服务端返回的数据: a=2
})
})
client.end('a=2');
// 或者 client.write(....);client.end();

数据传输

常见格式

1) json字符串 {a:1}
2) 表单格式 a=1&b=2 如test.form.html文件
3) 文件格式 图片 formData
4) 传递是字符串

所以需要告诉服务端传过去的数据类型,在header里设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1.client.js
const http = require('http');

let config ={
port: 3000,
hostname: 'localhost',
headers:{// 请求头 规范
'Content-Type': 'application/x-www-form-urlencoded', // 表单格式
// 'Content-Type': 'application/json' json类型
},
method: 'POST'
}
// 请求后 会将响应的结果放到函数中
let client = http.request(config, function(res) {
res.on('data', function(chunk){
console.log('接收到服务端返回的数据:', chunk.toString());
})
})
client.end('a=2'); // 写响应体 ajax的data数据
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
// 1.server.js
const http = require('http');
const url = require('url');
const querystring = require('querystring'); // 查询字符串
let server = http.createServer();

// 监听请求事件
server.on('request', (req,res)=>{
let {pathname, query} = url.parse(req.url, true);
let arr = [];
req.on('data', function(chunk) {
arr.push(chunk);
});
req.on('end', function() {
res.statusCode = 404;
res.setHeader('Content-Type','text/plain;charset=utf-8');
let content = Buffer.concat(arr).toString();
let type = req.headers['content-type']
if(type === 'application/json'){
let obj = JSON.parse(content)
return res.end(obj.a+''); // 为了保证是个字符串加''
}else if(type === 'application/x-www-form-urlencoded'){
// 第二个参数 字段间的分隔符 第三个参数 是key value分隔符
let obj = querystring.parse(content,'&', '='); // 默认可以不传
return res.end(obj.a+'');

// let str = 'a=1&b=2'; //如果是这样的字符串,也可以用下面的正则取到
// str.replace(/([^=&])=([^&=])/g,function(){
// console.log(arguments[1],arguments[2])
// })
}else{ // 如果不支持的就默认返回
return res.end(content);
}
});
})

let port = 3000;
server.listen(port, ()=>{
console.log('server start ' + port);
});
server.on('error', (err) => {
// console.log(err);
if(err.errno === 'EADDRINUSE'){
server.listen( ++port);
}
})

表单格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- test.form.html -->
<body>
<form action="http://localhost:3000" method="POST" enctype="application/x-www-form-urlencoded">
<input type="text" name="username">
<input type="text" name="password">
<button>提交</button>
</form>

<!--
form没有跨域问题 action可以直接跳转 method只有post、get
用VS安装的插件 Open with Live Server运行该文件
-->
localhost:3000 服务器返回 username=123&password=456 格式
</body>
<!-- 步骤:输入框输入123 456 点击提交,跳转到localhost:3000 页面上打印出 username=123&password=456 -->

上面讲的都是怎么去通信,但实际上不可能你发送给服务器什么,服务器就返还给你
客户端访问服务器有2种情况 1、请求别人的服务器 自己写个文件去访问别人服务器;2、我去对服务器要资源(静态资源)

node 适合 I/O 密集 像web 的网站,主要是 想要html服务器返回给你html,想要js,服务器返回给你js,它主要的操作就是I/O,所以这样的功能,node的性能比较高
别人客户端访问我 我服务器可以返回什么:

1) 服务器可以返回静态资源 3.static.js
2) 我的服务器可以返回动态的资源 接口
3) 访问别人的服务器 把别人的服务的结果格式化后返回去 这就是所谓的中间层 或者爬虫

静态资源

新建 index.html 、style.css、3.static.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<link rel="stylesheet" href="/style.css">
hello
</body>
</html>
1
2
3
4
// style.css
body{
background: red
}
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
// 3.static.js
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');

let server = http.createServer((req,res)=>{
// fs操作 绝对路径
// localhost:3000/index.html
let {pathname} = url.parse(req.url); // /index.html
// 上面的pathname有/ 所以这里绝对路径用join
let filePath = path.join(__dirname, pathname);
// /Users/jiafei/Desktop/Architecture-Course/16.http/index.html
// 判断文件是否存在 existsSync access stat
fs.stat(filePath, (err, statObj)=>{
if(err){
res.statusCode = 404;
return res.end('Not Found');
}else{
if (statObj.isDirectory()) {
// 如果是文件夹 需要读取文件中的index.html
filePath = path.join(filePath, "index.html");
fs.access(filePath, function(err) {
if (err) {
res.statusCode = 404;
res.end(`Not Found`);
} else {
// readFile writeFile pipe
res.setHeader("Content-Type", "text/html;charset=utf-8");
fs.createReadStream(filePath).pipe(res);
// 可读流 可写流
}
});
} else {
// 如果是文件读取出来写回去就ok
let t = require("mime").getType(filePath);
res.setHeader("Content-Type", t + ";charset=utf-8");
fs.createReadStream(filePath).pipe(res);
}
}
})
console.log(filePath);
});
server.listen(3000);

分析:浏览器 访问index.html(html加载css) -> style.css,客户端访问服务器 想要html 服务器就把html读出来,返回去,fs的读取操作。
服务端 nodemon 3.static.js
客户端:浏览器 http://localhost:3000/index.html

优化 4.static.js

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 url = require('url');
const path = require('path');
const fs = require('fs').promises;
const { createReadStream } = require("fs");
const mime = require("mime");

// 提供一个静态服务
class HttpServer{
async handleRequest(req,res){ // 处理请求
// this => HttpServer 指向当前类的实例
// console.log(this);
let { pathname, query } = url.parse(req.url, true);
let filePath = path.join(__dirname, pathname);

try {
let statObj = await fs.stat(filePath); // 文件是否存在
this.sendFile(statObj, filePath, req, res); // 就把文件发回去
} catch (e) {
this.sendError(e, res);
}
}
async sendFile(statObj, filePath, req, res) { // 发送文件需要判断是不是目录
if (statObj.isDirectory()) {
filePath = path.join(filePath, "index.html"); // 增加index.html
try {
await fs.access(filePath); // 如果有就继续
} catch (e) {
return this.sendError(e, res); // 如果没有 就直接报错
}
}
// 返回文件的逻辑
res.setHeader("Content-Type", mime.getType(filePath) + ";charset=utf-8");
createReadStream(filePath).pipe(res); // res.write() res.end()
}
sendError(e,res) { // 发送错误
console.log(e);
res.statusCode = 404;
res.end(`Not Found`);
}
// 开启一个服务
start(...args){
// 1) 保证this指向正确 this.handleRequest.bind(this)
let server = http.createServer(this.handleRequest.bind(this));
server.listen(...args);
}
}

let hs = new HttpServer();
hs.start(3000, ()=>{
console.log('server start~~');
})