作为还在漫漫前端学习路上的一位自学者。我以学习分享的方式来整理自己对于知识的理解,同时也希望能够给大家作为一份参考。希望能够和大家共同进步,如有任何纰漏的话,希望大家多多指正。感谢万分!
之前, 我们搭建了静态文件服务器. 用户通过在浏览器搜索栏输入 URL 来请求保存在服务器的指定文件. 但是除了提供静态文件, 服务器能做的还有很多很多. 在这一篇, 我们要学会用 Node.js 处理从前端页面的 HTML 表单中提交的信息.
搭建路由
在日常, 我们访问一个站点的不同地址时, 通常页面内容也随之改变. 这是因为服务器为了实现更多的功能, 其会根据请求 URL 的不同而做出不同的处理, 这被称作 "路由"
『 路由 』简单来说就是 请求和请求处理代码之间的映射关系. 当服务器为一个特定 URL 挂在了请求处理代码时, 所有针对于这个特定 URL 的请求都会交由其处理.
假设我们要做一个用于自我介绍的个人网页, 其包含: "主页". "项目介绍页面", "关于我页面".
那么我们可以像下面代码中那样来搭建路由规则:
// 引入相关模块
var http = require('http');
var url = require('url');
// 搭建 HTTP 服务器
var server = http.createServer(function(req, res) {
// 获取请求 URL, 根据 URL 中的 pathname 来匹配对应的处理方法.
var urlObj = url.parse(req.url);
var urlPathname = urlObj.pathname;
switch (urlPathname) {
case "/main":
// 因为返回内容中有中文, 所以别忘了指定编码方式
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
res.write("主页页面");
res.end();
break;
case "/aboutme":
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
res.write("关于我页面");
res.end();
break;
case "/projects":
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
res.write("项目介绍页面");
res.end();
break;
// 如果都不匹配就返回 404
default:
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
res.write("404 - Not Found");
res.end();
break;
}
});
// 在 3000 端口监听请求
server.listen(3000, function() {
console.log("服务器运行中.");
console.log("正在监听 3000 端口:")
})
复制代码
上面代码根据请求 URL 的不同, 而将请求交给不同的处理代码. 你可以尝试运行服务器, 然后用浏览器去请求相应的 URL, 来看看得到的响应是什么.
获取 GET 表单提交
在学习了路由相关知识之后, 我们再来了解一下如何获取从客户端发过来的 表单提交.
我们先介绍用 GET 方法提交的表单. 通过 GET 提交的表单内容会组装成『 查询字符串 』嵌入在请求 URL 里. 例如下面这段:
https://www.zhihu.com/search?type=content&q=罐装汽水Garrik
复制代码
从 ?
问号开始就是这段 URL 的查询字符串; 参数之间用 &
分开; =
等号前面的是参数名, 后面的是参数值.
上面这段 URL 的查询字符串如何解析成 JSON 的话就是:
{
"type": "content",
"q": "罐装汽水Garrik"
}
复制代码
那么再简单了解了基础知识之后呢, 就让我们赶快来写代码吧!
首先让我们来写一个有 HTML 表单的页面, 然后命名为 login.html (当然你也可以按照你的想法写代码和命名)
这个表单我想用来提交登录信息, form
元素的 action
属性我定义为 login
, 意思是将请求发送到 login
这段路径下. method
属性我定义为 get
, 意思是以 GET 方法提交表单.
<body>
<form action="login" method="get">
账户: <input type="text" name="username" />
<br />
密码: <input type="text" name="password" />
<br />
<input type="submit" value="提交">
</form>
</body>
复制代码
之后再让我们来写服务器代码. 通过前面的介绍, 你知道我们需要解析 URL 的查询字符串. 做到这点很简单, 只需要在调用 url.parse
函数解析请求 URL 时为其传入第二个参数 true
. 这个函数就会自动帮你把 URL 的查询字符串解析成一个 JavaScript 对象了, 保存在函数返回对象的 query
属性中. 如果没有查询的话属性值就是 null
我们可以用路由去匹配路径, 当请求 URL 的路径和表单发送的路径相匹配时, 将请求交给特定代码去处理.
var server = http.createServer(function(req, res) {
// 解析请求 URL
var urlObj = url.parse(req.url, true);
// 获取请求 URL 的路径
var urlPathname = urlObj.pathname;
// 获取请求 URL 的查询字符串解析成的对象
var queryObj = urlObj.query;
// 路由
switch (urlPathname) {
// 响应 login 页面
case "/":
case "":
// 我用了静态服务器那篇的模块, 不了解的地方可以去那篇参考
readStaticFile(res, "./login.html");
break;
// 响应查询对象的 JSON 形式到浏览器
case "/login":
res.writeHead(200, { "Content-Type": "text/plain" });
res.write(JSON.stringify(queryObj));
res.end();
break;
// 错误处理
default:
readStaticFile(res, "./404.html");
}
});
复制代码
当运行起服务器之后, 访问 login 页面, 提交表单你看到的应该像是下面这样:
获取 POST 表单提交
说完 GET, 我们再来说说用 POST 方法提交表单. 不同于用 GET 方法时, 提交的内容都包含在 URL 里. POST 提交的内容全部的都在请求体中.
我们 HTTP 服务器 http.createServer
接收的请求对象 req
并没有一个属性内容为请求体. 原因是 POST 请求体可能体积非常大, 如果每次接收请求都包含请求体的话会很耗时. 而且万一遇到了恶意 POST 请求攻击, 服务器的资源就被大大地浪费了.
为了获取 POST 请求体, 我们需要手动来操作. 因为 POST 请求数据量可能很大, 所以它被拆分成了很多个小数据块 ( chunk ) 我们通过在服务器监听请求对象 req
的 'data'
事件来一个个地接收这些数据块, 并将其拼接在一起.
当请求传输完毕, 会触发请求对象 req
的 'end'
事件. 我们需要监听它, 事件触发后, 在其事件处理函数中解析 POST 的请求体.
var server = http.createServer(function(req, res) {
var urlObj = url.parse(req.url, true);
var urlPathname = urlObj.pathname;
switch (urlPathname) {
case "/":
case "":
readStaticFile(res, "./login.html");
break;
case "/login":
// 当请求方法为 POST 时触发
if (req.method === 'POST') {
// 用于保存拼接后的请求体
var post = '';
// 'data' 事件触发, 将接受的数据块 chunk 拼接到 post 变量上
req.on('data', function(chunk) {
post += chunk;
});
// 请求完毕, 'end' 事件触发
req.on('end', function() {
// querystring 是 Node.js 自带模块, parse 方法用于将查询字符串解析成对象
var queryObj = querystring.parse(post);
// 将接收的 POST 请求体以 JSON 格式响应回客户端
res.writeHead(200, { "Content-Type": "text/plain" });
res.write(JSON.stringify(queryObj));
res.end();
});
}
break;
default:
readStaticFile(res, "./404.html");
}
});
复制代码
对了, 最重要的一点, 别忘了将 login.html 文件中的表单提交方法从 get
改成 post
<body>
<form action="login" method="post">
<!-- 省略了 -->
</form>
</body>
复制代码
现在运行服务器, 提交表单, 看看结果是什么. 应该效果像下图所示:
POST 文件上传
文件上传我们可以很方便的用第三方模块 formidable 来实现.
首先用 npm 来安装模块:
npm install formidable --save
复制代码
formidable 是用于是表单数据解析的模块, 非常适合用于文件上传的处理. 使用该模块时, 先要调用它的 IncomingForm
构造函数初始模块. 该函数返回一个 IncomingForm
实例用于解处理表单提交数据. 之后通过调用该实例的 parse
方法来解析数据.
当用户使用表单提交数据时,表单中可能会包含两类数据: 普通表单数据, 文件数据. parse
方法解析时,会将这两种数据分别放到fields
和 files
这两个回调参数中.
那么不多废话直接上代码:
// 模块引入
var formidable = require('formidable');
var server = http.createServer(function(req, res) {
var urlObj = url.parse(req.url, true);
var urlPathname = urlObj.pathname;
switch (urlPathname) {
case "/":
case "":
readStaticFile(res, "./upload.html");
break;
// 路由为 '/upload'
case "/upload":
if (req.method === 'POST') {
// 初始化 formidable 的 IncomingForm 实例
var form = new formidable.IncomingForm();
// uploadDir 设置上传文件时临时文件存放的位置
form.uploadDir = "./uploads";
// keepExtensions 属性设置是否保留上传文件的扩展名, 默认为 false
form.keepExtensions = true;
// 开始解析
form.parse(req, function(err, fields, files) {
if (err) {
var message = "文件解析失败";
} else {
var message = "文件上传成功";
}
res.writeHead(200, { "Content-Type": "text/plain;charset=utf-8" });
res.write(message);
res.end();
})
}
break;
default:
readStaticFile(res, "./404.html");
}
});
复制代码
服务器代码写完后, 让我们写 upload.html 文件:
<body>
<form action="upload" enctype="multipart/form-data" method="post">
<input type="file" name="upload" />
<br />
<input type="submit" value="提交">
</form>
</body>
复制代码
注意要设置的表单的编码方式 enctype
为 "multipart/form-data"
表单数据默认的编码方式为 "application/x-www-form-urlencoded"
不可用于文件上传. 在使用包含文件上传控件的表单时,必须使用 "multipart/form-data"
这个值.
写好后, 运行服务器, 上传一张你喜欢的照片, 看看结果是什么. 以下是我的操作:
可以看到照片已经上传到了 uploads 目录下.
GET vs POST
前面分别用 GET 和 POST 方法提交了表单, 那么这两种方法到底区别是什么呢?
先来看看 MDN 对这两个方法的定义:
- 『 HTTP GET 方法 』: 请求指定的资源. 使用 GET 的请求应该只用于获取数据
- 『 HTTP POST 方法 』: 发送数据给服务器
上面说的已经很简洁, 当你想要请求服务器上的资源时用 GET 方法. 发送数据时用 POST 方法. 像我之前用 GET 方法提交登录信息, 是不符合规范的. 实际开发中, 这种行为不允许出现.
说完定义, 让我们再来看看这两种方法在表现上有什么不同.
-
善于观察的你一定已经发现, GET 提交的表单数据显式地添加在了请求 URL 的查询字符串中. 而 POST 把提交的数据放置在了请求体中. 这也体现出为什么 GET 不能用于传输数据, 你总不希望你的账号和密码这么明显地暴露在 URL 里吧.
-
因为浏览器对 URL 的长度都有限制, 所以 GET 方式提交的数据是有大小限制的, 一般不超过 1024 字节. 理论上讲, POST 提交数据时没有大小限制的. 但出于性能考虑, 服务器接收时可能对 POST 传输的数据大小进行限制.
? 好啦,今天的分享就告一段落啦。下一篇中,我会介绍 "模板引擎"
如果喜欢的话就点个关注吧!O(∩_∩)O 谢谢各位的支持❗️