将interface{}参数转换为*http.Request或*http.Response在Go中。

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

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{}解决方案是完全可行的,并且可能更可取。这只是为了提供更多的知识。

首先,稍微偏离一下话题,如果你感兴趣的是一个常见的方法(例如WriteCookies)而不是一个常见的字段(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.

huangapple
  • 本文由 发表于 2015年9月8日 17:46:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/32454490.html
匿名

发表评论

匿名网友

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

确定