大型的PUT请求从Go到CouchDB

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

Large PUT requests from Go to CouchDB

问题

我在使用CouchDB和Golang时遇到了一个令人困扰的问题。当向CouchDB发送相对较大的POST/PUT请求时(阈值似乎在8000字节左右),连接会超时,并且我会从Go中得到一个"tcp: use of closed network connection"错误。

最终(大约一两秒后),CouchDB发送一个500响应,其中包含以下内容:

{"error":"unknown_error", "reason": "noproc"}

couchdb日志中还有一个堆栈跟踪:

Stacktrace: [{couch_db,collect_results,3,
                [{file,"couch_db.erl"},{line,833}]},
             {couch_db,write_and_commit,4,
                [{file,"couch_db.erl"},{line,845}]},
             {couch_db,update_docs,4,
                [{file,"couch_db.erl"},{line,782}]},
             {couch_db,update_doc,4,
                [{file,"couch_db.erl"},{line,426}]},
             {couch_httpd_db,update_doc,6,
                [{file,"couch_httpd_db.erl"},{line,753}]},
             {couch_httpd_db,do_db_req,2,
                [{file,"couch_httpd_db.erl"},{line,234}]},
             {couch_httpd,handle_request_int,5,
                [{file,"couch_httpd.erl"},{line,318}]},
             {mochiweb_http,headers,5,
                [{file,"mochiweb_http.erl"},{line,94}]}]

因此,我编写了一个快速的单元测试来复制这个问题(并确保不是我的包装器引起的问题)。我这样发送请求:

client := &http.Client{}
req, err := http.NewRequest(
	"PUT",
	"http://localhost:5984/unittestdb/testdocid1",
	bytes.NewReader(testBody1), //testBody1是10000字节的JSON对象
)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)

这样复制了这个问题...所以我尝试使用curl向couchdb发送一个大的json文档。那个是可以工作的。

因此,我启动了wireshark,并检查了我的代码发送给CouchDB的请求,并将其与curl发送的请求进行了比较。我注意到Curl请求中有一个额外的头部:

Expect: 100-continue

我不得不查找这个头部,因为我记不起以前见过它(或者可能我以前没有遇到过)。然后我进行了一次谷歌搜索,看看Golang的http客户端是否支持Expect/Continue功能,并找到了这个链接:https://github.com/golang/go/issues/3665

所以Go不支持它,至少在1.6之前不支持。所以我认为这一定是一个不常见的问题,并且不是我的问题的根源。我花了几个小时随机尝试http.Client、Transport等其他东西。

最终,我在我的Go http请求中手动设置了"Expect: 100-continue"头部(如果body大小超过一定大小),然后...它起作用了。没有超时,没有错误,没有couchdb将堆栈跟踪写入日志。

现在我很困惑,如果Go不支持这个,它是如何工作的呢?我只是通过这样做来掩盖问题吗?还是我可以耸耸肩继续前进?

我怀疑CouchDB那边可能有问题,也许有一些我遗漏的配置设置?

英文:

I've been having a vexing problem with CouchDB and Golang. When sending POST/PUT requests to CouchDB with relatively large Body sizes (the threshold seems to be ~8000 bytes or so), the connection times out and I get a "tcp: use of closed network connection" error from Go.

Eventually (a second or two later), CouchDB sends a 500 Response along with a:

{"error":"unknown_error", "reason": "noproc"}

in the body. The couchdb log also has a stack trace written to it:

Stacktrace: [{couch_db,collect_results,3,
                [{file,"couch_db.erl"},{line,833}]},
             {couch_db,write_and_commit,4,
                [{file,"couch_db.erl"},{line,845}]},
             {couch_db,update_docs,4,
                [{file,"couch_db.erl"},{line,782}]},
             {couch_db,update_doc,4,
                [{file,"couch_db.erl"},{line,426}]},
             {couch_httpd_db,update_doc,6,
                [{file,"couch_httpd_db.erl"},{line,753}]},
             {couch_httpd_db,do_db_req,2,
                [{file,"couch_httpd_db.erl"},{line,234}]},
             {couch_httpd,handle_request_int,5,
                [{file,"couch_httpd.erl"},{line,318}]},
             {mochiweb_http,headers,5,
                [{file,"mochiweb_http.erl"},{line,94}]}]

So I wrote up a quick unit test to replicate the issue (and to make sure it's not my wrapper causing the problem). I'm making the request like so:

client := &http.Client{}
req, err := http.NewRequest(
	"PUT",
	"http://localhost:5984/unittestdb/testdocid1",
	bytes.NewReader(testBody1), //testBody1 is 10000 bytes of json object
)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)

This replicated the issue... so I tried curling a large json document to couchdb. That worked.

So, I fired up wireshark and examined the request made by my code to CouchDB and compared it to a request sent from curl. I noticed one additional header in the Curl Request:

Expect: 100-continue

I had to look that one up, as I couldn't recall having seen (or maybe I just hadn't had to deal with) that header before. So then I did a google search to see if the Golang http client supported the Expect/Continue functionality, and found this: https://github.com/golang/go/issues/3665

So Go doesn't support it and won't at least until 1.6. So I assumed it must be an obscure thing and wasn't the source of my problem. I spent a couple of hours randomly trying other stuff with the http.Client, Transport, etc.

Eventually, I manually set the "Expect: 100-continue" header in my http request in Go (if the body size is over a certain size), and ... it worked. No timeout, no error, no couchdb barfing stack traces into the logs.

Now I am confused, if Go doesn't support this, how is it working? Am I just masking the problem by doing this? Or can I just shrug my shoulders and move on?

I suspect there is an issue on the CouchDB side, perhaps there's a configuration setting I'm missing?

答案1

得分: 3

我不知道答案,但我认为对你来说关键的规范是RFC 2616第8.2.3节。整个章节都很有趣,但特别是这部分:

> "这个规则有一个例外:为了与RFC 2068兼容,服务器可以在不包含带有“100-continue”期望的Expect请求头字段的HTTP/1.1 PUT或POST请求的响应中发送100(Continue)状态。这个例外的目的是为了最小化与未声明的等待100(Continue)状态相关的任何客户端处理延迟,它仅适用于HTTP/1.1请求,而不适用于具有任何其他HTTP版本值的请求。"

听起来你陷入了Go缺少100-continue实现和CouchDB一侧的某种特殊情况之间的困境。(无论客户端的行为如何,我认为500内部错误不是服务器的适当响应。)

如果Go客户端允许你设置,我建议尝试使用HTTP/1.0作为解决方法。

英文:

I don't know the answer, but I think that the critical spec for you is RFC 2616 section 8.2.3. The whole section is interesting, but particularly this:

> "There is an exception to this rule: for compatibility with RFC 2068,
> a server MAY send a 100 (Continue) status in response to an HTTP/1.1
> PUT or POST request that does not include an Expect request-header
> field with the "100-continue" expectation. This exception, the purpose
> of which is to minimize any client processing delays associated with
> an undeclared wait for 100 (Continue) status, applies only to HTTP/1.1
> requests, and not to requests with any other HTTP-version value."

It sounds like you're getting caught between Go's missing 100-continue implementation, and some kind of corner case on the CouchDB side. (Whatever the behavior of the client, I don't think 500 Internal error is an appropriate response from the server.)

I'd try HTTP/1.0 as a workaround, if the Go client will let you set that.

huangapple
  • 本文由 发表于 2015年5月30日 10:26:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/30541591.html
匿名

发表评论

匿名网友

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

确定