不出意外的话,本节将是本书最后一章,诸君且看且珍惜。
proxy代理
上节我们提到,当前的部署环节中,仍有可能制约我们后续迭代上线的因素。它会是什么呢?
你可曾注意我说,deno cache那一句的作用是为了在容器中缓存依赖。如果你在执行docker-compose build时观察控制台的打印信息,能看到这些依赖的ts文件的下载过程。如果你修改一段代码,再次执行,会发现这个下载会再次重复执行。
你有没有想过,这个下载过程有可能失败?
网络的世界变幻莫测,旦夕间祸福难知。
你可能会大吃一惊,2022年了怎么还会发生这种事情?少年,习惯就好。我们的资源在CDN网站上,这些网站不是一成不变的稳定,尤其是在当今复杂的国际环境下。
Node.js因为是集中式管理,所有包都存放在npm平台上,国内有著名的淘宝镜像服务,可以帮我们以更快更稳定的速度下载依赖。而Deno是与之截然相反的分布式管理,每个网站都能提供资源下载,也与现在的流行程度有关,还没有大厂入场参与,所以没有类似cnpm之类的镜像网站。
Deno官方教程里有段代理的说明:
Deno supports proxies for module downloads and the Web standard fetch API. Proxy configuration is read from environmental variables: HTTP_PROXY, HTTPS_PROXY and NO_PROXY. In case of Windows, if environment variables are not found Deno falls back to reading proxies from registry.
正常来说,如果配置了这个代理服务,所有的依赖下载将会走这个代理,正能满足我们的需求,但如何搭建一个代理服务,没有说明,我提了issue,暂时还没有讨论,后面将持续关注这点。
通常我们写的代理服务器,在deno cache被代理时,HTTP/S请求只能接收到CONNECT请求头,具体url信息是获取不到的,这就限制了我们最多只能开发一个类似于翻墙的代理工具,部署在墙外,作为一个普通的TCP连接中转使用,如果想要缓存供下次连接使用则无头绪。
替代方案
也确实是生产中遇到了这个问题,有段时间每天下午3点到5点,deno.land的CDN非常不稳定,在CICD中下载依赖失败。
正当我一筹莫展时,想到一个骚操作。
直接把依赖的地址换成我的地址不就行了?
比如原来依赖是这样的:
import { anyExceptionFilter } from "https://deno.land/x/oak_exception@v0.0.9/mod.ts";
我将它改成:
import { anyExceptionFilter } from "http://localhost/https/deno.land/x/oak_exception@v0.0.9/mod.ts";
这样,我开发一个Web服务http://localhost,所有的请求都落到它上面,由它将原地址资源缓存下来,这不就相当于一个代理了吗?
当我把自己工程内的依赖地址都更换完毕后,又发现另一件悲催的事情,我仅仅改变自己工程的依赖项是不行的,因为我依赖的A文件,它可能又依赖于B文件,B文件还可能依赖C文件,这些文件的地址都还是原来的,这样的半成品可没有意义。
于是,更骚的操作来了,我把下载的资源内容中的url也全部给修改成我的地址,这样就确保了所有资源请求都会落到我的服务上。
部署服务
Docker
如果你使用Docker部署,可以参考这个docker-compose.yml:
version: '3'
services:
deno_proxy:
image: docker.io/jiqingyun/deno_proxy:2.3.0
ports:
- "2000:80"
volumes:
- ./cache:/app/cache
environment:
- BASE_URL=http://10.100.30.65:2000
这个BASE_URL就是你的服务的Web地址,可以修改为你的具体域名。
在你的工程下,修改deno.jsonc文件,增加一段:
"map": "deno run --allow-read --allow-write https://deno.land/x/deno_proxy@v1.1.0/cli/mod.ts --baseUrl https://你的web地址 --oldPath import_map.json --newPath import_map_proxy.json"
假设你的网站是http://localhost,执行deno task map,会生成import_map_proxy.json文件:
{
"imports": {
"@/": "./src/",
"std/": "http://localhost/https/deno.land/std@0.118.0/",
"deno_class_validator": "http://localhost/https/deno.land/x/deno_class_validator@v1.0.0/mod.ts",
"date_file_log": "http://localhost/https/deno.land/x/date_file_log@v0.2.6/mod.ts",
"deno_mongo_schema": "http://localhost/https/deno.land/x/deno_mongo_schema@v0.10.5/mod.ts",
"ejs": "http://localhost/https/deno.land/x/deno_ejs@v0.2.3/mod.ts",
"nanoid": "http://localhost/https/deno.land/x/nanoid@v3.0.0/mod.ts",
"oak_exception": "http://localhost/https/deno.land/x/oak_exception@v0.1.2/mod.ts",
"oak_nest": "http://localhost/https/deno.land/x/oak_nest@v1.11.0/mod.ts",
"yaml_loader": "http://localhost/https/deno.land/x/yaml_loader@v0.1.0/mod.ts",
"timeago": "http://localhost/https/esm.sh/v78/timeago.js@4.0.2/es2022/timeago.js",
"markdown": "http://localhost/https/deno.land/x/markdown@v2.0.0/mod.ts"
}
}
注意将这个文件添加到Git忽略中。其它生产流程中用到map文件也都换成它,只需要每次在生产前重新生成这个文件即可。
你可能会说,上面用的deno.land/x/deno_prox…这个命令也是CDN的,这在CDN不稳定时不一样没用?没错,所以把它也换成http://localhost/https/deno.land/x/deno_proxy@v1.1.0/cli/mod.ts 就可以了。
Deno
如果想直接使用Deno启动服务,可以这样:
BASE_URL=http://localhost:2000 PORT=2000 deno run --allow-net --allow-env --allow-write --allow-read --unstable https://deno.land/x/deno_proxy@v1.2.0/mod.ts
PS:我后来用Rust重写了一版,Deno版本中有些Bug没有修改。——2023年8月31日
作业
最后一个作业。
如果你用到GitLab的CICD任务流——.gitlab-ci.yml,可能会包含许多任务,比如代码校验、安装依赖、单元测试、构建镜像、部署服务等多个环节。后面的任务可能是要用到前面任务的衍生资源(这里主要指依赖),每次任务都安装一次依赖自然造成了浪费,所以你需要复用这个依赖。应该怎么复用这个依赖呢?除了.gitlab-ci.yml文件,你的Dockerfile文件又将做何修改呢?
有答案可以留言。
总结
一路走来,相信你已经学会怎么使用Deno开发一个逻辑自洽的Web服务。
现在流行前后端分离,纯粹靠服务端渲染的场景并不多了。不要拘泥于本书的框架,运用之妙,存乎一心,比如在数据库的选择上,session可以使用redis数据库,要使用事务,也可以使用MySQL或PostgreSQL等关系型数据库, 一切以实际情况为准。至于服务的集群、负载均衡、监控运维、防范攻击、请求限流等等,读者可自行进阶研究。
本书只是管窥之见,难免疏漏,各方家敬请指正。
再次感谢《一起学Node.js》的作者大神nswbmw,也感谢一直鼓励我支持我的技术专家忠哥。
本文代码地址,有需要的同学自取。
公众号『前端与Deno』会不定期更新一些使用Deno过程中遇到的问题,或者某些场景下的实践记录,欢迎大家关注!