官方文档讲到Node.js® 是一个基于 Chrome V8 引擎 的 JavaScript 运行时环境,所以严格意义上来讲,Node.js不能算是一门语言,本质上还是基于JavaScript以及Chrome V8 引擎的。在日益工程化的前端世界里,前端开发者或多或少都会涉及node的使用,比如我们常用到的CLI工具,当然它还可以做Web服务器,比如Express和Koa,它们是对Node.js原生API进行了封装。通过文档,我们其实也可以不借助这些比较成熟的框架,只用原生API也能编写一个Web服务器出来。
Hello World
搭建一个简单的Web服务器,其实引用Node原生的http模块就够了,下面我们编写一个简单的Hello World程序:
//引进http模块
const Http = require('http');
//自定义端口
const port = 3000
// 创建请求句柄
const requestHandle = (req, res) => {
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('Hello World')
}
//创建请求监听者对象(Server)
const Server = Http.createServer(requestHandle);
Server.listen(port, () => {
console.log(`Server is running on http://127.0.0.1:${port}/`)
})
以上我们利用Node的http模块,创建了一个监听者对象,在服务创建后通过Server.listen监听相应的port,这样我们通过127.0.0.1:3000去访问就可以返回我们编写的Hello World 程序了!!!
但是离我们的Web服务器还相差甚远呢,哈哈,主要是还差对Web服务器基础功能的处理:
1.支持`HTTP`动词,比如`GET`,`POST`等
2.支持路由
所以我们要实现一个真正能用的Web服务器,我们需要逐一实现这些基础功能
处理HTTP动词
我们将requestHandle这个句柄改造一下,添加我们的Http请求方式
//引进url模块
const url = require('url');
const requestHandle = (req, res) => {
// url.parse可以将req.url解析成一个对象,里面包含有pathname和querystring等参数
const urlObject = url.parse(req.url);
switch (req.method) {
case "GET":
getGetParam(urlObject, res);
break;
case "POST":
getPostParam(urlObject, res);
break;
}
}
//Get请求
const getGetParam = (urlObject, res ) =>{
//我们定义以'/api/get'的为Get请求
if (urlObject.pathname.startsWith('/api/get')) {
res.statusCode = 200
res.setHeader('Content-Type', 'application/json;charset=UTF-8')
res.end('这是Get请求')
}
}
//POST请求
const getPostParam = (urlObject, res) =>{
//我们定义以'/api/post'的为post请求
if (urlObject.pathname.startsWith('/api/post')) {
res.statusCode = 200
res.setHeader('Content-Type', 'application/json;charset=UTF-8')
res.end('这是post请求')
}
}
当然,在实际开发中的,我们的post请求并不是这样玩的,这里仅是为了区分请求方式
处理路由
我们将getGetParam、getPostParam这两个函数再修改下
//Get请求
const getGetParam = (urlObject, res) => {
const resData = {
code: 0,
msg: 'success',
data: [
{ id: 0, name: '张三' },
{ id: 1, name: '李四' },
],
}
//我们定义以'/api/get'的为Get请求
if (urlObject.pathname.startsWith('/api/get')) {
// 再判断路由
if (urlObject.pathname === '/api/get/users') {
res.setHeader('Content-Type', 'application/json;charset=UTF-8')
res.end(JSON.stringify(resData))
}
}
}
//POST请求
const getPostParam = (urlObject, req, res) => {
const resData = {
code: 0,
msg: 'success',
data: [
{ id: 0, name: '王五', age: 22, sex: '男' },
{ id: 1, name: '赵六', age: 24, sex: '男' },
],
}
//我们定义一个formData变量,用于暂存请求体信息
let formData = ''
//我们定义以'/api/post'的为post请求
if (urlObject.pathname.startsWith('/api/post')) {
// 再判断路由
if (urlObject.pathname === '/api/post/users') {
//通过req的data事件监听函数,每当接受到请求体的数据,就累加到post变量
//post请求经常会很长,此时会分段传入
req.on('data', (chunk) => {
//将段落合并
formData += chunk
})
//当所有数据发送完毕之后,此时将会触发end事件
req.on('end', () => {
// 此时可以做一些逻辑处理,如文件写入等等
let result = JSON.parse(JSON.stringify(formData))
console.log('result:', result)
// 打印如下
// result: ----------------------------360048846796916393599860
// Content-Disposition: form-data; name="name"
// 王五
// ----------------------------360048846796916393599860
// Content-Disposition: form-data; name="age"
// 23
// ----------------------------360048846796916393599860
// Content-Disposition: form-data; name="sex"
// 男
// ----------------------------360048846796916393599860--
res.setHeader('Content-Type', 'application/json;charset=UTF-8')
// 这里我们就先不处理,将定义的数据返回
res.end(JSON.stringify(resData))
})
}
}
}
此时,我们的服务器已经具备路由请求能力了
写在最后
在实际开发过程中,还有许多需要考虑和权衡的地方,这里仅展示了一个server的基础能力,还有很多未实现的特性呢,如文件写入、长连接实现全双工通讯呀,这里就不赘述了,后面会继续进行探究。
- 文中实例代码已在github上 webServer