在本地开发调试环境使用 HTTPS

avatar
技术支持 @LeanCloud

作者 LeanCloud weakish

原则上,本地开发调试环境和线上生产环境的差异越小越好。由于目前大多数站点(无论是直接供用户访问的网站还是提供后端 API 的站点)使用的都是 HTTPS,所以在本地开发调试环境也使用 HTTPS 很有必要。它不仅有助于尽早发现和 HTTPS 相关的一些问题(比如代码中不小心请求了 HTTP 资源,由于本地调试阶段使用 HTTP 协议,所以没有发现,上线后因为混合内容被浏览器阻塞,网站显示异常甚至不能正常工作),也有助于避免一些客户端的限制(比如通过局域网访问站点,测试站点的 service worker 功能)。

其实在本地配置 HTTPS 非常快捷方便,有现成的成熟工具可以使用。大致分两步:

  1. 生成证书。
  2. 修改项目的启动服务。

生成证书

我们将使用 mkcert 这个零配置的命令行工具生成证书。

首先安装 mkcert。macOS 下可以使用 Homebrew 安装,其他系统请参考 mkcert 的文档

brew install mkcert
brew install nss

其中,nss 是可选的,如果不使用或者不需要测试 Firefox,那么可以不安装 nss

接着我们创建一个目录来存放证书,比如 ~/.cert

mkdir -p ~/.cert

自动生成证书:

mkcert -key-file ~/.cert/key.pem -cert-file ~/.cert/cert.pem "localhost"

让系统信任生成的证书:

mkcert -install

因为需要在系统中安装本地 root CA,所以运行上述命令会请求 sudo 权限。只有初次生成证书时需要运行这个命令,后续通过 mkcert -key-file 生成的证书会自动被系统信任。

修改项目的启动服务

许多框架都提供了几乎是开箱即用的 HTTPS 支持,比如在启动命令时加上相应的命令行参数或是环境变量,或者在启动服务的代码中进行简单的配置。下面就以前端开发比较流行的http-server、React、Express 为例(三者正好对应了前述三种方式),介绍如何在本地开发调试时启动 HTTPS 服务。

http-server

纯静态项目可以通过 http-server 进行调试,要切换至 HTTPS 模式,只需加上三个命令行参数:

npx http-server -S -C ~/.cert/cert.pem -K ~/.cert/key.pem

React

通过 Create React App 创建的 React 项目,只需在启动服务时指定几个环境变量就可以切换至 HTTPS 模式:

npx create-react-app example
cd example
HTTPS=true SSL_CRT_FILE=$HOME/.cert/cert.pem SSL_KEY_FILE=$HOME/.cert/key.pem npm start

1

为了省去每次启动都需要在前面加上这几个环境变量的麻烦,可以直接修改 package.jsonscripts.start 项:

"start": "HTTPS=true SSL_CRT_FILE=$HOME/.cert/cert.pem SSL_KEY_FILE=$HOME/.cert/key.pem react-scripts start",

这样,以后只需运行 npm start 即可启动 HTTPS 服务。

Express

相比上述两个项目,Express 稍微麻烦一点点,需要简单修改启动服务代码。

首先我们创建项目目录并安装 expressjs:

mkdir example
cd example
npm init -y
npm i express

然后编写以下简单的启动 HTTPS 服务的示例代码:

const express = require('express')
const app = express()
const https = require('https')
const fs = require('fs')
const os = require('os')
const path = require('path')
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

https.createServer({
    key: fs.readFileSync(path.join(os.homedir(), '.cert/key.pem')),
    cert: fs.readFileSync(path.join(os.homedir(), '.cert/cert.pem'))
}, app).listen(port, () => {
  console.log(`Example app listening at https://localhost:${port}`)
})

可以看到,和 Express 官网上的 Hello World 示例代码相比,我们另外引入了 https 包,并通过 createServer 方法创建了 https 服务,并指定了证书路径为之前生成的本地证书。

2

反向代理

线上生产环境常见的一种架构是在 HTTP 服务前加一层反向代理,HTTPS 加解密在反向代理层面完成。如果项目的生产环境使用这一架构,那么为了最大限度地与生产环境保持一致,本地开发调试时也可以采取类似的架构,比如使用 Caddy 充当反向代理服务器。

macOS 下 Caddy 可以通过 Homebrew 安装,其他系统请参考 Caddy 文档

brew install caddy

正常启动 HTTP 服务后,只需一行命令即可加上反向代理(这里假定服务的端口是 3000):

caddy reverse-proxy --from localhost --to localhost:3000

通过 https://localhost 即可访问服务:

3

Caddy 会自动生成证书,获取系统信任,无需另行生成证书,也无需修改项目的启动服务。

结语

为了保证本地开发调试环境和线上生产环境尽可能一致,在本地也使用 HTTPS 很有必要。这里介绍了两种在本地开发调试阶段使用 HTTPS 的方法,实施起来都非常快捷方便,可以根据项目的具体情况和个人偏好选用。当然,除了在这两种解决问题的方案中选择,也可以选择直接消灭问题,在线上环境进行调试。许多 serverless 云服务都提供线上调试功能,比如 LeanCloud 的云引擎提供和生产环境几乎完全一样的预备环境,可以自动获取项目 git 仓库的更新并部署到预备环境,调试完成、测试通过后可以一键发布至生产环境,免去在本地搭建环境、配置工具的麻烦。