原文来自 我的个人博客
1. 创建服务器
什么是 Web
服务器?
- 当应用程序(客户端)需要某个资源时,可以向一台服务器,通过
HTTP
请求获取到这个资源 - 提供资源的这个服务器,就是一个
Web
服务器
目前有很多开源的 Web
服务器:Nginx
、Apache(静态)
、Apache Tomcat(静态、动态)
、Node.js
在 Node
中,提供 web
服务器的资源返回给浏览器,主要是通过 http
模块。
我们先简单对它做一个使用:
const http = require("http");
const HTTP_PORT = 9000;
const server = http.createServer((req, res) => {
res.end("Hello World");
});
server.listen(HTTP_PORT, () => {
console.log(`服务器在${HTTP_PORT}启动`);
});
此时我们在浏览器中输入 localhost:9000
,就会出现 Hello World
:
解释上面这段代码:
通过
http
模块的createServer
方法创建了一个服务器对象,它的底层其实是直接使用new Server
创建对象的。那么当然,我们也可以自己来创建这个对象:
const server = new http.Server((req, res) => { res.end("Hello World"); }); server.listen(HTTP_PORT, () => { console.log(`服务器在${HTTP_PORT}启动`); });
创建
Server
时会传入一个回调函数,这个回调函数在被调用时会传入两个参数:req
:request
请求对象,包含请求相关的信息;res
:response
响应对象,包含我们要发送给客户端的信息;
Server
通过listen
方法来开启服务器,并且在某一个主机的端口上监听网络请求也就是当我们通过
ip:port
的方式发送到我们监听的Web
服务器上时,我们就可以对其进行相关的处理;listen
函数有三个参数:- 端口
port
:可以不传,系统会默认分配端 主机
host
:通常可以传入localhost
、ip地址127.0.0.1
、或者ip地址0.0.0.0
,默认是0.0.0.0
;localhost
:本质上是一个域名,通常情况下会被解析成127.0.0.1
;127.0.0.1
:回环地址(Loop Back Address)
,表达的意思其实是我们主机自己发出去的包,直接被自己接收;- 正常的数据包会经过 应用层 - 传输层 - 网络层 - 数据链路层 - 物理层 ;
- 而回环地址,是在网络层直接就被获取到了,是不会经常数据链路层和物理层的;
- 比如我们监听
127.0.0.1
时,在同一个网段下的主机中,通过ip地址
是不能访问的;
0.0.0.0
:- 监听
IPV4
上所有的地址,再根据端口找到不同的应用程序; - 比如我们监听
0.0.0.0
时,在同一个网段下的主机中,通过ip
地址是可以访问的;
- 监听
- 回调函数:服务器启动成功时的回调函数;
- 端口
2. Request 请求
2.1 Request 对象
在向服务器发送请求时,我们会携带很多信息,比如:
- 本次请求的
URL
,服务器需要根据不同的URL
进行不同的处理; - 本次请求的请求方式,比如
GET
、POST
请求传入的参数和处理的方式是不同的; - 本次请求的
headers
中也会携带一些信息,比如客户端信息、接收数据的格式、支持的编码格式等; - 等等...
这些信息,Node
会帮助我们封装到一个 request
的对象中,我们可以直接来处理这个 request
对象:
const server = new http.Server((req, res) => {
// request 对象
console.log(req)
console.log(req.method)
console.log(req.headers)
res.end("Hello World");
});
2.2 URL 的处理
客户端在发送请求时,会请求不同的数据,那么会传入不同的请求地址:
- 比如
http://localhost:9000/login
; - 比如
http://localhost:9000/products
;
服务器端需要根据不同的请求地址,作出不同的响应:
const server = new http.Server((req, res) => {
const url = req.url;
if (url === "/login") {
res.end("welcome Back~");
} else if (url === "/products") {
res.end("products");
} else {
res.end("error message");
}
});
2.3 URL 的解析
如果用户发送的地址中还携带一些额外的参数,例如以下情况
http://localhost:9000/login?name=why&password=123
;- 这个时候,
url
的值是/login?name=why&password=123
;
这个时候我们可以使用内置模块 url
处理
const parseInfo = url.parse(req.url);
console.log(parseInfo)
此时的 parseInfo
为:
接下来我们就可以直接用 qs
或者 URLSearchParams
处理 query
拿到参数了
const queryObj = new URLSearchParams(parseInfo.query);
console.log(queryObj.get("name"));
console.log(queryObj.get("password"));
2.4 请求头
在 request
对象的 header
中也包含很多有用的信息,客户端会默认传递过来一些信息:
content-type
是这次请求携带的数据的类型:application/x-www-form-urlencoded
:表示数据被编码成以'&'
分隔的键-
值对,同时以'='
分隔键和值application/json
:表示是一个json
类型;text/plain
:表示是文本类型application/xml
:表示是xml
类型;multipart/form-data
:表示是上传文件;
content-length
:文件的大小长度keep-alive
:http
是基于TCP
协议的,但是通常在进行一次请求和响应结束后会立刻中断;在
http1.0
中,如果想要继续保持连接:- 浏览器需要在请求头中添加
connection: keep-alive
; - 服务器需要在响应头中添加
connection: keep-alive
; - 当客户端再次放请求时,就会使用同一个连接,直接一方中断连接;
- 浏览器需要在请求头中添加
在
http1.1
中,所有连接默认是connection: keep-alive
的;- 不同的
Web
服务器会有不同的保持keep-alive
的时间; Node
中默认是5s
中;
- 不同的
accept-encoding
:告知服务器,客户端支持的文件压缩格式,比如js
文件可以使用gzip
编码,对应.gz
文件;accept
:告知服务器,客户端可接受文件的格式类型;user-agent
:客户端相关的信息;
3. Response 响应
3.1 返回响应结果
如果我们希望给客户端响应的结果数据,可以通过两种方式:
write方法
:这种方式是直接写出数据,但是并没有关闭流;end方法
:这种方式是写出最后的数据,并且写出后会关闭流;
// 响应数据的两个方式
res.write('Hello World)
res.write("Hello Response")
res.edn("message end")
如果我们没有调用 end
和 close
,客户端将会一直等待结果:
- 所以客户端在发送网络请求时,都会设置超时时间。
3.2 返回状态码
Http状态码(Http Status Code)
是用来表示 Http
响应状态的数字代码:
Http状态码
非常多,可以根据不同的情况,给客户端返回不同的状态码;MDN
响应码解析地址:https://developer.mozilla.org/zh-CN/docs/web/http/status
常见HTTP状态码 | 状态描述 | 信息说明 |
---|---|---|
200 | OK | 客户端请求成功 |
201 | Created | POST请求,创建新的资源 |
301 | Moved Permanently | 请求资源的URL已经修改,响应中会给出新的URL |
400 | Bad Request | 客户端的错误,服务器无法或者不进行处理 |
401 | Unauthorized | 未授权的错误,必须携带请求的身份信息 |
403 | Forbidden | 客户端没有权限访问,被拒接 |
404 | Not Found | 服务器找不到请求的资源 |
500 | Internal Server Error | 服务器遇到了不知道如何处理的情况。 |
503 | Service Unavailable | 服务器不可用,可能处于维护或者重载状态,暂时无法访问 |
// 1.
res.statusCode = 400
// 2.
res.writeHead(200)
3.3 响应头文件
返回头部信息,主要有两种方式:
res.setHeader
:一次写入一个头部信息;res.writeHead
:同时写入header
和status
;
res.setHeader('Context-Type', 'application/json;charset=utf8')
res.writeHead(200, {
"Content-Type": "application/json;charset=utf8"
})
Header
设置 Content-Type
有什么作用呢?
- 默认客户端接收到的是字符串,客户端会按照自己默认的方式进行处理;
4. 文件上传
其实对于后端,手动处理上传的文件是很复杂的,正常情况下我们都会借助于一些插件做处理,
下面仅做对于图片上传的一个简单的演示。
const http = require("http");
const qs = require("querystring");
const fs = require("fs");
const HTTP_PORT = 9000;
// 1. 创建 sever 服务器
const server = new http.Server((req, res) => {
// 文件设置为二进制
req.setEncoding("binary");
// 获取 content-type 中的 boundary的值
let boundary = req.headers["content-type"]
.split("; ")[1]
.replace("boundary=", "");
const fileSize = req.headers["content-length"];
let curSize = 0;
let body = "";
req.on("data", (data) => {
curSize += data.length;
res.write(`文件上传进度:${(curSize / fileSize) * 100}%\n`);
body += data;
});
req.on("end", () => {
// 切割数据
const payload = qs.parse(body, "\r\n", ":");
// 获取最后的类型(image/png)
const fileType = payload["Content-Type"].substring(1);
// 获取要截取的长度
const fileTypePosition = body.indexOf(fileType) + fileType.length;
let binaryData = body.substring(fileTypePosition);
binaryData = binaryData.replace(/^\s\s*/, "");
const finalData = binaryData.substring(
0,
binaryData.indexOf("--" + boundary + "--")
);
fs.writeFile("./foo.png", finalData, "binary", (err) => {
console.log(err);
res.end("文件上传完成~");
});
});
});
// 2. 开启 server 服务器
server.listen(HTTP_PORT, () => {
console.log(`服务器在${HTTP_PORT}启动`);
});
使用 postman
测试,并查看图片能显示。