当前位置:首页 > 文章列表 > Golang > Go问答 > HTTP 的 response 中的响应体和头部是分开发送的吗?

HTTP 的 response 中的响应体和头部是分开发送的吗?

来源:SegmentFault 2023-01-28 13:12:37 0浏览 收藏

在Golang实战开发的过程中,我们经常会遇到一些这样那样的问题,然后要卡好半天,等问题解决了才发现原来一些细节知识点还是没有掌握好。今天golang学习网就整理分享《HTTP 的 response 中的响应体和头部是分开发送的吗?》,聊聊Java、HTTP、go、PHP、python,希望可以帮助到正在努力赚钱的你。

问题内容

HTTP 报文的组成

image.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请求,而是会分为两步运行请求:

  1. 发送一个请求,该请求头部包括一个Expect: 100-continue的字段,用来询问server是否愿意接受数据
  2. 当接收到从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学习网公众号!

版本声明
本文转载于:SegmentFault 如有侵犯,请联系study_golang@163.com删除
在go语言中引入第三方库的问题在go语言中引入第三方库的问题
上一篇
在go语言中引入第三方库的问题
为什么goroutine使用channel阻塞执行时存在输出缺失的情况?
下一篇
为什么goroutine使用channel阻塞执行时存在输出缺失的情况?
评论列表
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    541次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    504次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    497次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码