走进node.js - Web

123 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情

往期推荐

# 走进 node.js
# 走进 node.js - 起步
# 走进 node.js - 模块
# 走进node.js - NPM
# 走进node.js - package.json
# 走进node.js - 文件
# 走进node.js - 服务端开发
# 走进node.js - 服务端开发2

走进node.js - Web 学习目标

  • 理解 HTTP 协议概念
  • 掌握 http 模块的基本使用

请求没有得到响应

// 0. 加载 http 核心模块
const http = require("http");

// 1. 创建服务器,得到 Server 实例
const server = http.createServer();

// 2. 监听客户端的 request 请求事件,设置请求处理函数
server.on("request", (request, response) => {
  // request.header
  console.log("收到客户端的请求了");
});

// 3. 绑定端口号,启动服务器
//    真正需要通信的应用程序
//    如何从 a 计算机的 应用程序 通信到 b 计算机的 应用程序
//    ip 地址用来定位具体的计算机
//    port 端口号用来定位具体的应用程序
//    联网通信的应用程序必须占用一个端口号,同一时间同一个端口号只能被一个应用程序占用
//    开发测试的时候使用一些非默认端口,防止冲突
server.listen(3000, function() {
  console.log("Server is running at port 3000.");
});

服务器loser

  • Node 服务器不同于 APache,默认能力非常的简单,一切请求都需要自己来处理。
// 0. 加载 http 核心模块
const http = require("http");

// 1. 创建服务器,得到 Server 实例
const server = http.createServer();

// 2. 监听客户端的 request 请求事件,设置请求处理函数
//    req 请求对象(获取客户端信息)
//    res 响应对象(发送响应数据)
//      end() 方法
server.on("request", (req, res) => {
  // 发送响应数据
  // res.write('hello')
  // res.write(' hello')
  // res.write(' hello')
  // res.write(' hello')
  // res.write(' hello')
  // res.write(' hello')
  // res.write(' hello')

  // 数据写完之后,必须告诉客户端,我的数据发完了,你可以接收处理了
  // 否则客户端还是会一直等待
  // 结束响应,挂断电话
  // res.end()

  const client = req.socket;

  // 推荐
  res.end(`
    您的 ip 地址:${client.remoteAddress}
    您的 port 端口号:${client.remotePort}
`);
});

// 3. 绑定端口号,启动服务器
//    真正需要通信的应用程序
//    如何从 a 计算机的 应用程序 通信到 b 计算机的 应用程序
//    ip 地址用来定位具体的计算机
//    port 端口号用来定位具体的应用程序
//    联网通信的应用程序必须占用一个端口号,同一时间同一个端口号只能被一个应用程序占用
//    开发测试的时候使用一些非默认端口,防止冲突
server.listen(3000, function() {
  console.log("Server is running at port 3000.");
});

根据不同 url 地址处理不同请求

网站中的资源都是通过 url 地址来定位的,所以我就可以在请求处理函数获取客户端的请求地址,然后根据不同的请求地址处理不同的响应。

// 0. 加载 http 核心模块
const http = require("http");

// 1. 创建服务器,得到 Server 实例
const server = http.createServer();

// 2. 监听客户端的 request 请求事件,设置请求处理函数
//    req 请求对象(获取客户端信息)
//    res 响应对象(发送响应数据)
//      end() 方法
// 任何请求都会触发 request 请求事件
// /a /b /c /dsanjdasjk
// req 请求对象中有一个属性:url 可以获取当前客户端的请求路径
server.on("request", (req, res) => {
  // console.log(req.url)
  // 127.0.0.1:3000/abc
  // 一切请求路径都始终是以 / 开头
  // / index page
  // /login login page
  // /about about me
  // 其它的 404 Not Found.
  // res.end('index page')

  const url = req.url;

  // 通常情况下,都会把 / 当作首页
  // 因为用户手动输入地址,不加任何路径,浏览器会自动补上 / 去请求
  if (url === "/") {
    console.log("首页");
    res.end(`
<h1>首页</h1>
<ul>
<li>
  <a href="/login">登陆</a>
</li>
<li>
  <a href="/reg">注册</a>
</li>
</ul>
`);
  } else if (url === "/login") {
    console.log("登陆");
    res.end("login page");
  } else if (url === "/reg") {
    console.log("注册");
    res.end("reg page");
  } else {
    console.log("404 不认识");
    res.end("404 Not Found.");
  }
});

server.listen(3000, function() {
  console.log("Server is running at port 3000.");
});

解决中文乱码问题

  • Content-Type
  • html 文件中的 <meta charset="UTF-8" />
    • html 文件需要如果声明了 meta-charset 则可以不写 Content-Type
  • 建议每个响应都告诉客户端我给你发送的 Content-Type 内容类型是什么

处理页面中的多个请求

/**
 * http 结合 fs 发送文件内容
 */

const http = require("http");
const fs = require("fs");

const server = http.createServer();

server.on("request", (req, res) => {
  const url = req.url;
  console.log(url);
  if (url === "/") {
    fs.readFile("./views/index.html", (err, data) => {
      if (err) {
        return res.end("404 Not Found.");
      }
      // 响应数据类型只能是:字符串 和 二进制数据
      // TypeError: First argument must be a string or Buffer
      // res.end(123)

      res.setHeader("Content-Type", "text/html; charset=utf-8");
      res.end(data);
    });
  } else if (url === "/css/main.css") {
    fs.readFile("./views/css/main.css", (err, data) => {
      if (err) {
        return res.end("404 Not Found.");
      }
      // 响应数据类型只能是:字符串 和 二进制数据
      // TypeError: First argument must be a string or Buffer
      // res.end(123)

      res.setHeader("Content-Type", "text/css; charset=utf-8");
      res.end(data);
    });
  } else if (url === "/js/main.js") {
    fs.readFile("./views/js/main.js", (err, data) => {
      if (err) {
        return res.end("404 Not Found.");
      }
      // 响应数据类型只能是:字符串 和 二进制数据
      // TypeError: First argument must be a string or Buffer
      // res.end(123)

      res.setHeader("Content-Type", "application/x-javascript; charset=utf-8");
      res.end(data);
    });
  } else if (url === "/img/ab2.jpg") {
    fs.readFile("./views/img/ab2.jpg", (err, data) => {
      if (err) {
        return res.end("404 Not Found.");
      }
      // 响应数据类型只能是:字符串 和 二进制数据
      // TypeError: First argument must be a string or Buffer
      // res.end(123)

      // 只有文本类型需要加 charset 编码
      // 图片不是文本,所以不用加编码
      res.setHeader("Content-Type", "image/jpeg");
      res.end(data);
    });
  }
});

server.listen(3000, () => {
  console.log("running...");
});

基本路由

路由(Routing)是由一个 URI(或者叫路径标识)和一个特定的 HTTP 方法(GET、POST 等)组成的,涉及到应用如何处理响应客户端请求。

每一个路由都可以有一个或者多个处理器函数,当匹配到路由时,这个/些函数将被执行。

路由的定义的结构如下:

app.METHOD(PATH, HANDLER);

其中:

  • app 是 express 实例
  • METHOD 是一个 HTTP 请求方法
  • PATH 是服务端路径(定位标识)
  • HANDLER 是当路由匹配到时需要执行的处理函数

基本示例

// 当你以 GET 方法请求 / 的时候,执行对应的处理函数
app.get("/", function(req, res) {
  res.send("Hello World!");
});

中间件

image.png Express 的最大特色,也是最重要的一个设计,就是中间件。一个 Express 应用,就是由许许多多的中间件来完成的。

为了理解中间件,我们先来看一下我们现实生活中的自来水厂的净水流程。

在上图中,自来水厂从获取水源到净化处理交给用户,中间经历了一系列的处理环节,我们称其中的每一个处理环节就是一个中间件。这样做的目的既提高了生产效率也保证了可维护性。

一个简单的中间件例子:打印日志

app.get("/", (req, res) => {
  console.log(`${req.method} ${req.url} ${Date.now()}`);
  res.send("index");
});

app.get("/about", (req, res) => {
  console.log(`${req.method} ${req.url} ${Date.now()}`);
  res.send("about");
});

app.get("/login", (req, res) => {
  console.log(`${req.method} ${req.url} ${Date.now()}`);
  res.send("login");
});

在上面的示例中,每一个请求处理函数都做了一件同样的事情:请求日志功能(在控制台打印当前请求方法、请求路径以及请求时间)。