从基础模块开始,手把手教你搭建简易 HTTP 服务器

0 阅读7分钟

在前端开发的旅程中摸索一段时间后,我发现后端开发的世界同样充满魅力,尤其是 Node.js 这一领域,让我有了新的探索方向。Node.js 凭借其基于 Chrome V8 引擎的特性,在服务器端开发中崭露头角,特别适合中小型项目的开发。它能够高效地处理并发请求,这对于如今追求快速响应的网络应用来说至关重要。

走进 Node.js

JavaScript 拥有两种主要的模块化系统,CommonJSES Module。在 Node.js 早期,CommonJS 是主要的模块化方案,通过require来引入模块。例如,在构建 HTTP 服务器时,我们会用到require('http')引入http模块,这个模块可是 Node.js 内置的核心模块,就像搭建房屋的基石一样,为我们构建 HTTP 服务器提供了基础支持。使用require引入模块时,它是同步加载的,也就是说在代码运行到这一行时,会立即去查找并加载对应的模块,只有加载完成后,代码才会继续往下执行。

随着 JavaScript 的发展,ES6 带来了更先进的模块化方案 ——import。它是异步加载模块,在代码编译阶段就会处理模块的导入,这使得代码的执行效率和性能有了很大提升。不过,Node.js 默认并不支持 ES Module,在早期版本中,若想使用,需要借助像 babel 这样的工具进行编译。但令人欣喜的是,在 Node.js 的最新版本(如版本 22)中,已经开始支持 ES Module,这意味着我们在编写代码时,有了更现代化的选择,Node.js 似乎也在逐步向require和 CommonJS 挥手告别。

在实际开发中,我们经常会用到各种模块。除了http模块,fs模块(通过require('fs')引入)用于文件系统操作,比如读取文件,这在我们需要向客户端返回 HTML、CSS、JavaScript 等文件时非常有用;path模块(require('path'))则帮助我们处理文件路径,不同操作系统的路径分隔符有所不同,Windows 使用\,而 Linux 和 Mac OS 使用/path模块可以让我们的代码在不同系统中都能正确处理路径,避免因路径问题导致的错误;还有url模块(require('url')),用于处理 URL,它能帮助我们解析 URL 中的各个部分,获取我们需要的信息。

搭建属于自己的 HTTP 服务器

端口在网络服务中扮演着重要角色,它就像是一扇门,连接着特定的服务。例如,3306 端口通常是 MySQL 数据库使用的,8080 端口常用于 HTTP 服务(像 tomcat 服务器),80 端口也是 HTTP 服务常用端口(如 nginx 服务器)。从域名(比如localhost)开始,它会被解析为对应的 IP 地址(如 127.0.0.1),这个 IP 地址指向某台设备,然后通过端口找到对应的进程,进程中包含线程,线程最终执行我们编写的代码。一台设备上可以有多个端口被使用,也就意味着可以同时运行多个 HTTP 服务,承载多个网站。不过,在选择端口时,要注意避开一些特殊端口,以免出现冲突。

下面我们通过代码来实际感受一下如何使用 Node.js 搭建一个简单的 HTTP 服务器。首先,我们引入http模块:

const http = require('http'); 
// 或者在支持ES Module的环境下使用:
// import http from 'node:http'

接着,我们创建一个服务器实例:

const server = http.createServer((req, res) => { 
    // 这里的req是请求对象,包含了客户端请求的信息
    // res是响应对象,用于向客户端返回信息
});

HTTP 是基于请求响应的协议,路由通过Method(如 GET、POST 等)和url来定位服务器端的资源。例如,当我们访问http://localhost:8080/时,服务器需要知道返回什么内容给我们。在代码中,我们通过判断req.methodreq.url来实现:

if(req.method === 'GET' && req.url === '/' || req.url === '/index.html')      
{
    fs.readFile(path.join(__dirname, 'public','index.html'), 
    // 异步 callback
    (err, data) => { 
        // 后端稳定为主,前端体验为主
        if (err) {
            res.writeHead(500, { 'Content-Type': 'text/plain' });
            res.end('Internal Server Error');
        }
        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.end(data.toString());
    })
}

这段代码的意思是,如果客户端发送的是 GET 请求,并且请求的 URL 是根路径/或者/index.html,那么服务器会读取public目录下的index.html文件。path.join(__dirname, 'public','index.html')就是利用path模块拼接出文件的正确路径,__dirname表示当前文件所在的目录。读取文件是异步操作,因为读取文件可能需要一些时间,如果同步操作,会阻塞代码的执行,影响服务器的性能。如果读取文件过程中出现错误(err不为空),服务器会返回一个 500 状态码,表示内部服务器错误;如果读取成功,服务器会设置响应头Content-Typetext/html,表示返回的是 HTML 文件,然后将读取到的文件内容发送给客户端。

类似地,我们可以为 CSS 和 JavaScript 文件设置路由:

if(req.method === 'GET' && req.url === '/style.css')
{
    fs.readFile(path.join(__dirname,'public','style.css'), (err, data) => {
        if (err) {
            res.writeHead(500, { 'Content-Type': 'text/plain' });
            res.end('Internal Server Error');
        }
        res.writeHead(200, { 'Content-Type': 'text/css' });
        res.end(data);
    })
}

if(req.method === 'GET' && req.url === '/script.js')
{
    fs.readFile(path.join(__dirname,'public','script.js'), (err, data) => {
        if (err) {
            res.writeHead(500, { 'Content-Type': 'text/plain' });
            res.end('Internal Server Error');
        }
        res.writeHead(200, { 'Content-Type': 'text/javascript' });
        res.end(data);
    })
}

这样,当客户端请求/style.css/script.js时,服务器就能正确地返回对应的文件。

最后,我们让服务器监听一个端口,比如 1234:

server.listen(1234);

这样,一个简单的 HTTP 服务器就搭建完成了。当我们在浏览器中访问http://localhost:1234/时,就能看到服务器返回的index.html页面内容,页面中的 CSS 样式和 JavaScript 脚本也能正常加载,因为我们已经为它们设置了正确的路由。

image.png

使用 nodemon 实现自动重启

但是发现一个小问题,每次修改代码后都需要手动重启服务器才能看到效果,这无疑会浪费许多时间。为了解决这个问题,我们可以使用一些工具来实现热更新,让开发体验更加流畅。

nodemon 是一个非常流行的 Node.js 开发工具,它可以监视项目中的文件变化,并在文件修改后自动重启服务器。这样,我们就不需要每次修改代码后都手动重启服务器了。

首先,我们需要全局安装 nodemon:

npm install -g nodemon

安装完成后,我们可以使用 nodemon 来启动我们的服务器。假设我们的服务器文件是server.js,我们可以这样启动:

nodemon server.js

现在,当我们修改server.js或者项目中的其他文件时,nodemon 会自动检测到变化,并重启服务器。这样,我们就可以立即看到修改后的效果,大大提高了开发效率。

nodemon 的工作原理其实很简单。它会监视指定目录下的文件变化,当检测到文件修改时,它会杀死当前正在运行的 Node.js 进程,然后重新启动一个新的进程。这样,我们的代码修改就会立即生效。

需要注意的是,nodemon 只适合在开发环境中使用,不建议在生产环境中使用。因为在生产环境中,频繁重启服务器可能会影响服务的稳定性和可用性。

除了 nodemon,我在网上了解到还有一些更高级的热更新方案,比如使用 Webpack 或者 Babel 等工具结合 Node.js 实现模块级的热更新。这种方案可以只更新修改的模块,而不需要重启整个服务器,性能更好,开发体验也更加流畅。不过,这种方案相对复杂一些,需要对 Webpack 和 Babel 等工具有一定的了解。

通过这次对 Node.js 后端开发的初步探索,我深刻感受到了它的强大和魅力。虽然只是搭建了一个非常基础的 HTTP 服务器,但这其中涉及到的模块使用、路由处理以及热更新等知识,为我们进一步深入学习 Node.js 后端开发奠定了基础。