英文:
Convert interface{} parameter into *http.Request or *http.Response in Go
问题
我正在尝试创建一个实用函数,该函数将读取请求/响应的主体并返回它。目前我已经完成了以下工作:
func GetBody(in interface{}) []byte {
var body io.Reader
var statusCode int
switch v := in.(type) {
case *http.Request, *http.Response:
body = v.Body
statusCode = v.StatusCode
default:
log.Fatal("只能接受 http.Request 和 http.Response 参数来解析主体")
}
if statusCode != 200 {
log.Fatalf("收到的状态码 [%d] 不是 [200]", statusCode)
}
body, err := ioutil.ReadAll(body)
if err != nil {
log.Fatal(err)
}
return body
}
但是我收到了一个编译器错误:v.Body undefined (type interface {} is interface with no methods)
。我是否遗漏了什么,或者不可能创建一个通用函数,既适用于 *http.Request
又适用于 *http.Response
?
英文:
I'm tying to create an util function that will read body of the Request/Response and return it.
Here's what I've done for the moment:
func GetBody(in interface{}) []byte {
var body io.Reader
var statusCode int
switch v := in.(type) {
case *http.Request, *http.Response:
body = v.Body
statusCode = v.StatusCode
default:
log.Fatal("Only http.Request and http.Response parameters can be accepted to parse body")
}
if statusCode != 200 {
log.Fatalf("Received status code [%d] instead of [200]", statusCode)
}
body, err := ioutil.ReadAll(body)
if err != nil {
log.Fatal(err)
}
return body
}
But I'm receiving an error of compiler: v.Body undefined (type interface {} is interface with no methods)
Am I missing something or it's impossible to make a generic function that will server both for *http.Request
and *http.Response
答案1
得分: 4
这是因为存在双重的case
。
v
仍然是一个interface{}
,因为它可以是*http.Request
或*http.Response
之一。
switch v := in.(type) {
case *http.Request:
body = v.Body
statusCode = v.StatusCode
case *http.Response:
body = v.Body
statusCode = v.StatusCode
default:
log.Fatal("只能接受http.Request和http.Response参数来解析body")
}
这应该可以工作。
英文:
It is because of the double case
.
v
is still an interface{}
because it could be either an *http.Request
or an *http.Response
switch v := in.(type) {
case *http.Request
body = v.Body
statusCode = v.StatusCode
case *http.Response:
body = v.Body
statusCode = v.StatusCode
default:
log.Fatal("Only http.Request and http.Response parameters can be accepted to parse body")
}
This should work
答案2
得分: 2
除了其他直接解决类型切换问题的答案之外,我想指出另一种解决方案。
请注意,interface{}
解决方案是完全可行的,并且可能更可取。这只是为了提供更多的知识。
首先,稍微偏离一下话题,如果你感兴趣的是一个常见的方法(例如Write
或Cookies
)而不是一个常见的字段(Body
),那么通过自定义接口很容易且更好地访问它。
可以通过定义一个类型来实现:
type cookier interface {
Cookies() []*http.Cookie
}
func ShowCookies1(r cookier) {
log.Println("Got cookies:", r.Cookies())
}
或者在函数定义中使用匿名类型:
func ShowCookies2(r interface {
Cookies() []*http.Cookie
}) {
log.Println("Got cookies:", r.Cookies())
}
这些函数可以接受任何具有Cookies
方法的对象,包括*http.Request
和*http.Response
。
不幸的是,在你的特定情况下,你希望访问的是一个常见字段而不是一个常见方法,所以不能直接使用匹配的接口。
你可以创建一个小的包装类型,添加一个GetBody
方法(可以说这样的函数应该在标准包中定义)。
type reqbody struct{ *http.Request }
type respbody struct{ *http.Response }
type getbody interface {
GetBody() io.ReadCloser
}
func (r reqbody) GetBody() io.ReadCloser { return r.Body }
func (r respbody) GetBody() io.ReadCloser { return r.Body }
func GetBody2(r getbody) ([]byte, error) {
body := r.GetBody()
defer body.Close()
return ioutil.ReadAll(body)
}
调用者知道他们有什么类型,并执行以下操作之一:
buf, err = GetBody2(reqbody{req})
buf, err = GetBody2(respbody{resp})
从某种意义上说,这比只使用interface{}
更丑陋。
但它的好处是,不像一个接受任何类型并在运行时如果程序员错误地使用不适当类型的参数时会导致恐慌/错误的函数,
这个解决方案在编译时强制调用者安全地传递你知道的正确类型的参数。
进一步观察,你只是从io.ReadCloser
中读取所有内容,然后关闭它,所以可以进一步简化为以下形式
(这可能比你的interface{}
解决方案更好):
func GetReqBody(r *http.Request) io.ReadCloser { return r.Body }
// 可以在下面的函数中添加检查 r.StatusCode 的代码:
func GetRespBody(r *http.Response) io.ReadCloser { return r.Body }
func ReadAndClose(rc io.ReadCloser) ([]byte, error) {
defer rc.Close()
return ioutil.ReadAll(rc)
}
同样,调用者知道他们有什么类型,并执行以下操作之一:
buf, err = ReadAndClose(GetReqBody(req))
buf, err = ReadAndClose(GetRespBody(resp))
或者只需:
buf, err = ReadAndClose(req.Body)
buf, err = ReadAndClose(resp.Body)
你可以在Go Playground上看到所有这些选项的示例。
最后,使用ioutil.ReadAll
时要小心。
通常最好避免将整个文件或网络流预先读入缓冲区,而是在读取时将其作为流进行处理。
特别是,可以轻松地使用具有任意大的主体的HTTP请求进行拒绝服务攻击或浪费服务器资源
(http.MaxBytesReader
也可以帮助)。
英文:
In addition to the other answers which directly address the issue with your type switch, I'd like to point out an alternative solution.
Note, the interface{}
solution is perfectly fine and may easily be considered preferable to this. This is presented for edification.
First, as a slight aside, if the thing you were interested in was a common method (e.g. Write
or Cookies
) rather than a common field (Body
), it would be easy and better to access it via a custom interface.
Either by defining a type:
type cookier interface { // Should probably use a better name
Cookies() []*http.Cookie
}
func ShowCookies1(r cookier) {
log.Println("Got cookies:", r.Cookies())
}
Or by using an anonymous type in the function definition:
func ShowCookies2(r interface {
Cookies() []*http.Cookie
}) {
log.Println("Got cookies:", r.Cookies())
}
These functions can accept anything that has a Cookies
method, this includes *http.Request
and *http.Response
.
Unfortunately, in your particular case you wish to access a common field rather than a common method so you can't directly just use a matching interface.
You could make a small wrapper type that adds a GetBody
method (one could argue such a function should have been defined in the standard package).
type reqbody struct{ *http.Request }
type respbody struct{ *http.Response }
type getbody interface {
GetBody() io.ReadCloser
}
func (r reqbody) GetBody() io.ReadCloser { return r.Body }
func (r respbody) GetBody() io.ReadCloser { return r.Body }
func GetBody2(r getbody) ([]byte, error) {
body := r.GetBody()
defer body.Close()
return ioutil.ReadAll(body)
}
The caller knows what type they have and does one of:
buf, err = GetBody2(reqbody{req})
buf, err = GetBody2(respbody{resp})
In some sense this is uglier than just using interface{}
.
But it has the benefit that instead of having a function that takes absolutely any type and panics/errors at run-time if a programmer mistakenly calls it with something not of the appropriate type,
this instead forces the caller to safely pass something you known is of the correct type at compile time.
Looking at this further, you're just reading everything from an io.ReadCloser
and then closing it so it could further be simplified to the following
(which probably is better than your interface{}
solution):
func GetReqBody(r *http.Request) io.ReadCloser { return r.Body }
// Could add checking r.StatusCode to the following one as well:
func GetRespBody(r *http.Response) io.ReadCloser { return r.Body }
func ReadAndClose(rc io.ReadCloser) ([]byte, error) {
defer rc.Close()
return ioutil.ReadAll(rc)
}
Again, the caller knows what type they have and does one of:
buf, err = ReadAndClose(GetReqBody(req))
buf, err = ReadAndClose(GetRespBody(resp))
Or just:
buf, err = ReadAndClose(req.Body)
buf, err = ReadAndClose(resp.Body)
You can see an example of all these options on the Go Playground.
Finally, be careful using ioutil.ReadAll
.
Often it is better to avoid pre-reading an entire file or network steam into a buffer and instead process it as a stream as it is being read.
In particular, it's trivial to make an HTTP request with an arbitrary large body as a denial of service attack or to waste server resources
(http.MaxBytesReader
can also help).
答案3
得分: 0
你正在尝试做的更像是这样:
switch in.(type) {
case *http.Request:
body = v.(*http.Request).Body
case *http.Response:
body = v.(*http.Response).Body
default:
log.Fatal(...)
}
编辑:我删除了回答中错误的部分,请参考HectorJ的回答,了解更正确的语法方式。
英文:
What you are trying to do is more something like that:
switch in.(type) {
case *http.Request:
body = v.(*http.Request).Body
case *http.Response:
body = v.(*http.Response).Body
default:
log.Fatal(...)
}
EDIT: I removed the wrong part of my answer, see HectorJ's answer for the more syntaxic please way of doing this.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论