HTTP 的 response 中的响应体和头部是分开发送的吗?
来源:SegmentFault
时间:2023-01-28 13:12:37 387浏览 收藏
在Golang实战开发的过程中,我们经常会遇到一些这样那样的问题,然后要卡好半天,等问题解决了才发现原来一些细节知识点还是没有掌握好。今天golang学习网就整理分享《HTTP 的 response 中的响应体和头部是分开发送的吗?》,聊聊Java、HTTP、go、PHP、python,希望可以帮助到正在努力赚钱的你。
问题内容
HTTP 报文的组成
![image.png image.png](/uploads/20230128/167488380263d4b2dab895e.png)
Response 报文由:
- 状态行 start-line
- 响应头 HTTP headers
- 空行 empty-line
- 响应体 body
四部分组成
参考资料:
mozilla doc:Date
http里的时间格式
好了下面是问题的正文了
我发现一般的 HTTP 服务器在发送
Response报文的时候,是把(状态行、响应头、空行)作为一个整体发送(即调用一次 socket.send()),然后在调用一次 socket.send() 把响应体
body发出去。
看了 Gunicorn 服务器,和 aiohttp
为什么不一次发送呢?
下面是 Gunicorn 的两段源代码:
gunicorn/http/wsgi.py
def create(req, sock, client, server, cfg): resp = Response(req, sock, cfg) environ = default_environ(req, sock, cfg) # ... environ['PATH_INFO'] = util.unquote_to_wsgi_str(path_info) environ['SCRIPT_NAME'] = script_name # .... return resp, environ class Response(object): def start_response(self, status, headers, exc_info=None): # ... self.status = status self.process_headers(headers) return self.write def write(self, arg): self.send_headers() # 可以看到一句 self.send_headers(), 单独发出了 headers # ... util.write(self.sock, arg, self.chunked)
👆 可以看到一句
self.send_headers(), 单独发出了
headers
gunicorn/util.py
def write(sock, data, chunked=False): if chunked: return write_chunk(sock, data) sock.sendall(data)
下面是一段
aiohttp的 demo 代码。
import aiohttp import asyncio async def fetch(session, url): async with session.get(url) as resp: if resp.status != 200: resp.raise_for_status() data = await resp.text() return data
👆 比如
aiohttp在获取
response.text的时候会加一个
await,我不明白为什么要加
await,因为客户端接收到
response的时候,已经包含
text了呀,为什么还要加一个
await呢?除非是 text 是单独发送的,所以才会有 IO 等待的问题,是这样吗?
总结一下,问题的核心就是一般的 HTTP 服务器(
nginx、
httpd、
tomcat、
uwsgi等等),在调用 socket 的 send 给 client 发送 HTTP response 的时候,是不是都是先调用一次
socket.send发送不包含 body 的部分,在调用 N 次发送
body的呢?
正确答案
问题有点乱,我来逐一回答。
为什么不一次发送
socket实际上并不是直接发送的,而是发到buffer里的,接收也是从buffer里取。这个过程分为同步还是异步,如果是同步模式,buffer满了,则会阻塞,直到buffer有足够空间为止。如果是异步模式,则会返回一个错误标识符。buffer的发送是tcp传输层的事情,你个人是无法控制的.但在你看的这个代码的socket实际不是c语言里的socket,而是包装过的,分多次发送只是为了使业务逻辑更清晰,实际几次发送,怎么发送,得看操作系统实现的传输层协议的策略。
两次发送
这个纯粹是客户端的行为。
比如我们用的curl命令,底层是libcurl,当使用libcurl的POST方式时,假设POST数据的大小大于1024个字节,libcurl不会直接发送POST请求,而是会分为两步运行请求:
- 发送一个请求,该请求头部包括一个Expect: 100-continue的字段,用来询问server是否愿意接受数据
- 当接收到从server返回的100-continue的应答后,它才会真正的发起POST请求。将数据发送给server。
对于浏览器环境,如果是跨域请求,浏览器或先发送一个option请求,获取服务器的cors配置(在响应头里),你的请求符合服务器的cors策略,才会进一步发送正式的请求,否则就不会发送。
对于你说的304问题,以nginx为例,如果你请求一个静态文件的时候,nginx会给请求的实体生成一个唯一的etag,内存里会以etag为key,缓存的过期时间及请求大小等内容为value保存起来,并将对应的etag和expire返回给浏览器保存。后面再请求这个文件的时候,header带了对应的
*Modified*等系列字段,那么nginx等服务端会用你带的这个字段的对应的值,跟它内存里缓存kv数据比较,如果符合缓存策略,则会直接返回304状态,这个时候body是空的。我这里是以nginx举例,你实际也可以自己实现,比如你请求
/user/1,在你程序里,可以用这个地址的md5当做etag,过期时间当做
expires返给客户端,当然还有
date和
cache-control。在redis里则存上以这个etag为key的缓存,下次请求的时候,如果header里带了
If-Modified-Since和
If-None-Match,那你直接解析出etag和过期时间跟redis里的数据对比,如果符合条件,则直接返回304状态,后面的逻辑不用走一遍了。
值得主要的是,上述的策略都是客户端和服务端的约定,你服务端可以接受,也可以不接受,并不是强制的。比如浏览器虽然带了Modified字段,但是我服务器不接受,也不缓存对应请求的对象的缓存信息,按普通请求跑一遍逻辑,返回200,也是正常的。只是说,如果服务器响应缓存策略,则可以减少服务端负担而已。
今天关于《HTTP 的 response 中的响应体和头部是分开发送的吗?》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于golang的内容请关注golang学习网公众号!
-
244 收藏
-
354 收藏
-
356 收藏
-
435 收藏
-
272 收藏
-
139 收藏
-
204 收藏
-
325 收藏
-
476 收藏
-
486 收藏
-
439 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 541次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 506次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习
-
- 害怕的香水
- 很详细,已加入收藏夹了,感谢师傅的这篇文章,我会继续支持!
- 2023-04-10 10:02:34
-
- 清秀的口红
- 感谢大佬分享,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢博主分享文章!
- 2023-04-05 14:36:34
-
- 丰富的大炮
- 这篇文章出现的刚刚好,太全面了,受益颇多,已加入收藏夹了,关注大佬了!希望大佬能多写Golang相关的文章。
- 2023-03-31 04:57:31
-
- 高大的冰棍
- 这篇文章真是及时雨啊,太全面了,很棒,码起来,关注大佬了!希望大佬能多写Golang相关的文章。
- 2023-02-24 21:20:49
-
- 魁梧的板栗
- 这篇文章太及时了,楼主加油!
- 2023-02-13 14:18:35
-
- 笑点低的狗
- 写的不错,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢楼主分享博文!
- 2023-02-06 15:09:36
-
- 忧伤的柚子
- 这篇文章内容太及时了,好细啊,很棒,已加入收藏夹了,关注楼主了!希望楼主能多写Golang相关的文章。
- 2023-02-01 09:46:58
-
- 顺心的星星
- 很详细,已加入收藏夹了,感谢大佬的这篇文章内容,我会继续支持!
- 2023-01-31 20:58:44
-
- 光亮的小馒头
- 很详细,mark,感谢老哥的这篇技术文章,我会继续支持!
- 2023-01-30 14:27:29
-
- 冷静的画板
- 太给力了,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢博主分享文章!
- 2023-01-29 12:08:34