http模块
http协议:超文本传输协议(互联网应用最广泛的协议之一)
http请求方法:
- get获取数据
- post新增数据
- put/patch更新数据
- delete删除数据
- head/options/connect/trace
URL统一资源定位符,其本身也是一个字符串。
客户端与服务
在网络节点中,负责消费资源的电脑,叫做客户端。
负责对外提供网络资源的电脑,叫做服务器。
http模块是node.js官方提供的、用来创建web服务器的模块。通过http模块提供的http.createServer()方法,就能方便的把一台普通电脑变成一台web服务器,从而对外提供web资源服务。
使用http模块创建web服务器,需要先导入
const http=require('http')
服务器和普通电脑的区别就是服务器上安装了web服务器软件,例如IIS,Apache等,在Node.js中不需要使用第三方web服务器软件,可以基于Node.js提供的http模块,简单代码手写一个服务器软件,对外提供web服务。
服务器相关概念
ip地址
ip地址是互联网上每台计算机的唯一地址,ip地址具有唯一性。
ip地址的格式通常是“点分十进制”,表示成(a,b,c,d)的形式,其中a,b,c,d都是0~255之间的十进制整数。eg:(192.168.1.1)
互联网中的每台web服务器都有自己的ip的地址,可以在Windows终端运行ping www.baidu.com命令,即可查看百度服务器的ip地址。
域名和域名服务器
ip地址不直观不便于记忆,于是有字符型的地址方案-域名地址
ip地址和域名是一一对应的关系,这种对应关系放在一种叫做域名服务器的电脑中。使用者只需要通过域名访问对应服务器即可,对应的转换工作由域名服务器实现。
因此,域名服务器就是提供ip地址和域名之间的转换服务的服务器。
注意:开发测试期间,127.0.0.1对应的域名是localhost,他们都代表我们自己的这台电脑,在使用效果上没有任何区别。
端口号
一台电脑可以运行成百上千个web服务,每个web服务都对应一个唯一的端口号,客户端发送过来的网络请求,通过端口号,可以被准确的交给对应的web服务进行处理。
注意:
每个端口号不能同时被多个web服务占用。
在实际应用中,url中的80端口可以被省略。
localhost:80可以直接访问localhost
网页中URL的使用
URL可以分为相对路径和绝对路径
相对路径:相对于当前文件位置的路径
绝对路径:目标文件在硬盘上的真实路径(最精确路径)
html中link引入文件的最好的一种方式应该是绝对路径
<link rel="stylesheet" href="/index.css">
绝对路径
绝对路径靠谱性强,相对容易理解在项目中运用比较多
https://xxx.com/web直接向目标资源发送请求,容易理解。网站的外链会用此形式
//xx.com/web与页面URL的协议拼接行测好难过完整的URL再发送请求。大型网站使用较多。
/web与页面的协议主机名,端口拼接形成完整的URL再发送请求。中小型网站。
相对路径
比如http://www.xxx.com/about/h5.html
相对路径是在发送请求时,与当前页面URL路径进行计算,得到完整的URL后,再发送请求,学习阶段用的较多。
网页中URL使用场景总结
包括但不限于以下场景
- a标签href
- link标签href
- script标签href
- img标签src
- video audio标签的src
- form中的action
- AJAX请求中的URL
浏览器查看HTTP报文
可以使用一个表单提交的demo做相关测试
<form action="http://127.0.0.1:9000" method="post">
<input type="text" name="username">
<input type="text" name="password">
<input type="submit" value="提交">
</form>
在浏览器打开对应的html文件(注意在此之前运行http服务端,监听端口开启服务),输入表单信息提交,在F12控制台可以查看网络
获取请求行和请求头
想要获取请求的数据,需要通过request对象
- request.method请求方法
- request.httpVersion请求版本
- request.url请求路径
- require(‘url’).parse(request.url).pathname URL路径
- require(‘url’).parse(request.url,true).query url查询字符串
- request.headers请求头
- request.on(‘data’,function(chunk){});request.on(‘end’,function(){});
注意事项
- request.url只能获取路径以及查询字符串,无法获取URL中的域名以及协议的内容
- request.headers将请求信息转化为一个对象,并将属性名都转化成了小写。
- 关于路径:如果访问网站的时候,只填写可ip地址或者域名信息,此时的请求路径是/
- favicon.ico:浏览器自动发送的请求
创建最基本的web服务器
基本步骤
- 导入http模块
const http=require('http')
- 创建web服务器实例
调用http.createServer()方法快速创建一个web服务器实例
const server=http.createServer()
- 为服务器实例绑定request事件,监听客户端请求
// 使用服务器实例的.on()方法,为服务器绑定一个request事件
server.on('request', (req, res) => {
// 只要有客户端来请求我们自己的服务器,就会触发request事件,从而调用这个事件处理函数
console.log('Hello HTTP Server')
})
req是一个对请求报文的封装对象,可以获取请求报文的相关内容
res是一个对响应报文的封装对象,可以获取响应报文的相关内容
- 启动服务器
调用服务器的.listen()方法,即可启动当前的web服务器实例
// 调用服务器实例的.listen(端口号,cb回调)方法,即可启动当前的web服务器实例
server.listen(9000, () => {
console.log('服务已经启动')
})
可以在powdershell执行node命令,
vscode的终端新建终端也可以执行node命令,ctrl+c暂停服务器
注意事项
- 命令行ctrl+c停止服务
- 当服务启动后,更新代码必须重启服务才能生效
- 响应内容中文乱码的解决办法
response.setHeader('Content-type', 'text/html;charset=utf-8');
-
端口号被占用
Error: listen EADDRINUSE: address already in use :::9000
(1)关闭当前正在运行监听端口的服务
(2)修改其他端口号 -
HTTP协议默认端口是80,HTTP服务开发常用端口有3000,8080,8090,9000等
如果端口被其他程序占用,可以使用资源监视器找到占用端口的程序,然后使用任务管理器关闭对应的程序。
桌面底部任务栏右键任务管理器,点击性能,窗口底部就有资源监视器
根据资源监视器找到占用端口的进程的pid,在资源管理器根据pid找到进程右键结束任务,端口就可以用了。另外如果pid没有显示pid,在名称那里右键选择pid就显示了如图
-
HTTPS协议默认端口是443
req请求对象
只要服务器接收到了客户端的请求,就会调用通过server.on()为服务器绑定的request事件处理函数。
如果想在事件处理函数中,访问与客户端相关的数据或属性,可以使用如下方法:
server.on('request', (req) => {
//req是请求对象,它包含了与客户端相关的数据和属性,例如:
// req.url是客户端请求的URL地址
// req.method是客户端的method请求类型
const str = 'Your request url is ${req.url}, and request method is ${req.method}'
console.log(str)
})
运行的完整的代码如下
const http = require('http')
const server = http.createServer()
server.on('request', (req) => {
//req是请求对象,它包含了与客户端相关的数据和属性,例如:
// req.url是客户端请求的URL地址
// req.method是客户端的method请求类型
const str = 'Your request url is ${req.url}, and request method is ${req.method}'
console.log(str)
})
// 调用服务器实例的.listen(端口号,cb回调)方法,即可启动当前的web服务器实例
server.listen(1314, () => {
console.log('http server running at http://127.0.0.1')
})
运行80端口报错
所以换成了1314端口
运行成功
res响应对象
在服务器的request事件处理函数中,如果想访问与服务器相关的数据或属性,可以使用如下方式:
server.on('request', (req, res) => {
// res是响应对象,它包含了与服务器相关的数据和属性,例如 要发送到客户端的字符串
const str = 'Your request url is ${req.url}, and request method is ${req.method}'
// res.end()方法向客户端发送指定内容,并结束这次请求的处理过程
res.end(str)
})
完整运行的代码
// 导入http模块
const http = require('http')
// 创建web服务器实例
const server = http.createServer()
// req是请求对象,包含与客户端相关的数据和属性
server.on('request', (req,res) => {
// req.url事故客户端请求的url地址
const url = req.url
// req.method是客户端请求的method类型
const method = req.method
const str = 'Your request url is ${url}, and request method is ${method}'
console.log(str)
// 调用res.end()方法,向客户端响应一些内容
res.end(str)
})
server.listen(1314, () => {
console.log('server running at http://127.0.0.1')
})
运行结果截图
创建HTTP报文和提取请求体的代码示例
const http = require('http');
// 创建服务对象
const server = http.createServer((request, response) => {
// 获取请求的方法
console.log(request.method);
// 获取请求的url
console.log(request.url);//只包含url中的路径与查询字符串
// 获取HTTP协议的版本号
console.log(request.httpVersion)
// 获取HTTP的请求头
console.log(request.headers.host);
response.end('http');//设置响应体
});
// 监听端口,启动服务
server.listen(9000, () => {
console.log('服务已经启动')
});
在地址栏输入请求
request.url会在终端输出url路径与查询字符串
提取HTTP报文的请求体
const http = require('http');
// 创建服务对象
const server = http.createServer((request, response) => {
// 声明一个变量
let body = '';
// 绑定data事件
request.on('data', chunk => {
body += chunk;
})
// 绑定end事件
request.on('end', () => {
console.log(body);
// 响应
response.end('Hello HTTP');
});
});
// 监听端口,启动服务
server.listen(9000, () => {
console.log('服务已经启动')
});
在地址栏直接发送请求是get请求,请求体为空,服务端返回空
可以借助html的表单发送不为空的请求
提取HTTP报文中URL的路径与查询字符串
const http = require('http');
const url = require('url');
const server = http.createServer((request, response) => {
let res = url.parse(request.url, true);
console.log(res);
let pathname = res.pathname;
let keyword = res.query.keyword;
console.log(keyword);
response.end('url');
});
// 监听端口,启动服务
server.listen(9000, () => {
console.log('服务已经启动')
});
一开始没有监听端口启动服务,我还想怎么node命令之后没有任何反应呢,搞笑。。。
还有一种方式,代码如下
const http = require('http');
const { URL } = require('url');
const server = http.createServer((request, response) => {
// 实例化url的对象
// let url = new URL('http://www.xxx.com/search?a=100&b=200');
// let url = new URL('./search?a=100&b=200', 'http://127.0.0.1:9000');
let url = new URL(request.url, 'http://127.0.0.1:9000');
// 输出路径
console.log(url.pathname);
// 输出keycode查询字符串
console.log(url.searchParams.get('keyword'));
response.end('url new');
});
server.listen(9000, () => {
console.log('服务已经启动')
});
注意实例化url对象的代码。
HTTP请求练习题
按照以下要求搭建HTTP服务
请求类型get,请求地址为/login和/reg响应体结果分别是登录页面和注册页面。
核心步骤
- 获取请求的url地址
- 设置默认的相应内容为404 Not found
- 判断用户请求的是否为/login.html
- 判断用户请求的是是否是/reg.html
- 设置Content-Type响应头,防止中文乱码
- 使用res.end()把内容响应给客户端
基础的代码模板如下
// 1.导入http模块
const http = require('http');
// 2.创建服务对象
const server = http.createServer((request, Response) => {
Response.end('practise');
});
// 3.监听端口,启动服务
server.listen(9000, () => {
console.log('服务已经启动,,端口9000监听中')
})
上述代码为基础的代码模板,因为有请求就会执行回调函数,所以功能函数会放在回调函数当中,并且需要在当中判断请求方法以及是否url路径是否与要求匹配。
由于最后只能有一个end方法,所以在写完判断的end的方法之后要把原来的practise的end方法删除掉。否则会出现write after end 的报错信息。
删除后运行页面的内容出现中文乱码,这个时候只需要设置response.setHeader即可。
let method=request.method;
可以简写为
let { method } = request;
request里面有method属性,所以可以用赋值的方式去获取。[对象解构]
完整代码如下,最后的else保证了谷歌浏览器favicon.ico的响应
// 1.导入http模块
const http = require('http');
// 2.创建服务对象
const server = http.createServer((request, Response) => {
// 获取请求的方法
let { method } = request;
// 获取请求的url路径
let { pathname } = request;
// let pathname = new URL(request.url, 'http://127.0.0.1');
Response.setHeader('content-type', 'text/html;charset=utf-8');
// console.log(pathname);
// console.log(method);
// 判断
if (method === 'GET' && require('url').parse(request.url).pathname === '/login') {
// 登录的情形
Response.end('登录页面');
} else if (method === 'GET' && require('url').parse(request.url).pathname === '/reg') {
Response.end('注册页面');
} else {
Response.end('404 Not Found');
}
});
// 3.监听端口,启动服务
server.listen(9000, () => {
console.log('服务已经启动,,端口9000监听中')
})
【bug记录】
判断的逻辑如下,判断之前打印pathname和method,前者是url集合后者是get,login和reg页面也无法正常切换,页面无论如何只显示404notfound的默认样式。
最后将pathname换成了require(‘url’).parse(request.url).pathname,页面切换正常。或者在上面使用对象解构声明let { pathname } = request;
//存在问题的代码示例
if (method === 'GET' && pathname === '/login') {
// 登录的情形
Response.end('登录页面');
} else if (method === 'GET' && pathname === '/reg') {
Response.end('注册页面');
} else {
Response.end('404 Not Found');
}
设置HTTP响应报文
- 设置响应状态码 response.statusCode
- 设置响应状态描述 response.statusMessage(用的非常少)
- 设置响应头信息 response.setHeader(‘头名’,‘头值’)
- 设置响应体 response.write(‘xx’)或者 response.end(‘xxx’)
在服务对象里设置响应状态码
Response.statusCode = 203;
默认的响应状态码是200,多次设置后面设置的会覆盖前面的哈哈哈。
在服务对象里面设置响应状态描述
Response.statusMessage = ‘sorry tony’;
在服务对象里面设置响应头
- Response.setHeader(‘content-type’, ‘text/html;charset=utf-8’);
- Response.setHeader(‘server’, ‘node.js’);
- // Response.setHeader(‘暗号’, ‘是谁在等天明’);//error
- Response.setHeader(‘test’, [‘a’, ‘b’, ‘c’]);
响应头设置中文内容给会报错
响应体的设置
- Response.write(‘love’);
- Response.write(‘love’);
- Response.write(‘love’);
- Response.write(‘love’);
- Response.end();//设置响应体
响应体可以重复设置,最后会叠加呈现在页面上,一般write设置了载体,end里面就不会设置载体了。
HTTP响应练习题
搭建HTTP服务,响应一个4列3行的表格,并且要求表格有隔行换色效果,且点击单元格能高亮显示
const http = require('http');
const fs = require('fs');
const server = http.createServer((request, Response) => {
// 读取文件内容
let html = fs.readFileSync(__dirname + '/http响应表格.html');
Response.end(html);//end可以是字符串可以是buffer
});
server.listen(9000, () => {
console.log('服务已经启动,,端口9000监听中')
})
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>Document</title>
</head>
<style>
td {
padding: 20px 40px;
}
table tr:nth-child(odd) {
background: #aef;
}
table tr:nth-child(even) {
background: #fcb;
}
table,
td {
border-collapse: collapse;
}
</style>
<script>
// 获取所有的td
let tds = document.querySelectorAll('td');
// 遍历
tds.forEach(item => {
item.onclick = function () {
this.style.background = '#red';
}
})
</script>
<body>
<table border="1">
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
</body>
</html>
前几次运行出现的问题是js部分代码执行的功能是点击对应的表格出现光标,而不是高亮显示,暂时不知道问题出在哪里。
第二天,我笑了,script标签里面的代码写进window.οnlοad=function(){}里面就好了呀哈哈哈。
网页资源加载基本过程
html
<head>
<link rel="stylesheet" href="./index.css">
</head>
<body>
<h1>测试</h1>
<hr>
<img src="./素材/msld1.jpg">
<script src="./index.js"></script>
</body>
css
body{
height: 100vh;
padding: 20px;
box-sizing: border-box;
background: linear-gradient(right bottom,#aef,#c90);
}
hr{
margin: 20px 0;
border-bottom: solid 1px rgba(0, 0, 0, 0.363);
}
js
window.onload = function () {
let img = document.querySelector('img');
img.onclick = function () {
alert('点我干嘛')
}
}
【记录bug】
考虑添加else条件判断返回img
,依旧不可以
移动图片资源到英文文件夹,依然不显示。
最后文件正常显示是在vscode的html文件中右键 open with live server,在端口5500下正常显示。
静态资源服务
静态资源是指内容长时间不发生改变的资源,比如图片视频,脚本文件,字体文件,html文件等等
动态资源是内容经常更新的资源你,比如百度首页,网易首页,京东搜索列表页面等等。
const http = require('http');
const fs = require('fs');
// 创建服务对象
const server = http.createServer((req, res) => {
// 获取请求url的路径
let { pathname } = new URL(req.url, 'http://127.0.0.1');
// 拼接文件路径
let filePath = __dirname + '/page' + pathname;
// 读取文件fs异步API
fs.readFile(filePath, (err, data) => {
if (err) {
Response.statusCode = 500;
Response.end('文件读取失败');
return;//防止代码继续往后执行
}
// 响应文件内容
Response.end(data);//设置响应体
})
});
server.listen(9000, () => {
console.log('服务已经启动')
});
静态资源目录与网站根目录
http服务在哪个文件夹中寻找静态资源,那个文件夹就是静态资源目录,也称之为网站根目录
vscode中使用live-server访问HTML时,它启动的服务网站根目录是谁
vscode 当前打开的文件夹
const http = require('http');
const fs = require('fs');
// 创建服务对象
const server = http.createServer((req, res) => {
// 获取请求url的路径
let { pathname } = new URL(req.url, 'http://127.0.0.1');
// 声明一个变量
let root = __dirname + '/../';
// 拼接文件路径
let filePath = root + pathname;
// 读取文件fs异步API
fs.readFile(filePath, (err, data) => {
if (err) {
res.setHeader('content-type', 'text/html;charset=utf-8');
res.statusCode = 500;
res.end('文件读取失败');
return;//防止代码继续往后执行
}
// 响应文件内容
Response.end(data);//设置响应体
})
});
server.listen(9000, () => {
console.log('服务已经启动')
});
运行结果
设置资源类型mime
媒体类型(MIME类型)是一种标准,用来表示文档、文件或者字节流的性质和格式。
HTTP服务可以设置响应头Content-Type来表明响应体的MIME类型,浏览器会根据该类型决定如何处理资源。
常见文件对应的mime类型
- application/octet-stream
这是应用程序文件的默认值。未知的应用程序文件,浏览器一般不会自动执行或询问执行。- text/plain
文本文件默认值。即使它意味着未知的文本文件,但浏览器认为是可以直接展示的。
text/css
text/html
text/javascript
对于未知的资源类型,可以选择application/octet-stream类型,浏览器在遇到该类型的响应时,会对响应体内容进行独立存储,也就是我们常见的下载效果。
const http = require('http');
const fs = require('fs');
const path = require('path');
// 创建服务对象
const server = http.createServer((req, res) => {
// 获取请求url的路径
let { pathname } = new URL(req.url, 'http://127.0.0.1');
// 声明一个变量
// let root = __dirname + '/../';
let root=__dirname
// 拼接文件路径
let filePath = root + pathname;
// 读取文件fs异步API
fs.readFile(filePath, (err, data) => {
if (err) {
res.setHeader('content-type', 'text/html;charset=utf-8');
res.statusCode = 500;
res.end('文件读取失败');
return;//防止代码继续往后执行
}
// 获取文件的后缀名
let ext = path.extname(filePath);
console.log(ext);
res.setHeader('content-type','xxx')
// 响应文件内容
res.end(data);//设置响应体
})
});
server.listen(9000, () => {
console.log('服务已经启动')
});
运行结果截图
// 声明一个变量
let mimes = {
html: 'text/html',
css: 'text/css',
js: 'text/javascript',
png: 'image/png',
jpg:'image/jpeg'
}
let type = mimes[ext];
if (type) {
// 匹配到
res.setHeader('content-type', type);
} else {
// 没有匹配到
res.setHeader('content-type', 'application/octet-stream');
}
如果css资源中有中文(中文注释也算),那么访问具体的文件的时候页面也会出现乱码,html的不会乱码因为meta设置字符是utf-8,当然,响应头的charset优先级更高一些
完善错误处理
打开一个不存在的文件报错,完善错误处理
if (err) {
// 设置字符集
res.setHeader('content-type',' text/html; charset=utf-8');
// 判断错误的代号
switch (err.code) {
case 'ENOENT':
res.statusCode = 404;
res.end('<h1>404 Not Found </h1>');
case 'EPERM':
res.statusCode = 403;
res.end('<h1>403 Forbidden </h1>');
}
return;
}
第一个err访问一个不存在的文件,第二个err通过手动修改html的读取属性的权限,禁止读取,然后在测试。右键属性安全编辑里面就可以操作。
第三个请求方式的错误处理
可以借助form表单进行post请求,打开表单提交表单发送post请求
第五种其他的错误处理
default:
res.statusCode=500;
res.end('<h1>500 Internal Server Error </h1>');
GET和POST使用场景
get请求的场景
- 在地址栏直接输入url访问
- 点击a链接
- link标签引入css
- script标签引入js
- video和audio引入多媒体
- img标签引入图片
- form标签中的method为get(不区分大小写)
- ajax中的get请求
post请求的场景 - form标签中的method为post(不区分大小写)
- AJAX中的post请求
get与post请求的区别
get和post是http协议请求的两种方式,主要有以下几个区别
- 作用,get主要用来获取数据,post主要用来提交数据
- 参数位置。get带参数请求是将参数缀到URL之后,post带参数是将参数放到请求体中。
- 安全性。 post请求相对get安全一些,因为在浏览器中参数会暴露在地址栏中
- get请求大小有限制,一般为2k,而post请求则没有大小限制。