4 HTTP请求与相应处理
4.1 请求参数
用户名和密码需要通过某种形式(请求参数)传递到服务器端,服务器端要获取内容,用于后面的验证。
请求参数分为get请求参数和post请求参数
4.2 get请求参数
发送请求
- app.js
const http = require('http');
const app = http.createServer();
app.on('request', (req, res) => {
res.writeHead(200, {
'content-type': 'text/html;charset=utf8'
});
console.log(req.url);
if (req.url == '/index' || req.url == '/') { //错误写法req.url == '/index' || '/'
res.end('<h2>欢迎来到主页</h2>');
} else if (req.url == '/list') {
res.end('welcome to list page');
} else {
res.end('not found');
}
if (req.method == 'POST') {
res.end('post');
} else if (req.method == 'GET') {
res.end('get');
}
});
app.listen(3000);
console.log('网站服务器启动成功');
- 打开服务器,在浏览器中输入http://localhost:3000/index?name=zhangsan&age=20
下面考虑在服务器端如何获取请求参数:在nodejs中提供了url内置模块,可以用于提取/index?name=zhangsan&age=20中的参数
(1)第一步
- app.js
const http = require('http');
const app = http.createServer();
//用于处理url地址
const url = require('url');
app.on('request', (req, res) => {
res.writeHead(200, {
'content-type': 'text/html;charset=utf8' //返回纯文本,若不指定这一项也是默认返回纯文本
});
//服务器端获取请求参数
console.log(req.url);
// url.parse(req.url); //parse:解析。 该方法返回一个对象
console.log(url.parse(req.url));
if (req.url == '/index' || req.url == '/') { //错误写法req.url == '/index' || '/'
res.end('<h2>欢迎来到主页</h2>');
} else if (req.url == '/list') {
res.end('welcome to list page');
} else {
res.end('not found');
}
if (req.method == 'POST') {
res.end('post');
} else if (req.method == 'GET') {
res.end('get');
}
});
app.listen(3000);
console.log('网站服务器启动成功');
- 打开服务器,在浏览器中输入http://localhost:3000/index?name=zhangsan&age=20
(2)第二步 - 继续修改app.js将要查询的参数解析成对象的形式
将以下代码
console.log(url.parse(req.url));
修改为(添加第二个参数)
//参数1:要解析的url地址
//参数2:将要查询的参数解析成对象的形式
// url.parse(req.url,true);
console.log(url.parse(req.url, true));
再刷新浏览器后:
(3)第三步
继续修改app.js拿到具体的请求参数
将以下代码
//参数1:要解析的url地址
//参数2:将要查询的参数解析成对象的形式
// url.parse(req.url,true);
console.log(url.parse(req.url, true));
修改为
//参数1:要解析的url地址false
//参数2:将要查询的参数解析成对象的形式
// url.parse(req.url,true);
let params = url.parse(req.url, true).query;
console.log(params.name);
console.log(params.age);
再刷新浏览器后:
解决not found问题
添加请求参数后,判断失效。因为req.url里面既包含了请求地址又包含了请求参数,所以就不可以直接用url来判断
(1)修改app.js
将以下代码
let params = url.parse(req.url, true).query;
console.log(params.name);
console.log(params.age);
if (req.url == '/index' || req.url == '/') { //错误写法req.url == '/index' || '/'
res.end('<h2>欢迎来到主页</h2>');
} else if (req.url == '/list') {
res.end('welcome to list page');
} else {
res.end('not found');
}
if (req.method == 'POST') {
res.end('post');
} else if (req.method == 'GET') {
res.end('get');
}
修改为:
//解构对象
let { query, pathname } = url.parse(req.url, true)
console.log(query.name);
console.log(query.age);
if (pathname == '/index' || pathname == '/') { //错误写法req.url == '/index' || '/'
res.end('<h2>欢迎来到主页</h2>');
} else if (pathname == '/list') {
res.end('welcome to list page');
} else {
res.end('not found');
}
(2)开启服务器,刷新浏览器不再显示not found
4.3 post请求参数
1 客户端发送请求参数到服务器端
- 在之前的form.js文件中进行编辑
<body>
<!--
表单中的属性
method:指定当前表单提交的方式,可以是GET也可以是POST,默认GET
action:指定当前表单提交的地址
-->
<form action="http://localhost:3000" method="POST">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit">
</form>
</body>
- 注意:
在用户点击提交按钮的时候,希望把用户名和密码提交到服务器端,服务器如何接收参数呢?
答:和get一样服务器通过参数的名字name来接收,所以每个表单项都需要一个名字,服务器根据这些名字来获取表单项,input中的name属性就是用来指定当前表单项的名字的。 - get请求,请求参数是放在地址栏中传输的,post的请求参数在请求报文中,在服务器关闭的情况下,在浏览器中打开form.js,输入用户名和密码提交,停止加载页面后
浏览器为了让我们更好地观察post参数,实际上对post参数做了显示上的优化,无论是post参数还是get参数,它们的格式都是一样的,点击Form Data右边的view source,可以看到
2 如何在服务器端接收请求参数
- 在sever文件夹下新建post.js,由于post参数没有存放在地址栏中,所以没有办法通过req.url来获取,post参数是通过事件(data、end)的方式来接收的。
- post参数在理论上数据量可以是无限的,作为服务器而言,为了减轻压力,服务器对于post参数不是一次就接收完,比如传输一个100M的数据,服务器会分10次接收,每一次只会接收10M.当有请求参数传输的时候就会触发data事件,当请求参数传递完成后就会触发end事件
- post.js
//用于创建网站服务器模块
const http = require('http');
//返回值app就是网站服务器对象
const app = http.createServer();
app.on('request', (req, res) => {
//post参数是通过事件的方式接收的
//data事件:请求参数传递时触发
//end事件:请求参数传递完成后触发
//请求参数与请求相关,所以在req上绑定这两个事件
//由于服务器不是一次性将post参数接收完成,所以先要声明一个变量postParams,然后在触发data事件时,要将当前传递过来的参数params和声明的变量进行拼接,之后在end事件触发的时候,输出变量postParams
let postParams = '';
req.on('data', params => { //params是传递过来的参数
postParams += params;
});
req.on('end', () => {
console.log(postParams);
});
//客户端的每次请求,服务器端都要作出相应,否则客户端将一直处于等待的状态
//服务器端的相应内容如下:
res.end('ok');
});
app.listen(3000);
console.log('网站服务器启动成功');
-
打开服务器
-
浏览器中打开form.js,属于用户名和密码后提交,并在控制台中查看结果
-
虽然获取到了参数,但是依然是字符串的形式,下面要对字符串username=ithema&password=123做处理,就要用到nodejs中的querystring模块,修改form.js如下
//用于创建网站服务器模块
const http = require('http');
//返回值app就是网站服务器对象
const app = http.createServer();
//处理请求参数模块
const querystring = require('querystring');
app.on('request', (req, res) => {
let postParams = '';
req.on('data', params => { //params是传递过来的参数
postParams += params;
});
req.on('end', () => {
console.log(querystring.parse(postParams));
});
//客户端的每次请求,服务器端都要作出相应,否则客户端将一直处于等待的状态
//服务器端的相应内容如下:
res.end('ok');
});
app.listen(3000);
console.log('网站服务器启动成功');
- 重复上述过程
3 小结
请求体就是指请求报文
4.4 路由
在网站应用中有一个路由的概念,通过路由就可以完成上述网站的访问
而上述代码缺少对请求方式的判断,同一个请求地址,如果请求方式不同,也要执行不同的处理逻辑。什么样的请求地址对应什么样的处理逻辑都是由开发人员决定的。
- 在lesson2目录下新建文件夹route,route文件夹下新建app.js
//1. 引入系统模块http
const http = require('http');
const url = require('url');
//2. 创建网站服务器
const app = http.createServer();
//3. 为网站服务器对象添加请求事件
app.on('request', (req, res) => {
//4. 实现路由功能
//(1)获取客户端的请求方式
const method = req.method.toLowerCase(); //req.method通常返回大写的GET或者POST,做判断的时候为了方便通常会转化为小写
//(2)获取客户端的请求地址
const pathname = url.parse(req.url).pathname;
//(3)逻辑处理s
res.writeHead(200, {
'content-type': 'text/html;charset=utf8'
});
if (method == 'get') {
if (pathname == '/' || pathname == '/index') {
res.end('欢迎来到首页');
} else if (pathname == '/list') {
res.end('欢迎来到列表页');
} else {
res.end('您访问的页面不存在');
}
} else if (method == 'post') {
}
});
//5. 监听端口
app.listen(3000);
console.log('服务器启动成功');
-
打开服务器
-
在浏览器中访问
4.5 静态资源
服务器端不需要处理,直接响应给客户端的资源就是静态资源,例如cssjsimagehtml文件。即浏览器可以直接运行的文件。比如网址http://www.itcast.cn/images/logo.png,请求的就是一张图片,服务器在接收到这个请求之后只需要找到这个图片,并将其直接响应给客户端。这个图片就是静态资源,无论谁来访问这个网址,得到的结果都是一样的。
静态资源访问功能:在服务器端创建一个专门的文件夹,用于放置这些静态资源,当客户端请求某个静态资源文件的时候
- 在lesson2目录下新建文件夹static,在static文件夹下新建文件夹public,在public中放置静态资源文件(提前准备好)
- 在static文件夹下新建app.js文件,要在这个文件创建网址服务器并实现静态资源访问的功能
//1. 引入系统模块http
const http = require('http');
const url = require('url');
//2. 创建网站服务器
const app = http.createServer();
//3. 为网站服务器对象添加请求事件
app.on('request', (req, res) => {
res.end('ok');
});
//5. 监听端口
app.listen(3000);
console.log('服务器启动成功');
-
启动服务器
-
访问静态资源
错误的访问方法:http://localhost:3000/public/default.html
原因:地址栏中的请求地址仅仅是一个字符串标识,仅仅是看起来像一个路径,也就是说上述public/default.html可以和服务器的真实路径是不一样的
目标:希望用户输入http://localhost:3000/default.html就可以访问到default.html(不需要public) -
修改app.js
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const app = http.createServer();
app.on('request', (req, res) => {
//获取用户不含请求参数的请求路径
let pathname = url.parse(req.url).pathname; // 得到/default.html
//将用户的请求路径转换成实际的服务器硬盘路径
//app.js文件的绝对路径__dirname
// res.end(path.join(__dirname, 'public', pathname)); //E:Web13_Nodejslesson2staticpublicpublicdefault.html
let realPath = path.join(__dirname, 'public', pathname);
//根据真实路径读取文件
fs.readFile(realPath, (error, result) => {
//如果文件读取失败
if (error != null) {
res.writeHead(404, {
'content-type': 'text/html;charset=utf8'
});
res.end('文件读取失败');
return;
}
res.end(result);
});
});
app.listen(3000);
console.log('服务器启动成功');
- 刷新浏览器
静态资源访问功能的优化
希望在上述情况下也访问default.html
- app.js修改
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const app = http.createServer();
app.on('request', (req, res) => {
let pathname = url.parse(req.url).pathname;
//新增一行代码如下:
pathname = pathname == '/' ? 'default.html' : pathname;
let realPath = path.join(__dirname, 'public', pathname);
fs.readFile(realPath, (error, result) => {
if (error != null) {
res.writeHead(404, {
'content-type': 'text/html;charset=utf8'
});
res.end('文件读取失败');
return;
}
res.end(result);
});
});
app.listen(3000);
console.log('服务器启动成功');
- 刷新浏览器
静态资源访问功能的优化2
当服务器端为客户端作出相应的时候,要告知客户端当前给你的资源类型是什么。那么就有一个小问题,当浏览器拿到服务器发来的html代码的时候,浏览器要执行代码,但是在执行的过程中会遇到link标签、img标签、script标签等,这些标签可以去外链一些文件,但是目前只是请求了html文件(地址栏中输入http://localhost:3000/default.html)。当浏览器执行html代码遇到link标签、img标签、script标签中的需要外链文件的代码时,浏览器会自动向服务器发送请求。无论请求的是什么文件服务器都是通过fs.readFile来读取文件的,那么当前的一次读取有可能是html文件,有可能是css等,那么'content-type'应该如何写类型呢?(程序运行的过程中,我们不知道当前请求的是什么文件,所以这个值无法写成固定的,text/html? text/css?)
答案:这就需要用到第三方模块mime。该模块可以根据当前的请求路径,分析出这个资源的类型。
- 下载该模块
- 开启服务器
- app.js
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');
const app = http.createServer();
app.on('request', (req, res) => {
let pathname = url.parse(req.url).pathname; // 得到/default.html
pathname = pathname == '/' ? 'default.html' : pathname;
let realPath = path.join(__dirname, 'public', pathname);
//根据路径返回文件类型
let type = mime.getType(realPath);
fs.readFile(realPath, (error, result) => {
if (error != null) {
res.writeHead(404, {
'content-type': 'text/html;charset=utf8'
});
res.end('文件读取失败');
return;
}
res.writeHead(200, {
//高版本浏览器不加下面这行代码也可以顺利执行,低版本的就不行
//为了严谨,这里一定要指定返回资源的类型
'content-type': type
});
res.end(result);
});
});
app.listen(3000);
console.log('服务器启动成功');
- 打开浏览器
4.6 动态资源
相同的请求地址可以传递不同的请求参数,得到不同的响应结果,这个响应结果就是动态资源,服务器将这个静态资源文件直接响应给客户端
比如上述两个请求地址,它们的请求地址一样,但是传递了不同的请求参数,那么得到的结果就是不一样的。