• 标准库的 server 需要回 Trailer Header 好像只能 Hijack,比较麻烦,NBHTTP Server 为了用户便利还支持比较方便的 Trailer 方式(以"Trailer-"为前缀设置 header),比如:

    func onEcho(w http.ResponseWriter, r *http.Request) {
        w.Header().Add("Trailer-YourTrailerHeaderKey", "YourTrailerHeaderValue")
        // ...
    }
    
  • 并且 chunked 的引入,主要是因为服务端在回包时,有些数值没法在发送时就计算出来,比如校验包体相关的 Trailer Header 这些,所以需要先发送 Trailer 相关 header 字段的 key 放到 body 前的 header 列表里标识有哪些 header 字段,然后在发送完 body 后计算出这些 header 对应的值,再把这些 header 的 key-value 放到 body 后面,收到包的再按这个规则解析出来,这种无法预知 header 值的业务场景下只能通过 chunked 的方式,但除了这种,Content-Length 的编解码和传输数据量都更性能友好

  • 咱们按每个点来说:

    长连接

    我没太理解你说的长连接具体是指什么问题?是指 Connection 头对应的处理方式、还是指 4 层 tcp 的什么问题?

    如果只是认为一个请求之后 NBHTTP Server 就会主动断开连接,如果请求方不是 http 1.0,也没有发送 Connection: close 头,是会保持连接的,您可以 netstat 看下

    分段传输

    实现时我考虑过几点:

    1. 优先使用 Content-Length 发送 —— 已经实现
    2. 有 Transfer-Encoding: chnaked 或者 Trailer Header 时必须 chunked,这是协议标准 —— 已实现
    3. WriteHeader 则直接写,Write 则判断是否需要合并 header 和 body —— 未实现
    4. body 过大多次发送 —— 未实现

    最后实现了 1、2,应该算符合标准: 在没有 Transfer-Encoding: chnaked 或者 Trailer Header 时,server 回包用 Content-Length 的方式应该是并不影响 client 端解码,因为不管哪种方式回包,client 都需要解码出完整 body,所以按照 Content-Length 回包应该是没问题,并且 Content-Length 对于 C/S 两端编解码性能都更友好 如果有特例、即使没有 Transfer-Encoding: chnaked 或者 Trailer Header 也必须按照 chunked 的方式回包,请提供一个我研究下再进行实现

    为什么没有实现 3: 因为支持 1.1 的 pipeline,客户端的多个请求可能同时到达服务端,并且异步网络层跟标准库不一样,标准库是同步挨个读取、处理完一个才会读取下一个,但是异步库,为了更高的性能,我支持了业务层可定制的协程池,在帖子中乱序处理的部分有解释。应用层当然也可以定制成单个连接指定到特定的协程去处理,从而跟标准库类似,也是顺序处理,但是这在遇到数据库等慢 IO 操作时可能导致该任务协程的阻塞,从而影响其他连接上的请求的处理,只有作为网关、代理之类的不涉及数据库等慢操作的基础设施类服务才适合这种定制,所以作为通用框架没有这样实现。所以比如主帖中介绍的 request 2 的处理中,框架层不能直接就 WriteHeader 到 Conn,而是需要等到真正发送自己这个 response 时再打包发送。也可以实现成判断自己这个 response 是不是就对应最新的那个请求再进行发送,但是这并不能保证所有 response 都达到立即发送的效果,并且,性能不友好,所以没必要

    为什么没有实现 4: nbio 的配置项有 MaxWriteBufferSize,可以用来控制 Conn 最大应用层的发送队列缓冲,即 tcp 发送缓冲满了时,应用层也堆积数据超过这个 size 时 server 就会主动断开连接,因为该连接已经拥堵不堪,等待其恢复不如壮士断腕了。并且,网络层上讲,Writev 通常性能好于 Write 多端数据,内存拷贝拼接的成本小于多次 syscall 的成本,所以在 MaxWriteBufferSize 可控的基础之上,nbio 进行这些细节的优化来尽量提高通用场景的整体性能,并且如上面实现 1、2 的解释中提到的,允许 Content-Length 回包时性能优先

    返回一个文件内容会不会把所有读到内存中呢

    如果是静态资源服务器,我还是建议其他支持 sendfile 的内核级 zero copy 的,相当于前后端分离,go 只做接口服务。 当然一起做也可以,如果是返回完整文件,应该是需要整个文件内容读取到内存的,并且,如果无法利用 zero copy 并且又想高性能,应该在服务器启动时就 load 一次而不是每次去 open read write close 之类的。包括 go 新增的 embedded 也都是要加载到内存中,如果内存资源不够用,那真的是需要考虑加硬件了

    h2、握手、upgrade

    如主帖中介绍,websocket 和 http 2.0 在计划中,socket.io 之类的就不考虑支持了,这些每一项都是个体力活、需要时间,并且这个项目就我一个人在为爱发电,最近就是肝得有点狠了身体状态有点下滑得调整下,所以也希望更多大佬、小伙伴们一起来玩

  • 我之前在实现的时候是对比标准库的 server,标准库 response header 默认应该也是没有自带 keep-alive 之类的响应头,如果需要,留给应用层框架自行实现。nbio 定位不是像 gin 之类的 web 框架,而是定位在协议层本身、负责数据的基础传输和解析,对标的是标准库的同步阻塞模式

  • Cache-Control 相关的也没做支持,因为主要定位是接口服务、排除静态资源类,静态资源类的服务用其他的比如 nginx 可以 sendfile,zero copy,性能更好

  • 这两个地方有 Close 和 keep-alive 相关的处理: https://github.com/lesismal/nbio/blob/master/nbhttp/processor.go#L204 https://github.com/lesismal/nbio/blob/master/nbhttp/processor.go#L274

    我这里处理相对简单,没有按照标准,因为 http1.x 标准本身就不是什么好标准,如果 client 端不是 close,server 端按照配置的统一时间设置读超时。server 端响应给 client 端的,留给 web 框架自己实现

  • 上周也想写个 beego 的例子来着,但是没太研究明白 beego router + 外部 serve 需要怎么实现,谢老板多多指点,如果可以内嵌个支持更好

    路还很长,一定要让 golang 更强 😋

  • 我们内部具体的业务类型涉及微服务集群内部 RPC、APP 长连接的各种功能比如推送服务,还有一些自研的 devops、监控系统,因为比较通用且高性能,一个 RPC 库可以搞定绝大多数的业务场景,非静态资源类的服务我们甚至很少使用 HTTP 框架。有前同事、朋友做游戏、IM 软件等更复杂的有状态业务,也是用的类似框架,只是或多或少都带点自家业务相关的私货。

  • 感谢支持!😀 😀

  • 内部很多项目在用类似功能的框架、有一些自家业务定制相关的,具体项目不太方便透露; 开源出来的部分是我个人整理的精简升级版、只做业务不相关的基础框架、组件。