Nginx 反向代理返回结果为空的问题

更新日期: 2019-11-07阅读: 3.2k标签: 代理

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

现在有一个 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 功能避免了建立或者重新建立连接。

Connection: Keep-Alive
Connection: close

目前大部分浏览器都是用 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(rn),也不包括分块数据结尾的 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,但这其实只是技术问题,总会解决的。

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

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

原文 https://cuiqingcai.com/8443.html

链接: https://fly63.com/article/detial/6765

什么是在线代理ip网页代理

当我们需要大量IP进行快节奏完成业绩的时候,很多人都会想到去IP代理服务商那里购买IP代理,所以我相信很多人对于IP代理这个词已经有一定的认识了,那么还有一个词叫做:在线代理ip网页代理

centos7下搭建高匿HTTP代理

一般适用情况:1、两台都有外网IP,一台服务器请求资源通过另外一个服务器,本文重点讲第一种。2、两台服务器,其中一台服务器只有内网IP,另外一台服务器有公网和内网IP。

.Net Core/Framework之Nginx反向代理后获取客户端IP等数据探索

公司项目最近出现获取访问域名、端口、IP错误现象,通过排查发现, 之前项目一直通过Nginx自定义Headers信息来获取,但最近运维人员失误操作造成自定义Header信息丢失,造成项目拿不到对应的数据。

反向代理和内网穿透

反向代理看上去看深奥,其实不然,只是因为汉语言文化的差异导致它看上去深奥。一般反派感觉都比较厉害和神秘。要理解反向代理,我们就不得不说一下正向代理。正向代理代理的对象是客户端;反向代理代理的对象是服务端

反向代理Cloudflare加速网站(SNIproxy)

写在教程前:为什么要反向代理cloudflare?答:缩短路由,加快cloudflare节点到大陆用户的速度,用过cloudflare的用户应该知道,这家CDN的速度在除了大陆以外的地方访问都非常快,那么又没有什么办法使其对大陆访问良好呢?

ES6中的代理(Proxy)和反射(Reflection)

调用 new Proxy() 可常见代替其它目标 (target) 对象的代理,它虚拟化了目标,所以二者看起来功能一致。代理可拦截JS引擎内部目标的底层对象操作,这些底层操作被拦截后会触发响应特定操作的陷阱函数。

Vue多环境代理配置

多人协作模式下,修改代理比较麻烦,而且很容易某个开发人员会修改了vue.config.js文件后提交了。第一,很容易引起冲突。 第二,很容易出现代理错误,需要排查。而且现在微服务盛行

node.js代理访问

本地开发,代理访问,防止跨域(一般通过webpack配置代理即可),特殊情况如携带一些自定义的登录cookie则需要通过自己写node,作为一种server中间层,单线程异步可以缓解服务器压力

vue proxy代理跨域

changeOrigin的属性值为一个布尔值,如果设置为true,那么本地会虚拟一个NODE服务端接收你的请求并代你发送该请求(中间件)。[本质上是本地开了一个服务器dev-server,所有的请求都通过这里转发出去。]

Nginx反向代理之动静分离

我们已经知道了什么是正向代理与反向代理,这次我们就讲一下Nginx的动静分离的案例,其实质运用的就是反向代理,专门用一台服务器代理服务器上的图片资源。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!