HTTP 协议
HTTP(hypertext transport protocol)协议;中文叫 超文本传输协议,是一种基于TCP/IP的应用层通信协议吗,这个协议详细规定了 浏览器
和 万维网 服务器
之间互相通信的规则协议中主要规定了两个方面的内容:
- 客户端:用来向服务器发送数据,可以被称之为 请求报文
- 服务端:向客户端返回数据,可以被称之为 响应报文
请求报文
POST https://comment.api.163.com/api/v1/products/a2869674571f77b5a0867c3d71db5856/threads/I5TOD9K40001899O/comments?ibc=newspc&_=1685348594866 HTTP/1.1
Host: comment.api.163.com
Connection: keep-alive
Content-Length: 342
sec-ch-ua: "Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"
Accept: application/json, text/plain, */*
Content-Type: application/x-www-form-urlencoded
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: https://comment.tie.163.com
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://comment.tie.163.com/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7,en-US;q=0.6
Cookie: nts_mail_user=fmuncle@163.com:-1:1; _n
content=%E7%89%9B%E9%80%BC%EF%BC%81%E8%B5%9E%EF%BC%81%E5%8E%89%E5%AE%B3%EF%BC%81&originalContent=牛逼!赞!厉害!&ntoken=2e4db38d-2dd8-4dc2-97f5-dd39e528794b&token=9ca17ae2e6ffcda170e2e6eeacf33af8acb7a9b63f91868aa2c54b878b9e86c5649cb5a1d1c83df38ebf8df52af0feaec3b92a869e97d9cc7ab89ca198e65b928f9fa6c55f909e00a8ea33ace7c087d972afb9ee9e
请求报文四部分组成: 请求行、请求头、空行、请求体
① 请求行
请求方式: 包括 GET、POST、PUT 方式等,更多 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods
URL: 统一资源定位符,确定具体请求的资源
协议版本: http1.1
② 请求头
请求头是键值对结构,用于标记客户端相关的信息,更多请求头:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers
③ 空行
用于分隔请求头和请求体
④ 请求体
向后端发送的数据,请求体可以为空
响应报文
HTTP/1.1 200 OK
Server: Tengine
Content-Type: text/html; charset=utf-8
Content-Length: 330076
Connection: keep-alive
Date: Mon, 29 May 2023 07:45:58 GMT
Last-Modified: Mon, 29 May 2023 07:45:01 GMT
Vary: Accept-Encoding
Expires: Mon, 29 May 2023 07:46:28 GMT
Cache-Control: no-cache,no-store,private
P3P: CP=CAO PSA OUR
Ali-Swift-Global-Savetime: 1685346358
Via: cache79.l2cn3036[47,47,200-0,M], cache24.l2cn3036[49,0], vcache1.cn4730[0,0,200-0,H], vcache1.cn4730[2,0]
Age: 29
X-Cache: HIT TCP_MEM_HIT dirn:10:364746730
X-Swift-SaveTime: Mon, 29 May 2023 07:45:58 GMT
X-Swift-CacheTime: 30
cdn-src-ip: 116.238.99.140
X-Cache-Remote: HIT
cdn-ip: 58.215.47.197
cdn-source: ali
cdn-user-ip: 116.238.99.140
x-server-ip: 58.215.47.197
Timing-Allow-Origin: *
EagleId: 3ad72f1516853463874755901e
响应体...
响应报文由四部分组成: 响应行、响应头、空行、响应体
① 响应行
协议版本: HTTP/1,1
响应状态码: 200,标记响应状态,更多地响应状态码:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
响应状态描述: 与响应状态码对应
② 响应头
键值对结构,标识服务端相关信息
**更多响应头:**https://developer.mozilla.org/zh-CN/docs/Glossary/Response_header
③ 空行
分隔响应头和响应体
④ 响应体
服务器向客户端发送的数据都在响应体中,如 html文件的内容、css文件的内容、js文件的内容等
URL
统一资源定位系统(uniform resource locator;URL)是因特网的万维网服务程序上用于指定信息位置的表示方法。
http://www.baidu.com:8080/home/msg/data/personalcontent?num=8&indextype=manht#logo
完整 URL 的组成部分:
- 协议 ,如 https、http。
- 主机名,一般使用 IP 地址或域名。
- 端口号 ,HTTP 的端口号为 80,HTTPS 的为 443
- 路径,上面 URL 中的路径部分为:
/home/msg/data/personalcontent
。 - 查询字符串,上面 URL 中的路径部分为:
num=8&indextype=manht
。 - 锚点,上面 URL 中的路径部分为:
#logo
HTTP 响应状态码
状态码由三位数字组成,第一位数字表示响应的类型,常用的状态码有五大类如下所示:
1xx
:指示信息--表示请求已接收,继续处理。2xx
:成功--表示请求已被成功接收、理解、接受。3xx
:重定向--要完成请求必须进行更进一步的操作。4xx
:客户端错误--请求有语法错误或请求无法实现。5xx
:服务器端错误--服务器未能实现合法的请求。
常见状态代码、状态描述的说明如下。
200 OK:客户端请求成功。
400 Bad Request:客户端请求有语法错误,不能被服务器所理解。
401 Unauthorized:请求未经授权
403 Forbidden:服务器收到请求,但是拒绝提供服务。
404 Not Found:请求资源不存在,举个例子:输入了错误的URL。
500 Internal Server Error:服务器发生不可预期的错误。
503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常
创建 http 服务
创建服务
写一个程序,该程序可以接收到客户端浏览器的请求,并能为客户端浏览器做出响应; 该程序是后端程序,运行在服务器上,需要 node 的支持。
// 导入模块
const http = require('http');
/*
1. 创建 http 服务的方法的参数是个回调函数
2. 回调函数在接收到请求的时候自动执行
3. 回调函数在执行的时候,接收到两个参数,分别是请求对象,响应对象
4. createServer 方法返回一个对象
*/
const server = http.createServer((req, res) => {
console.log('我接收到了一个请求! 客户端IP:', req.socket.remoteAddress);
// 设置响应
res.end('<h1>Welcome to My WebSite</h1>');
});
// 启动服务 给http服务对象监听端口, 服务启动成功,回调函数就执行
// 端口号如果是 80,浏览器中的地址可以省略端口号
server.listen(8080, () => {
console.log('http server is running on 8080');
});
// 第二个参数可以设置 ip
// server.listen(8080, '127.0.0.1', () => {
// console.log('http server is running on 8080');
// });
/*
注意:
1. 修改代码之后,要重新启动服务,先 ctrl+c 结束,再重新运行
2. 如果端口号被占用,可以换一个端口或者关闭占用端口的进程
*/
1. http 对象 ==> 导入的 http 内置模块
2. http.Server 对象 ==> http.createServer() 的返回值
3. http.clientRequest 对象 ==> http.createServer() 的回调函数的第一个参数
4. http.serverResponse 对象 ==> http.createServer() 的回调函数的第二个参数
注意:如果启动服务的时候,报错提示端口被占用,可以采用下面两种方案来解决:
1)给我们的程序换个端口
2)把占用端口的其他程序关闭
- windows cmd 命令行中运行命令
netstat -ano | findstr 端口号
来获取占用端口的程序的进程ID - 资源管理器->详细信息,根据进程ID找到程序,右键选择结束任务。
获取请求报文信息
获取请求行的信息
请求对象.method
请求对象.url 获取到 url中的 pathname 部分和查询字符串部分,不包括协议、主机名、端口号、锚点
请求对象.httpVersion
request.httpVersion; // 获取 http 版本
request.url; // 获取请求的 url 地址
request.method; // 获取请求方式(请求方法)
获取请求头信息
请求对象.headers
request.headers; // 返回对象,包含请求报文中所有的请求头信息
request.headers.请求头名字;
获取客户端 IP 地址
请求对象.socket.remoteAddress
获取 URL 中的查询字符串
// 第一种方式 解析url
const url = require('url');
parse方法的第二个参数 设置为true ,得到的对象中的query属性是一个对象属性
const urlInfo = url.parse(req.url, true);
console.log(urlInfo.query);
------------------------------------------
const url = requeire('url');
url.parse(request.url, true).query; // 返回一个对象
// 第二种方式 解析 url
const {URL} = require('url');
// 需要手动拼接成完整的url,否则会报错
const urlInfo = new URL('http://127.0.0.1/' + req.url);
// 使用得到的对象中的searchParams属性,searchParams属性本身是一种类似MAP结构的对象
console.log(urlInfo.searchParams); // 得到一个类似MAP结构的对象
// 使用 get 方法获取相应的信息
console.log(urlInfo.searchParams.get('a'));
console.log(urlInfo.searchParams.get('b'));
get、post、put、delete 等所有的请求方式,都可以在url中拼接查询字符串!
获取请求体信息
// 给请求对象监听 data 事件 请求对象本质上就是以读取流
req.on('data', chunk => {
// += 会让 buffer 自动转为 string
reqBody += chunk;
});
// 给请求对象监听 end 事件, 读取完毕触发该事件
req.on('end', () => {
reqBody; // 是查询字符串格式,可以使用 querystring 模块处理成对象
});
只有 post、put 等请求方式才能有请求体,get 、delete 等方式没有请求体!
form 表单可以通过设置 method属性为 post 让表单通过 post方式提交数据,而不是默认查找字符床get方式
// request 本质上是一个可读流对象
// 定义变量 将请求体中读取的内容拼接到该变量
let reqBody = '';
// 分次从请求体中读取数据
req.on('data', chunk => {
reqBody += chunk;
});
// 请求体内容读取结束
req.on('end', () => {
reqBody; // 查询字符串
querystring.parse(reqBody); // 解析为对象
});
设置响应报文
设置响应行
响应对象.statusCode = 响应状态码
响应对象.statusMessage = 响应状态描述
------------------------------
response.statusCode = 200; // 设置响应状态码
response.statusMessage = 'OK'; // 设置响应状态描述
设置响应头
响应对象.setHeader('键', '值');
--------------------------
response.setHeader('响应头名字', '响应头内容')
// 同时设置 响应状态码 响应状态描述 响应头
响应对象.writeHead(响应状态码, '响应状态描述', {
'键':'值',
'键':'值',
'键':'值',
})
=--------------------------------
// 同时设置 响应状态码、设置响应状态描述、响应头
response.writeHead(响应状态码, '响应状态描述', {
'响应头名字' :'响应头内容',
'响应头名字' :'响应头内容',
'响应头名字' :'响应头内容'
...
})
设置响应体
// resposne 本质上是一个可写流对象 可以通过 write 将内容写入流
response.write('内容');
response.write('内容');
response.write('内容');
response.write('内容');
结束响应
// 结束响应
响应对象.end();
// 向响应体写入内容并结束响应
响应对象.end('<hr><h2>结束<h2>');
---------------------
// 只用来结束响应
resposne.end();
// 设置响应体并结束响应
response.end('响应体内容');
http 服务案例
根据路径不同做出不同响应
/*
http://127.0.0.1:8080/ 首页
http://127.0.0.1:8080/login 登录页
http://127.0.0.1:8080/register 注册页
其他 404
*/
// 导入模块
const http = require('http');
const url = require('url');
// 创建服务
const server = http.createServer((req, res) => {
// 获取到 url 中的路径名部分
const pathname = url.parse(req.url).pathname;
// 根据路径名不同做出不同的响应
switch (pathname) {
case '/':
case '/index':
res.setHeader('Content-type', 'text/html;charset=utf-8');
res.end('<h1>首页</h1><hr><a href="/login">登录</a> <a href="/register">注册</a>');
break;
case '/login':
res.setHeader('Content-type', 'text/html;charset=utf-8');
res.end('<h1>登录</h1>');
break;
case '/register':
res.setHeader('Content-type', 'text/html;charset=utf-8');
res.end('<h1>注册</h1>');
break;
default:
res.writeHead(404, 'Not Found', {
'Content-type': 'text/html;charset=utf-8'
});
res.end('<h1>404 您访问的页面不存在的!</h1><a href="/">返回首页</a>');
}
});
// 启动服务
server.listen(8080, () => {
console.log('http server is runing on 8080');
});
根据请求方式不同做出不同的响应
/*
所有请求方式 / 或者 /index 响应首页
get方式 /login 加载登录页面
post方式 /login 执行登录
其他路径 404
*/
// 导入模块
const http = require('http');
const url = require('url');
const fs = require('fs');
const path = require('path');
const qs = require('querystring');
// 创建服务
const server = http.createServer((req, res) => {
// 获取到 url 中的路径名部分
const pathname = url.parse(req.url).pathname;
// 根据路径和请求方式判断 做出不同的响应
if (pathname === '/' || pathname === '/index') {
const resBody = `
<h1>首页</h1>
<hr>
<a href="/login">登录</a>
`;
res.writeHead(200, 'OK', {
'Content-type': 'text/html;charset=utf-8'
});
res.end(resBody);
} else if (pathname === '/login' && req.method === 'GET') {
// 读取文件 login.html
fs.readFile(path.resolve(__dirname, './login.html'), (err,data) => {
if (err) {
res.writeHead(500, 'Internal Server Error', {
'Content-type': 'text/html;charset=utf-8'
});
res.end('<h1>500 服务器错误!</h1>');
} else {
res.end(data);
}
});
} else if (pathname === '/login' && req.method === 'POST') {
// 接收表单提交的数据
let reqBody = '';
req.on('data', chunk => {
reqBody += chunk;
});
// 读取请求体完毕
req.on('end', () => {
// 解析请求体
const body = qs.parse(reqBody);
// 定义响应内容
let resBody = '';
// 执行模拟登录
if (body.username === 'admin' && body.pwd === '123456') {
// 登录成功
resBody = '<p>登录成功! <a href="/">返回首页</a></p>';
} else {
// 登录失败
resBody = '<p>登录失败! <a href="/login">重新登录</a></p>';
}
// 作出响应
res.writeHead(200, 'OK', {
'Content-type': 'text/html;charset=utf-8'
});
res.end(resBody);
});
} else {
res.writeHead(404, 'Not Found', {
'Content-type': 'text/html;charset=utf-8'
});
res.end('<h1>404 页面不存在!</h1>');
}
});
// 启动服务
server.listen(8080, () => {
console.log('http server is runing on 8080');
});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录</title>
<link href="http://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<style>
.form-box {
margin-top: 40px;
}
</style>
</head>
<body>
<div class="form-box">
<div class="row">
<div class="col-sm-4 col-sm-offset-4">
<form method="post" action="/login">
<div class="form-group">
<label for="example01">用户</label>
<input name="username" type="text" class="form-control" id="example01" required>
</div>
<div class="form-group">
<label for="example02">密码:</label>
<input name="pwd" type="password" class="form-control" id="example02" required>
</div>
<button type="submit" class="btn btn-primary btn-block">登录</button>
</form>
</div>
</div>
</div>
</body>
</html>
http 服务
1. 创建服务和启动服务
2. 从请求报文中获取数据
3. 设置响应报文
GET 方式和 POST方式的区别:
1. get 方式一般用于从服务器获取数据; post 方式一般用于像服务器提交数据
2. get 方式的请求报文没有请求体; post 方式的请求报文中有请求体
3. post 方式更安全, get 方式通过url查询字符串向服务器传递数据,完全暴露; post 方式通过请求体传递数据,默认不可见的
4. get 方式传递数据受制于url的长度, post 方式传递数据理论上没有容量限制
实现静态资源托管服务
1. 根据url中的pathname,拼接静态资源文件在服务器上的路径
2. 读取静态资源文件,将文件内容作为响应体
如果静态资源文件不存在,响应 404; 如果文件读取失败,响应 500
3. 浏览器会对中文路径编码,后端程序需要使用 decodeURI() 解码
4. 响应头中设置 Content-type 指定静态资源文件的 MIME 类型
// 导入模块
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mimes = require('./mimes/mimes.json');
// 创建服务
const server = http.createServer((req, res) => {
// 从url中获取路径名
const pathname = url.parse(req.url).pathname;
// 获取到文件的扩展名
const extname = pathname.slice(pathname.lastIndexOf('.')+1);
// 根据 pathnaem 定义要请求的静态资源文件的路径
let filename = path.join(__dirname, 'public', pathname);
// 可能存在中文路径,浏览器会自动编码,需要进行解码
filename = decodeURI(filename);
// 判断要请求的静态资源文件是否存在
fs.access(filename, err => {
if (err) {
res.writeHead(404,'Not Found', {
'Content-type': 'text/html;charset=utf-8'
});
res.end('<h1>404 您访问的页面不存在!</h1>');
} else {
// 读取文件的内容 响应给客户端浏览器
fs.readFile(filename, (err,data) => {
if (err) {
res.writeHead(500,'Internal Server Error', {
'Content-type': 'text/html;charset=utf-8'
});
res.end('<h1>500 服务器发生未知错误! </h1>');
} else {
res.setHeader('Content-type', mimes[extname] || '')
res.end(data);
}
})
}
});
});
// 启动服务
server.listen(8080, () => {
console.log('http server is running on 8080');
});
MIME 文件类型
媒体类型(通常称为 Multipurpose Internet Mail Extensions 或 MIME 类型)是一种标准,用来表示文档、文件或字节流的性质和格式 。
服务器应该在响应头中设置 Content-type 指定响应内容的 MIME 类型。
{
"css":"text/css",
"gif":"image/gif",
"html":"text/html;charset=utf-8",
"ico":"image/x-icon",
"jpeg":"image/jpeg",
"jpg":"image/jpeg",
"js":"text/javascrip;charset=utf-8",
"json":"application/json;charset=utf-8",
"pdf":"application/pdf",
"png":"image/png",
"svg":"image/svg+xml",
"swf":"application/x-shockwave-flash",
"tiff":"image/tiff",
"txt":"txt/plain;charset=utf-8",
"wav":"audio/x-wav",
"wma":"audio/x-ms-wma",
"wmv":"video/x-ms-wmv",
"mp4":"video/mp4",
"mp3":"video/mpeg",
"xml":"text/xml"
}