如何在Go中实现HTTP双工处理程序?

huangapple go评论93阅读模式
英文:

How to do an HTTP duplex handler in Go?

问题

我听说这里说过:

> 一旦你在响应中写入任何内容,请求体将被关闭,这将阻止你从中读取任何内容。

如果这是真的,那么我该如何编写一个适当的双工处理程序,能够从请求体中读取数据,进行某种转换,然后以流式方式写入响应体,就像在Node.js中的做法一样?

英文:

I heard here that

> once you write anything in the response, the request body will be
> closed which prevents you from reading anything from it

If that is true, how can I write a proper duplex handler that is able to read from the request body, make some kind of transformation, and then write to the response body, in a streaming fashion just like people do in node.js ?

答案1

得分: 1

我成功地使用http.Hijacker来实现了这个功能。

在请求被发出并解析请求头之后,我可以从*http.Request.Body中读取数据,然后劫持连接并同时写入数据,代码如下:

hj, ok := w.(http.Hijacker)
if !ok {
    http.Error(w, "hijacking not supported", 500)
    return
}

conn, bufrw, err := hj.Hijack()
if err != nil {
    http.Error(w, err.Error(), 500)
    return
}
defer conn.Close()

然后,conn是一个net.Conn类型的对象,它是与客户端之间的底层TCP连接;bufrw是一个*bufio.ReadWriter类型的对象。要在不关闭请求体的情况下写入响应,只需执行以下操作:

_, err = bufrw.WriteString("HTTP/1.1 200 OK\n\n")
_, err = bufrw.WriteString("this")
_, err = bufrw.WriteString("is")
_, err = bufrw.WriteString("the")
_, err = bufrw.WriteString("response")
_, err = bufrw.WriteString("body")

然后,我不确定这一点,但也许有人可以完善这个答案,最好定期通过以下方式刷新连接上的缓冲区:

err := bufrw.Flush()
英文:

I ended up managing to do this with http.Hijacker.

After the request is made and the request headers are parsed, I can read from *http.Request.Body, then hijack the connection and write to it, at the same time, like this:

hj, ok := w.(http.Hijacker)
if !ok {
	http.Error(w, "hijacking not supported", 500)
	return
}

conn, bufrw, err := hj.Hijack()
if err != nil {
	http.Error(w, err.Error(), 500)
	return
}
defer conn.Close()

And then conn is a net.Conn which is the underlying TCP connection to the client, bufrw is a *bufio.ReadWriter, and to write the response without closing the body all I have to do is

_, err = bufrw.WriteString("HTTP/1.1 200 OK\n\n")
_, err = bufrw.WriteString("this")
_, err = bufrw.WriteString("is")
_, err = bufrw.WriteString("the")
_, err = bufrw.WriteString("response")
_, err = bufrw.WriteString("body")

And then I'm not sure about this but maybe someone can complete the answer, it's a good idea to flush the buffers down the connection once in a while with

err := bufrw.Flush()

答案2

得分: 1

一旦你在响应中写入任何内容,请求体将被关闭,这将阻止你从中读取任何内容。

2023年3月:Go 1.22+可能会包含该功能,遵循已接受的提案"net/http: add ResponseController.EnableFullDuplex"

net/http的HTTP/1服务器在开始写入响应后,不允许从传入的请求体中读取内容(请参阅ResponseWriter.Write文档)。

这个限制是因为服务器在写入响应头之前会消耗掉请求体的任何未读部分,以避免客户端在读取响应之前尝试写入完整的请求而导致死锁(请参阅#15527 (comment)了解更多上下文信息)。

我建议我们提供一种选择机制来禁用这种行为,允许服务器处理程序在读取请求的同时与写入响应交错。

现在你有了CL 472636: net/http: support full-duplex HTTP/1 responses,它开始实现这个功能。

// EnableFullDuplex表示请求处理程序将从Request.Body中读取内容与写入到ResponseWriter中交错。
//
// 对于HTTP/1请求,Go HTTP服务器默认会在开始写入响应之前消耗掉请求体的任何未读部分,以防止处理程序在并发读取请求和写入响应时发生死锁。
// 调用EnableFullDuplex会禁用此行为,并允许处理程序在并发写入响应时继续从请求中读取内容。
//
// 对于HTTP/2请求,Go HTTP服务器始终允许并发读取和响应。
func (c *ResponseController) EnableFullDuplex() error {
	rw := c.rw
	for {
		switch t := rw.(type) {
		case interface{ EnableFullDuplex() error }:
			return t.EnableFullDuplex()
		case rwUnwrapper:
			rw = t.Unwrap()
		default:
			return errNotSupported()
		}
	}
}
英文:

> once you write anything in the response, the request body will be closed which prevents you from reading anything from it

March 2023: Go 1.22+ might include that feature, following the accepted proposal "net/http: add ResponseController.EnableFullDuplex"

> The net/http HTTP/1 server does not permit reading from an inbound request body after starting to write the response. (See the ResponseWriter.Write documentation).
>
> This limitation is because the server drains any unread portion of the request body before writing the response headers, to avoid deadlocking clients that attempt to write a complete request before reading the response. (See #15527 (comment) for more context.)
>
> I propose that we offer an opt-in mechanism to disable this behavior, permitting a server handler to write some or all of the response interleaved with reads from the request.

You now have CL 472636: net/http: support full-duplex HTTP/1 responses which starts implementing that:

// EnableFullDuplex indicates that the request handler will interleave reads from Request.Body
// with writes to the ResponseWriter.
//
// For HTTP/1 requests, the Go HTTP server by default consumes any unread portion of
// the request body before beginning to write the response, preventing handlers from
// concurrently reading from the request and writing the response.
// Calling EnableFullDuplex disables this behavior and permits handlers to continue to read
// from the request while concurrently writing the response.
//
// For HTTP/2 requests, the Go HTTP server always permits concurrent reads and responses.
func (c *ResponseController) EnableFullDuplex() error {
	rw := c.rw
	for {
		switch t := rw.(type) {
		case interface{ EnableFullDuplex() error }:
			return t.EnableFullDuplex()
		case rwUnwrapper:
			rw = t.Unwrap()
		default:
			return errNotSupported()
		}
	}
}

答案3

得分: 0

根据你提供的内容,以下是翻译的结果:

从你提到的net/http文档中可以看到:

> var ErrBodyReadAfterClose = errors.New("http: invalid Read on closed Body")
>
> 当在关闭Body之后读取Request或Response的Body时,会返回ErrBodyReadAfterClose。通常在HTTP处理程序调用其ResponseWriter的WriteHeader或Write之后读取Body时会发生这种情况。

然而,我尝试了你提到的博客文章中的代码,在go 1.1.2下运行良好,除非我首先写入超过约4k的数据,否则r.ParseForm()会返回ErrBodyReadAfterClose

所以我认为答案是否定的,一般情况下在go中无法进行全双工的HTTP,除非响应很短(小于4k)。

我认为进行全双工的HTTP请求可能不会带来很大的好处,因为大多数客户端在发送请求之前不会尝试从响应中读取数据,所以你最多只能节省客户端和服务器的TCP缓冲区的大小。如果超过这些缓冲区的限制,可能会导致死锁,例如:

  • 客户端正在发送请求

  • 服务器正在发送响应

  • 服务器的缓冲区在发送响应时已满

  • 服务器阻塞

  • 服务器不再读取客户端的请求

  • 客户端的缓冲区已满

  • 客户端阻塞

  • 死锁

英文:

From the net/http docs you are referring to

> var ErrBodyReadAfterClose = errors.New("http: invalid Read on closed
> Body")
>
> ErrBodyReadAfterClose is returned when reading a Request or Response
> Body after the body has been closed. This typically happens when the
> body is read after an HTTP Handler calls WriteHeader or Write on its
> ResponseWriter

However I tried the code linked from the blog article you mentioned and it works fine under go 1.1.2 unless I write more than about 4k of data first in which case r.ParseForm() returns ErrBodyReadAfterClose.

So I think the answer is no, you can't do full duplex HTTP in go in general unless the responses are short (under 4k).

I would say that doing full duplex HTTP requests is unlikely to be a big benefit though as most clients won't attempt to read from the response until they have finished sending the request, so the most you'll win is the size of the TCP buffers in the client & server. A deadlock seems likely if these buffers are exceeded, eg

  • client is sending request
  • server is sending response
  • servers buffers get full sending response
  • server blocks
  • server no longer reading clients request
  • client buffers fill up
  • client blocks
  • deadlock

huangapple
  • 本文由 发表于 2013年11月4日 23:31:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/19771161.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定