最近在开发过程中遇到了这么一个问题:

现在有一个 Web 项目,前端是使用 Vue.js 开发的,整个前端需要部署到 K8S 上,后端和前端分开,同样也需要部署到 K8S 上,因此二者需要打包为 Docker 镜像。

对前端来说,打包 Docker 就遇到了一个问题:跨域访问问题。

因此一个普遍的解决方案就是使用 Nginx 做反向代理。

一般来说,我们需要在打包时配置一下 nginx.conf 文件,然后在 Dockerfile 里面指定即可。

Dockerfile

首先看下 Dockerfile:

# build stage
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build# production stage
FROM nginx:lts-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/
RUN rm /etc/nginx/conf.d/default.conf \
&& mv /etc/nginx/conf.d/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

一般来说,对于常规的 Vue.js 前端项目,Dockerfile 就这么写就行了。

简单介绍一下:

•第一步,使用 Node.js 镜像,在 Node.js 环境下对项目进行编译,默认会输出到 dist 文件夹下。•第二步,使用新的 Nginx 镜像,将编译得到的前端文件拷贝到 nginx 默认 serve 的目录,然后把自定义的 nginx.conf 文件替换为 Nginx 默认的 conf 文件,运行即可。

反向代理

这里比较关键的就是 nginx.conf 文件了,为了解决跨域问题,我们一般会将后端的接口进行反向代理。

一般来说,后端的 API 接口都是以 api 为开头的,所以我们需要代理 api 开头的接口地址,nginx.conf 内容一般可以这么写:

server {listen       80;server_name  localhost;location /api/ {proxy_pass http://domain.com/api/;proxy_set_header X-Forwarded-Proto $scheme;proxy_set_header Host $http_host;proxy_set_header X-Real-IP $remote_addr;}location / {root   /usr/share/nginx/html;index  index.html index.htm;}location = /50x.html {root   /usr/share/nginx/html;}error_page  404              /404.html;error_page   500 502 503 504  /50x.html;
}

一般来说,以上的写法是没有问题的,proxy_set_header 也把一些 Header 进行设置,转发到后端服务器。

如果你这么写,打包 Docker 之后,测试没有遇到问题,那就完事了。

问题

但我遇到了一个奇怪的问题,某个接口在请求的时候,状态码还是 200,但其返回值总是为空,即 Response Data 的内容完全为空。

但是服务器端看 Log 确实有正常返回 Response,使用 Vue 的 devServer 也是正常的,使用 Postman 来请求也是正常的,但是经过 Nginx 这么一反向代理就不行了,什么 Response 都接收不到。

部署到 Prod 环境之后,浏览器上面可以得到这么个错误:

ERR_INCOMPLETE_CHUNKED_ENCODING

最后经排查,发现后端接口使用时设定了 Transfer-Encoding: chunked 响应头:

Transfer-Encoding: chunked

这是啥?这时候就需要引出 Keep-Alive 的相关问题了。

什么是 Keep-Alive?

我们知道 HTTP 协议采用「请求-应答」模式,当使用普通模式,即非 Keep-Alive 模式时,每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接(HTTP 协议为无连接的协议)。当使用 Keep-Alive 模式(又称持久连接、连接重用)时,Keep-Alive 功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive 功能避免了建立或者重新建立连接。

•HTTP 1.0 中默认是关闭 Keep-Alive 的,需要在 HTTP 头加入Connection: Keep-Alive,才能启用 Keep-Alive•HTTP 1.1 中默认启用 Keep-Alive,如果请求头中加入 Connection: close,Keep-Alive 才关闭。

目前大部分浏览器都是用 HTTP 1.1 协议,也就是说默认都会发起 Keep-Alive 的连接请求了,所以是否能完成一个完整的 Keep-Alive 连接就看服务器设置情况。

启用 Keep-Alive 模式肯定更高效,性能更高。因为避免了建立/释放连接的开销。

Keep-Alive 模式下如何传输数据

Keep-Alive 模式,客户端如何判断请求所得到的响应数据已经接收完成呢?或者说如何知道服务器已经发生完了数据?

我们已经知道了,Keep-Alive 模式发送完数据,HTTP 服务器不会自动断开连接,所有不能再使用返回 EOF(-1)来判断。

那么怎么判断呢?一个是使用 Content-Length ,一个是使用 Transfer-Encoding。

Content-Length

顾名思义,Conent-Length 表示实体内容长度,客户端(服务器)可以根据这个值来判断数据是否接收完成。

由于 Content-Length 字段必须真实反映实体长度,但实际应用中,有些时候实体长度并没那么好获得,例如实体来自于网络文件,或者由动态语言生成。这时候要想准确获取长度,只能开一个足够大的 buffer,等内容全部生成好再计算。但这样做一方面需要更大的内存开销,另一方面也会让客户端等更久。

我们在做 WEB 性能优化时,有一个重要的指标叫 TTFB(Time To First Byte),它代表的是从客户端发出请求到收到响应的第一个字节所花费的时间。大部分浏览器自带的 Network 面板都可以看到这个指标,越短的 TTFB 意味着用户可以越早看到页面内容,体验越好。可想而知,服务端为了计算响应实体长度而缓存所有内容,跟更短的 TTFB 理念背道而驰。但在 HTTP 报文中,实体一定要在头部之后,顺序不能颠倒,为此我们需要一个新的机制:不依赖头部的长度信息,也能知道实体的边界。

但是如果消息中没有 Conent-Length,那该如何来判断呢?又在什么情况下会没有 Conent-Length 呢?

Transfer-Encoding

当客户端向服务器请求一个静态页面或者一张图片时,服务器可以很清楚地知道内容大小,然后通过 Content-length 消息首部字段告诉客户端需要接收多少数据。但是如果是动态页面等时,服务器是不可能预先知道内容大小,这时就可以使用 分块编码模式来传输数据了。即如果要一边产生数据,一边发给客户端,服务器就需要在请求头中使用Transfer-Encoding: chunked 这样的方式来代替 Content-Length,这就是分块编码。

分块编码相当简单,在头部加入 Transfer-Encoding: chunked 之后,就代表这个报文采用了分块编码。这时,报文中的实体需要改为用一系列分块来传输。每个分块包含十六进制的长度值和数据,长度值独占一行,长度不包括它结尾的 CRLF(\r\n),也不包括分块数据结尾的 CRLF。最后一个分块长度值必须为 0,对应的分块数据没有内容,表示实体结束。

回归问题

那么我说了这么一大通有什么用呢?

OK,在我遇到的业务场景中,我发现服务器的响应头中就包含了Transfer-Encoding: chunked 这个字段。

而这个字段,在 HTTP 1.0 是不被支持的。

而 Nginx 的反向代理,默认用的就是 HTTP 1.0,那就导致了数据无法获取的问题,可以参考 Nginx 的官方文档说明:http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass。

原文中:

Syntax: proxy_http_version 1.0 | 1.1;
Default: proxy_http_version 1.0;
By default, version 1.0 is used. Version 1.1 is recommended for use with keepalive connections and NTLM authentication.

所以,我们如果要解决这个问题,只需要设置一下 HTTP 版本为 1.1 就好了:

修改 nginx.conf 文件如下:

location /api/ {proxy_pass http://domain.com/api/;proxy_http_version 1.1;proxy_set_header X-Forwarded-Proto $scheme;proxy_set_header Host $http_host;proxy_set_header X-Real-IP $remote_addr;
}

这里就增加了一行:

proxy_http_version 1.1;

这样再测试,反向代理就会支持 Transfer-Encoding: chunked 模式了,这也就呼应了之前在浏览器中遇到的 ERR_INCOMPLETE_CHUNKED_ENCODING 错误。

自此,问题完美解决。

复盘记录

一开始本来只想简单一记录就了事的,但一边写,发现某个地方还可以展开写得更详细。

所以干脆最后我对这个问题进行了详细的复盘和记录。在写本文之前,我其实只思考到了 Keep-Alive 和 HTTP 1.1 的问题,其实我对 Transfer-Encoding 这个并没有去深入思考。在边写边总结的过程中,为了把整个脉络讲明白,我又查询了一些 Transfer-Encoding 和 Nginx 的官方文档,对这块的了解变得更加深入,相当于我在整个记录的过程中,又对整个流程梳理了一遍,同时又有额外的收获。

所以,遇到问题,深入去思考、总结和复盘,是很有帮助的,这会让我们对问题的看法和理解更加透彻。

怎么说呢?在开发过程中,难免会遇到一些奇奇怪怪的 Bug,但这其实只是技术问题,总会解决的。

但怎样在开发过程中,不断提高自己的技术能力,我觉得需要从每一个细节出发,去思考一些事情的来龙去脉。思考得越多,我们对整个事件的把握也会越清晰,以后如果再遇到类似的或者关联的事情,就会迎刃而解了。

平时我们可能很多情况下都在写业务代码,可能比较枯燥,感觉对技术没有实质性的提升,但如果我们能从中提炼出一些核心的问题或解决方案,这才是能真正提高技术的时候,这才是最有价值的。

参考文章

本文部分内容改写或摘自下列内容。

•HTTP Keep-Alive模式:https://www.cnblogs.com/skynet/archive/2010/12/11/1903347.html•Nginx proxy_set_header 理解:https://www.jianshu.com/p/cc5167032525•使用 Docker 打造超溜的前端环境:https://github.com/axetroy/blog/issues/178•  HTTP 协议中的 Transfer-Encoding:https://imququ.com/post/transfer-encoding-header-in-http.html

作者:华为云享专家 崔庆才丨静觅

从项目实际问题引发的思考相关推荐

  1. 外卖cps分销公众号小程序淘客项目,引发的思考(附0基础搭建源码)

    关注公众号的人都可以看到我之前发的关于外卖CPS项目的一篇实操文档,也不需要付费什么的就可以很清楚的了解到这个项目的概况以及一些实操教程.(当然很多朋友在来找我问问题的时候还都给我发了红包,非常感谢) ...

  2. 由SecureCRT引发的思考和学习

    由SecureCRT引发的思考和学习 http://mp.weixin.qq.com/s?__biz=MzAxOTAzMDEwMA==&mid=2652500597&idx=1& ...

  3. Octavia API接口慢问题排查引发的思考

    女主宣言 文本梳理了Octavia API接口访问慢问题的排查过程和解决方案,并对排查过程中涉及到的相关知识点进行了梳理,希望日后遇到类似的问题可以有所借鉴和参考. PS:丰富的一线技术.多元化的表现 ...

  4. 对软件项目开发的一点思考

    今天看到同事写的一些思考,感觉还不错,真的是通过这个项目让他成长起来了. 目录 I 1 引言 1 2 概念 1 3 国内软件项目角色分析 1 4 国内项目的一般性问题 2 5 客户与项目组对需求的认知 ...

  5. 一个分组查询引发的思考

    一个分组查询引发的思考 我们在看项目代码或者SQL语句时, 往往会看到很多非常复杂的业务或者SQL 那么问题来了. 复杂SQL是如何写成的? 下面通过一个数据展示的需求来体会到复杂的SQL是如何书写的 ...

  6. 阿里云无影云电脑初体验及引发的思考

    有幸尝试阿里无影云电脑,记录下使用过程,并对云电脑进行思考. ​ 无影是什么 阿里云无影云桌面( Elastic Desktop Service)的原产品名为弹性云桌面,融合了无影产品技术后更名升级. ...

  7. 腾讯「小借条」引发的思考:区块链+的商业模式让各企业争先恐后的奥秘

    区块链+又再一次被推到我们面前. 原文标题:<区块链+万物?腾讯"小借条"引发的思考>,作者陈丽姗 Key Takeaways: 腾讯区块链新品"小借条&qu ...

  8. JAVA-OPTS引发的思考

    JAVA-OPTS引发的思考 我们在性能测试过程中,经常要修改应用的JAVA-OPTS参数.修改这些参数,不单单是修改这些数字,本着知其所以然的态度,我们要知道这些参数背后的意义. 常见的JAVA-O ...

  9. 一个小程序引发的思考

    既然是一个小程序引发的思考,那么我们就先看看这个小程序,看看他有何神奇之处: namespace ConsoleApplication1 {class Program{static void Main ...

最新文章

  1. Bash中的逻辑运算
  2. Binder通信机制介绍
  3. vue中引用js_从JS中的内存管理说起 —— JS中的弱引用
  4. linux线程间通信优点,进程间通信与线程间通信【转】
  5. PAT乙级资料集-2022.04.06
  6. Linux——更改文件及目录权限(d rwx r-x r-x字段详解+更改代码指令)
  7. 程序人生 | 35岁以上的 iOS 程序员都到哪里去了?
  8. UVA 299 - Train Swapping(冒泡排序)
  9. 美图秀秀计算机教程,美图秀秀怎么抠图 美图秀秀抠图详细教程
  10. opencv保存视频文件很大
  11. 什么是web前端?前端可以做什么?html5有什么用?
  12. JS实现动态生成表格
  13. 基于体素化方法的点云降采样
  14. 中科院,量子计算机,中科院传来喜讯,中国量子技术领先世界,美:中国学者都不睡觉吗...
  15. 数据结构中二叉树的度
  16. 12v电源正负极区分_12V或者24V变压器输出端的电源线怎样区分正负极?
  17. 2021-07-17答疑解惑第二期
  18. MySQL基础部分学习笔记
  19. codeforces131B
  20. 简易式前端响应式期末小设计【电子商务】首页部分

热门文章

  1. 编码器的一点微小认识
  2. php接收get参数false是字符串,php怎么接收url参数
  3. 【存档】MySQL(8.0.12 .msi)安装文档
  4. yarn-cli 缓存(转)
  5. python 面试题2
  6. canvas绘制图形
  7. C#在类型实例化时都干了什么:从一道笔试题说开去
  8. js 页面载入时的执行顺序
  9. 电子书链接的集合 (不断更新)
  10. 1小时教你学会正则表达式