当作为函数参数传递时,修改http.ResponseWriter。

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

Modify http.ResponseWriter when passed as a function argument

问题

我有一个处理应用程序身份验证的身份验证中间件,其中有几个检查的情况需要进行验证,每个检查在出现错误时都有相同的逻辑:

res, err := doSomeCheck()
if err != nil {
    log.Println("Authentication failed: %v", err)
    json.NewEncoder(w).Encode(struct {Error string}{Error: "something is broke!"})
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusForbidden)
    return
}

我想用一个函数来写这个逻辑一次(每个情况之间的唯一区别是错误和客户端消息),像这样:

func authError(w http.ResponseWriter, err error, clientMsg string) {
    log.Println("Authentication failed: %v", err)
    json.NewEncoder(w).Encode(struct {
        Error string
    }{Error: clientMsg})
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusForbidden)
    return
}

但是 w 不是一个指针(我没有将其作为指向中间件处理程序的指针),所以我无法从函数中更改它,authError() 不会更改实际的响应。
如何优雅地使这个工作起来?

英文:

I have an auth middleware that handle app authentication, with several cases to checks, each check have the same logic in case of an error:

res, err := doSomeCheck()
if err != nil {
    log.Println("Authentication failed: %v", err)
    json.NewEncoder(w).Encode(struct {Error string}{Error: "something is broke!"})
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusForbidden)
    return
}

I want to write this logic once (the only difference between each case is the error and the client message) with some function like this:

func authError(w http.ResponseWriter, err error, clientMsg string) {
	log.Println("Authentication failed: %v", err)
	json.NewEncoder(w).Encode(struct {
		Error string
	}{Error: clientMsg})
	w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusForbidden)
	return
}

But w is not a pointer (I don't get it as a pointer to the middleware handler) so I can't change it from the function, authError() doesn't change the actual response.
How can I make this work elegantly?

答案1

得分: 3

w不是一个指针,但它是一个接口类型,并且在内部封装了一个指针。因此,你可以直接传递它,并且当你调用它的方法时,调用者会反映出来。

只是不要忘记,如果在之前已经写入了响应内容,你就不能再写入头部了。同样,如果你的authError()在输出中写入了内容,调用者就无法撤销。如果authError()生成了响应,那么在这种情况下,调用者应该返回。

还要注意,你必须先设置头部,然后调用ResponseWriter.WriteHeader(),然后才能写入响应体。

如果你调用了ResponseWriter.Write(),如果响应状态还没有被写入(假设为HTTP 200 OK),它将写入响应状态。

引用自ResponseWriter.Write()

// 如果还没有调用WriteHeader,Write在写入数据之前会调用WriteHeader(http.StatusOK)。
// 如果Header中不包含Content-Type行,Write会将Content-Type设置为将前512个字节的写入数据传递给DetectContentType的结果。
// 另外,如果所有写入数据的总大小小于几KB,并且没有Flush调用,Content-Length头部会自动添加。
Write([]byte) (int, error)

所以你的authError()应该像这样:

func authError(w http.ResponseWriter, err error, clientMsg string) {
    log.Println("Authentication failed: %v", err)
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusForbidden)

    err = json.NewEncoder(w).Encode(struct {
        Error string
    }{Error: clientMsg})
    if err != nil {
        log.Println("Failed to write response: %v", err)
    }

    return
}
英文:

w is not a pointer, but it's of an interface type, and it wraps a pointer under the hood. So you may pass it as-is, and when you call its methods, it will be reflected at the caller.

Just don't forget that if there's anything written to the response prior, you can't write the header (again). Same, if your authError() writes something to the output, the caller can't take that back. If authError() generates the response, the caller should return in that case.

Also note that you must first set headers, then call ResponseWriter.WriteHeader(), and only then can you write the response body.

If you call ResponseWriter.Write(), that will write the response status if it hasn't been (assuming HTTP 200 OK).

Quoting from ResponseWriter.Write():

// If WriteHeader has not yet been called, Write calls
// WriteHeader(http.StatusOK) before writing the data. If the Header
// does not contain a Content-Type line, Write adds a Content-Type set
// to the result of passing the initial 512 bytes of written data to
// DetectContentType. Additionally, if the total size of all written
// data is under a few KB and there are no Flush calls, the
// Content-Length header is added automatically.
Write([]byte) (int, error)

So your authError() should be something like this:

func authError(w http.ResponseWriter, err error, clientMsg string) {
    log.Println("Authentication failed: %v", err)
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusForbidden)

    err = json.NewEncoder(w).Encode(struct {
        Error string
    }{Error: clientMsg})
    if err != nil {
        log.Println("Failed to write response: %v", err)
    }

    return
}

huangapple
  • 本文由 发表于 2021年10月7日 16:32:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/69477901.html
匿名

发表评论

匿名网友

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

确定