用例:
- 使用tinyproxy-1.8.3做代理
- php访问soap服务时,使用tinyproxy做代理访问http地址(不是https)
- http响应数据有点儿多,走的是Transfer-Encoding: chunked
问题:
- tinyproxy代理之后,响应头中 HTTP协议版本号为1.1,没有content-length, 也没有connection: close ,也没有Transfer-Encoding: chunked,对于这种尴尬的情况,client端就不方便处理了; curl会有如下警告: no chunk, no close, no size. Assume close to signal end
- 真实原因: curl虽然使用HTTP/1.1方式发送请求,但是tinyproxy对于所有http请求都以HTTP/1.0的方式转发(但是添加了Connection: close 头),最终的openresty(nginx行为也如此)却无视HTTP/1.0,执意返回HTTP/1.1 响应,并且使用Connection: close; tinyproxy直接透传HTTP/1.1 状态行,却丢弃了响应头中的Connection: close;(或许是tinyproxy以为自己总是工作在HTTP/1.0,所以不需要Connection: close 吧)
tinyproxy转发的请求:(发给nginx,nginx再代理转发给后面的openresty)
1 2 3 4 5 |
GET / HTTP/1.0 Host: op-phpor.i.hrbbwx.com Connection: close Accept: */* User-Agent: curl/7.29.0 |
openresty收到的请求:
1 2 3 4 5 |
GET / HTTP/1.0 Host: op-phpor.i.hrbbwx.com Connection: close Accept: */* User-Agent: curl/7.29.0 |
openresty响应:
1 2 3 4 5 |
HTTP/1.1 200 OK Server: openresty/1.9.15.1 Date: Sat, 05 Nov 2016 02:51:16 GMT Content-Type: text/html; charset=UTF-8 Connection: close |
openresty是藏在一个nginx后面的,nginx的响应:
1 2 3 4 5 |
HTTP/1.1 200 OK Server: nginx/1.7.4 Date: Sat, 05 Nov 2016 02:51:16 GMT Content-Type: text/html; charset=UTF-8 Connection: close |
分析:
原本HTTP/1.0 是不支持Host头的,但是很多client还是添加了,很多server也不介意(甚至喜欢)HTTP/1.0协议中使用Host头,参考: http://stackoverflow.com/questions/246859/http-1-0-vs-1-1
Connection 头也是http1.1中定义的,但是tinyproxy也用在了HTTP/1.0中了(难道为了兼容一些不守规矩的server端?),在HTTP/1.0和Connection头的暗示下,server端没有使用Content-length 或 Transfer-encoding也可以理解,也或许正式Connection头的存在,server端才选择响应HTTP/1.1
tinyproxy 源码片段:
tiny proxy发送请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
static int establish_http_connection (struct conn_s *connptr, struct request_s *request) { char portbuff[7]; char dst[sizeof(struct in6_addr)]; /* Build a port string if it's not a standard port */ if (request->port != HTTP_PORT && request->port != HTTP_PORT_SSL) snprintf (portbuff, 7, ":%u", request->port); else portbuff[0] = '\0'; if (inet_pton(AF_INET6, request->host, dst) > 0) { /* host is an IPv6 address literal, so surround it with * [] */ return write_message (connptr->server_fd, "%s %s HTTP/1.0\r\n" "Host: [%s]%s\r\n" "Connection: close\r\n", request->method, request->path, request->host, portbuff); } else { return write_message (connptr->server_fd, "%s %s HTTP/1.0\r\n" "Host: %s%s\r\n" "Connection: close\r\n", request->method, request->path, request->host, portbuff); } } |
由于tinyproxy上行的是http/1.0 ,所以会故意将请求头中的connection相关信息去掉,代码如下:
这个函数不仅在process_client_headers 中用到,也在 process_server_headers中用到
解决办法:
- 如果使用隧道代理的方式,tinyproxy就不会做上述多余的处理了,但是, php的soapclient强制写死的逻辑是:如果方式的是https,则使用隧道代理方式,否则,不走隧道代理方式,可是服务提供的就是http,不是https啊!!!!!!
- 修复tinyproxy的这个问题,修复方案:
- 换个proxy
疑问:
- 如果 content-length 不存在与http 1.0,那么http 1.0中的post请求,post数据长度如何表示?(难道也是关闭连接为止?