Web前端高级工程师培训:使用 Node.js 构建一个 Web 服务端程序(3)

11、HTTP 协议

11-1、协议的定义

HTTP 是一种能够获取如 HTML 这样的网络资源的 protocol(通讯协议)。它是在 Web 上进行数据交换的基础,是一种 client-server 协议,也就是说,请求通常是由像浏览器这样的接受方发起的。一个完整的Web文档通常是由不同的子文档拼接而成的,像是文本、布局描述、图片、视频、脚本等等。(来源:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Overview)。

无论是客户端请求还是服务端响应,本质就是在交换数据。因为交换的数据的多样性,为了能够让双方正确的理解和处理交换的数据,就需要给这些数据定义一些交换的方式和格式,这就是 HTTP 协议的基本作用。

一些参考资料:

11-2、报文

报文 可以理解为交换的数据,在 HTTP 协议中规定了交换的格式,报文分为:

  • 请求报文
  • 响应报文
请求报文
响应报文
HTTP Headers

HTTP Headers 允许客户端和服务端在请求和响应的时候携带一些附加信息,客户端会根据不同的头信息字段和对应内容做出不同的操作。根据不同的上下文环境,HTTP Headers 可分为:

参考:https://tools.ietf.org/html/rfc4229#section-2.1.32

12、不同类型资源的处理

无论是请求还是响应,都会涉及到各种不同类型的资源要进行处理。为了能够让接收方(注:请求的接收方是服务端、响应的接收方是客户端)知道当前接收到内容类型,以便针对该类型进行对应的处理,所以需要在发送正文数据的同时需要同时告知该正文内容的类型格式。

12-1、使用 content-type 响应头返回资源类型

Content-Type 用于说明请求或相应中正文内容的 MIME 类型。

12-2、MIME 的作用与使用

MIME(Multipurpose Internet Mail Extensions) : 是一种用来表示 文档、文字或字节流的性质和格式的标准。

结构

MIME 的通用结构为 : type/subtypetype 为大类,subtype 为小类,如:text/htmltext/cssimage/png 等。

参考:https://www.iana.org/assignments/media-types/media-types.xhtml

12-3、设置响应头

使用 http 模块提供的 setHeader 方法就可以向响应中设置对应的头信息。

javascript 复制代码
// #C12-3-1
...
server.on('request', (req, res) => {
  res.setHeader('Content-Type', 'text/html;charset="utf-8"');
});
...

13、动态资源

许多时候,服务端还可能会提供一些动态处理的资源,比如会根据当前时间,当前客户端访问用户的身份权限等条件进行一些逻辑处理后返回对应的资源,这个时候,针对同一个 URL 的请求可能会产生不一样的结果,这种资源我们可以成为:动态资源

13-1、使用 URL 路由表优化动态资源处理

因为动态资源的特性,资源并不能像静态资源一样简单的存储在一个外部文件中,然后使用一个 URL 与之关联。所以,针对这种资源,通常情况下,我们需要对不同的 URL 进行不同的处理。

  • 把不同的 URL 对应的一些逻辑分别保存(通常是函数)。
  • 准备一个路由表(可以使用对象格式进行存储,key是 URL,value是对应的函数)。
  • 根据当前访问的 URL ,找到路由表中的对应的函数并执行。
javascript 复制代码
// #C13-1-1
...
// 路由表
const routesMap = new Map();
routesMap.set('/', async (req, res) => {
  res.setHeader('Content-Type', 'text/html;charset="utf-8"');
  res.end('首页');
});
routesMap.set('/list', async (req, res) => {
  res.setHeader('Content-Type', 'text/html;charset="utf-8"');
  res.end('列表');
});

server.on('request', async (req, res) => {
  ...
  const urlObj = new URL(req.url);
  ...
  
  const pathname = urlObj.pathname;
  
  // 根据当前的 pathname 指定 routeMap 中对应的函数
  let routeHandler = routesMap.get(pathname);
  if (routeHandler) {
      await routeHandler(req, res);
  }
});
...

13-2、URL 重定向

我们通过一个 重定向 的需求应用场景来加深一下关于协议(如 http 头)等相关知识的理解

需求 : 访问不存在的 URL ,重新定向到 / 路由。

13-2-1、HTTP 状态码

我们通常在访问出现一些问题的时候返回一个友好的页面(或数据)给客户端,但是客户端仅仅从页面(数据)本身是无法知道这个页面(或数据)是这次请求本身应该对应的数据,还是我们给他的一个错误提示,这个时候就可以在响应的同时返回一个称为:状态码 的内容给客户端,它由一组具有特定含义的数字组成:

  • 信息响应(100--199)
  • 成功响应(200--299)
  • 重定向(300--399)
  • 客户端错误(400--499)
  • 服务器错误 (500--599)

注:虽然服务端可以随意返回指定的状态码,但是我们应当按照实际的情况返回标准中定义好的状态码。

参考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status

参考:https://tools.ietf.org/html/rfc2616#section-10

javascript 复制代码
// #C13-2-1-1
...
// 路由表
...

server.on('request', async (req, res) => {
  ...
  const urlObj = new URL(req.url);
  ...
  
  const pathname = urlObj.pathname;
  
  // 根据当前的 pathname 指定 routeMap 中对应的函数
  let routeHandler = routesMap.get(pathname);
  if (routeHandler) {
      await routeHandler(req, res);
  } else {
    // 告知客户端你应该重新发送请求,新的请求地址在 Location 头中。
    res.statusCode = 302;
    res.setHeader('Location', '/');
    res.end();
  }
});
...

13-3、404 Not Found

404 也是我们经常听到的一个词,它其实就是状态码中的一种

404 : 请求失败,请求所希望得到的资源未被在服务器上发现。

javascript 复制代码
// #C13-3-1
...
// 路由表
...

server.on('request', async (req, res) => {
  ...
  const urlObj = new URL(req.url);
  ...
  
  const pathname = urlObj.pathname;
  
  // 根据当前的 pathname 指定 routeMap 中对应的函数
  let routeHandler = routesMap.get(pathname);
  if (routeHandler) {
      await routeHandler(req, res);
  } else {
    // 返回一个404的状态码与提示页面
    res.statusCode = 404;
    res.end('<h1>页面丢失了</h1>');
  }
});
...

14、后端渲染

14-1、数据与视图

除了一些静态文件数据以外,服务端还会通过诸如:数据库程序运算 等各种方式产生数据,同时我们又需要把这些数据通过页面(HTML,CSS)的方式进行组织(也就是试图)返回给客户端。

javascript 复制代码
// #C14-1-1
...

let users = [
  {id: 1, username: '大海'},
  {id: 2, username: '子鼠'},
  {id: 3, username: '小蕊'},
];

// 路由表
const routesMap = new Map();
routesMap.set('/', async (req, res) => {
  res.setHeader('Content-Type', 'text/html;charset="utf-8"');
  res.end(`
      <ul>
        ${users.map(u => {
          return `<li>${u.username}</li>`
        }).join('')}
      </ul>
  `);
});
...

server.on('request', async (req, res) => {
  ...
});
...

14-2、数据与视图分离

如果视图内容直接写在后端程序中,后端程序不好维护,且前端不好配合。

我们可以把前端相关的内容提取到外部文件中,在利用 Node.jsfs 模块进行文件读取,并进行一些简单的替换。

javascript 复制代码
// #C14-2-1
...
const fs = require('fs');

// 数据
...

// 路由表
const routesMap = new Map();
routesMap.set('/', async (req, res) => {
  res.setHeader('Content-Type', 'text/html;charset="utf-8"');
  let userListHtml = users.map(u => {
          return `<li>${u.username}</li>`
        }).join('');
  res.end( fs.readFileSync('./template/index.html').toString().replace(/${users}/gi, userListHtml) );
});
...

server.on('request', async (req, res) => {
  ...
});
...

./template/index.html

html 复制代码
// #C14-2-2
...
<ul>${users}</ul>
...

模板 : 未被处理过的原始文件(如这里的:./template/index.html)。

视图 : 通过模板与数据和逻辑的处理生成的最终内容。

14-3、使用模板引擎优化渲染

以上的模板数据解析替换方式太简单,复杂一些的视图逻辑就不太方便了。这个时候我们就可以使用一些 模板引擎(功能更加强大的模板解析替换库) 来完成解析工作。

14-3-1、基于 JavaScript 的常用模板引擎
  • ejs
  • artTemplate
  • Nunjucks
14-3-2、Nunjucks

参考:http://mozilla.github.io/nunjucks/

15、案例:基于 Node.js 构建一个商城应用

相关推荐
Gnevergiveup8 分钟前
2024网鼎杯青龙组Web+Misc部分WP
开发语言·前端·python
你不讲 wood27 分钟前
使用 Axios 上传大文件分片上传
开发语言·前端·javascript·node.js·html·html5
Iqnus_1231 小时前
vue下载安装
前端·vue.js·笔记
tryCbest1 小时前
Nodejs安装配置及创建vue项目
vue.js·node.js
网安_秋刀鱼1 小时前
CSRF防范及绕过
前端·安全·web安全·网络安全·csrf·1024程序员节
新知图书1 小时前
Django 5企业级Web应用开发实战-分页
前端·python·django
GDAL2 小时前
HTML入门教程8:HTML格式化
前端·html
清灵xmf2 小时前
UI 组件的二次封装
前端·javascript·vue.js·element-plus
聪明的墨菲特i2 小时前
Vue组件学习 | 二、Vuex组件
前端·vue.js·学习·前端框架·1024程序员节
海盗12342 小时前
Web前端开发工具和依赖安装
前端