从零开始用nodejs写一个简单的静态服务器

142 阅读2分钟

从零开始用nodejs写一个简单的静态服务器

nodejs搭建服务器第一步

const http = require("http")

const PORT = 8000
const server = http.createServer((req, res) => {
    res.end()
})
server.listen(PORT)

很简单,使用 http 这个模块中的createServer方法,该方法接收一个函数,函数的参数有两个,第一个是 req(request,代表请求的相关信息),第二个是res(response,代表响应的相关信息)。在函数里我们什么都不做,直接使用res.end()结束响应。最后使用server.listen(PORT)表示监听8000端口

处理请求和响应

上一步已经创建了一个简单的服务器,但是没有做任何处理直接结束了,这次我们加一些逻辑

const http = require("http")

const PORT = 8000
const server = http.createServer((req, res) => {
    if (req.method === "GET") {
        res.setHeader("Content-Type", "text/html;charset=utf-8")
        res.end("<h1>Hello NOdejs</h1>")
    }
})
server.listen(PORT)

这里加的逻辑就是如果 http 请求是 GET 请求,那么就返回 Hello Nodejs。

现在在浏览器中输入localhost:8000就可以看到 Hello Nodejs 了

其实就是浏览器发送了一个 GET 请求给我们写的服务器,我们的服务器接收到了该请求,并确认该请求是 GET 请求后,就返回了一个 html 给浏览器

加入读写文件的支持

上一步已经能从服务器返回数据了,但是我要做的是静态服务器啊,返回的是写好的 html 或者 js文件,直接这样在res.end()里写好像不太现实,兵来将挡水来土掩,这时候用 nodejs 操作文件就可以了。

const http = require("http")
const fs = require("fs")
const url = require("url")
const path = require("path")

const PORT = 8000
// 这次为了清楚,我们把函数单独拿出来写
const serverHandle = (req, res) => {
	// pathObj 是解析 url 得到的对象
    const pathObj = url.parse(req.url, true)
    console.log(pathObj)
    // __dirname 指的是当前文件夹在文件系统中的绝对路径,pathname是要打开的文件的相对路径
    // 通过 path.join 将两者拼接起来,就是要打开的文件的绝对路径
    const filePath = path.join(__dirname, pathObj.pathname)
    fs.readFile(filePath, "binary", (err, file) => {
        if (err) {
            res.writeHead(404, "not found")
            res.end("<h1>404 NOT FOUND</h1>")
        } else {
            res.write(file, "binary")
            res.end()
        }
    })
}
http.createServer(serverHandle).listen(PORT)
console.log(`正在监听${PORT}端口`)

我们的目录结构是这样的

我们在浏览器中输入 http://localhost:8000/static/index.html?a=1&b=1,就可以正确的打开 index.html文件了

分析一下上面的代码

  • 这是pathObj,为什么前面那么多null呢,因为req.url 不会获取完整的 url,只会获取客户端发送过来的,即 chrome 中 network 的那部分

  • url.parse(req.url, true)表示的是将该 url 解析成一个对象。其中第二个参数可省略,省略的话默认是false。设置为true将自动将查询参数转换成对象,什么意思呢? 解析后的对象中有个query的属性: false的话,query对应的值 => 'a=1&b=1' true的话,query对应的值 => { a: '1', b: '1' } 方便后面的调用(url.parse(request.url, true).query.a)

  • fs.readFile函数第一个参数传的是文件的路径,第二个参数是编码方式,这里采用二进制方式(和后面的res.write配合,下一条会说),第三个参数是回调函数

  • res.write(file, "binary")因为这里用了二进制返回数据,所以之前的fs.readFile就要用二进制的编码方式,原因参见官方文档

    第一个chunk参数就是我们的文件,我们的文件显然不是String类型,所以fs.readFileres.write都要采用二进制的编码方式

一点微小的改进

const http = require("http")
const fs = require("fs")
const url = require("url")
const path = require("path")

const PORT = 8000
const serverHandle = (req, res) => {
    // 改了下面的两行!!!
    const url = new URL(req.url)
    const filePath = path.join(__dirname, url.pathname)
    fs.readFile(filePath, "binary", (err, file) => {
        if (err) {
            res.writeHead(404, "not found")
            res.end("<h1>404 NOT FOUND</h1>")
        } else {
            res.write(file, "binary")
            res.end()
        }
    })
}
http.createServer(serverHandle).listen(PORT)
console.log(`正在监听${PORT}端口`)

之所以要这样改,参见官方文档

之前用的接口即将过时,所以采用新的,新的和旧的的区别见下图