更多个人博客:(https://github.com/zenglinan/blog)
如果对你有帮助,欢迎star。
用 Node.js 可以快速搭建一个 http 服务器, 本文将会手把手从头搭建, 最后还会实现一个简易的 express 服务器, 开始吧~
1. 搭建基本雏形
首先, 搭建 http 服务器, 需要先引用 Node.js 的核心模块: http, 实际上这是一个对象, 利用 http 上的 createServer 方法来创建一个 http 服务器, createServer 方法需要接受一个事件函数, 用于处理请求和响应
const http = require('http')
const server = http.createServer((req, res) => {
// req 是请求对象, 可以获取请求的一些方法路径数据等属性
// res 是响应对象, 可以进行设置响应数据等操作
})
到现在, 这个 http 服务器的雏形已经基本搭建好了!
但是想想还差点东西, 一个 url 里包含 协议 域名 端口, 我们还没指定端口呢.
const http = require('http')
const server = http.createServer((req, res) => {
})
server.listen(8000) // 监听 8000 端口
OK, 大功告成
2. 响应数据
现在这个 http 服务器的框架已经搭好了. 启动一下, 在浏览器输入localhost:8000
试一下吧
什么? 你说你试了一下, 没有响应?
当然, 我们这里还没有返回任何数据呢, 如果没有访问数据, 浏览器端肯定是显示无响应的.
这里我们先随便返回点数据给浏览器, 然后重启服务器
const http = require('http')
const server = http.createServer((req, res) => {
res.end('这是我返回的数据噢!')
})
server.listen(8000)
相信你已经看到页面上显示的....一堆乱码了吧_, 是的, 因为 JavaScript 默认字符集对中文的支持不好. 我们需要指定一下返回数据的 Content-Type
const http = require('http')
const server = http.createServer((req, res) => {
res.setHeader("Content-Type","text/html;charset=utf-8")
res.end('这是我返回的数据噢!')
})
server.listen(8000)
3. 处理路由
接下来, 我们需要对路由进行处理, 现在我们不管访问什么路径, 都统一返回一样的数据.
接下来我们实现一下, 访问 /a , /b, /c 三个路由, 返回不同的数据
之前说过, req 对象上存放着请求的一些属性. req.url
上记录着请求的路径, 获取后就可以判断访问路径来返回不同的数据了
const {url} = req
完整代码:
const http = require('http')
const server = http.createServer((req, res) => {
const {url} = req
res.setHeader("Content-Type","text/html;charset=utf-8")
if(url === '/a') res.end(`访问a路由`)
else if(url === '/b') res.end(`访问b路由`)
else if(url === '/c') res.end(`访问c路由`)
})
server.listen(8000)
4. 处理查询参数
接下来, 我们对查询参数做一下处理, 这时候, 是不是想到了什么, 上面我们的 url 没有考虑到查询参数的情况, 路由里应该滤除掉查询参数, 我们来一并处理
我们知道, 查询参数的形式是 a=x&b=x
, 这里为了方便使用, 我们引用另一个模块 querystring
, 他可以把查询参数字符串切割成键值对形式
const http = require('http')
const querystring = require('querystring')
const server = http.createServer((req, res) => {
const {url} = req
const path = url.split('?')[0]
const query = querystring.parse(url.split('?')[1]) // 保存着查询参数的对象
res.setHeader("Content-Type","text/html;charset=utf-8")
if(path === '/a') res.end(`访问a路由, 查询参数对象为${JSON.stringify(query)}`) // 返回序列化的查询参数
else if(path === '/b') res.end(`访问b路由`)
else if(path === '/c') res.end(`访问c路由`)
})
server.listen(8000)
5.处理 POST 请求
OK, 接下来要做啥呢? 想了想, 我们目前好像只处理了 GET 请求, 那我们来处理一下 POST 请求吧.
一样的, 请求的 method 可以通过req.method
获取
这里要注意: req 对象实现了 ReadableStream 接口, 我们可以用信息流的方式读取传来的数据 (关于流的概念可以看后面我的文章)
let postData = ''
req.on('data', chunk => { // 接收数据流
postData += chunk.toString() // 拼接信息流, 注意chunk是二进制格式, 需要转为二进制
})
req.on('end', () => {
// 接收数据流完毕的回调函数
})
完整代码 :
const http = require('http')
const querystring = require('querystring')
const server = http.createServer((req, res) => {
const {url, method} = req
const path = url.split('?')[0]
const query = querystring.parse(url.split('?')[1])
res.setHeader("Content-Type","text/html;charset=utf-8")
if(path === '/a' && method === 'GET') res.end(`访问a路由, 查询参数对象为${JSON.stringify(query)}`)
else if(path === '/b' && method === 'GET') res.end(`访问b路由`)
else if(path === '/c' && method === 'GET') res.end(`访问c路由`)
else if(path === 'p' && method === 'POST'){
let postData = ''
req.on('data', chunk => { // 接收数据流
postData += chunk.toString() // 拼接信息流, 注意chunk是二进制格式, 需要转为二进制
})
req.on('end', () => {
res.end(`我接受到了数据了:${postData}`)
})
}
})
server.listen(8000)
OK, 来回顾一下我们做了什么:
- 我们创建了一个基本的 http 服务器
- 对路由做了处理
- 获取了查询参数
- 处理了 POST 请求
6. 优化路由处理
我们现在来回顾一下自己的代码, 可以看到, 在路由处理的部分有一堆的 if else, 假如每多一个路由就多一个 if , 那就太冗余了.
这里我们用一个数组来存放一个个路由对象, 路由对象里包含了路径, 方法, 回调等必要信息
const http = require('http')
const querystring = require('querystring')
const server = http.createServer((req, res) => {
const {url, method: realMethod} = req
const realPath= url.split('?')[0]
const query = querystring.parse(url.split('?')[1])
let router = [] // 存放路由信息
res.setHeader("Content-Type","text/html;charset=utf-8")
router.push({
path: '/a',
method: 'GET',
handler(req, res){
res.end(`访问a路由, 查询参数对象为${JSON.stringify(query)}`)
}
})
router.push({
path: '/b',
method: 'GET',
handler(req, res){
res.end(`访问b路由`)
}
})
router.push({
path: '/c',
method: 'GET',
handler(req, res){
res.end(`访问c路由`)
}
})
router.push({
path: '/p',
method: 'POST',
handler(req, res){
let postData = ''
req.on('data', chunk => {
postData += chunk.toString()
})
req.on('end', () => {
res.end(`我接受到了数据了:${postData}`)
})
}
})
// 统一处理路由
router.forEach(route => {
let {path, method, handler} = route
console.log(realPath, realMethod)
if(realPath === path && realMethod === method){
return handler()
}
})
})
server.listen(8000)
7. 改写为 express 形式
是不是感觉稍微好看一点了, 加一个路由就 push 一个路由对象.
我们离最终目标很接近了, 接下来, 让我们模仿 express , 写一个 express 形式的 http 服务器(▽)
先来看看 express 是怎么写的
const express = require("express");
const app = express();
app.get("/a",
(req, res) => {
res.end("a路由");
}
);
app.get("/b",
(req, res) => {
res.end('b路由');
});
app.listen(3000, () => {
console.log("Example app listen at 3000");
});
可以看到, 导出的 express 是一个函数, 函数内部会 new 一个实例对象出来, 大概的架子便是这样.
const http = require('http')
class Express{
}
module.exports = function(){
return new Express()
}
接下来让我们实现完整代码:
const http = require('http')
class Express{
constructor(){
this.router = [] // 存放路由对象
}
get(path, handler){
this.router.push({
path,
method: 'GET',
handler
})
}
post(path, handler){
this.router.push({
path,
method: 'POST',
handler
})
}
listen(port, listenCallback){
const server = http.createServer((req,res) => {
const {url, method:realMethod} = req
const realPath = url.split('?')[0]
this.router.forEach((route) => { // 遍历路由对象
const {path, method, handler} = route
if(realPath === path && method === realMethod){
handler(req, res)
}
})
})
server.listen(port)
listenCallback()
}
}
module.exports = function(){
return new Express()
}
到这里, 我们已经简单将我们的 http 服务器改写成 express 形式了, 不过考虑的细节还远远不够 express 那么完善.
撒花ヾ(◍°∇°◍)ノ゙