在handlefunc之外安全终止一个请求。

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

Safely terminating a request outside of handlefunc

问题

如何在没有显式返回的情况下,安全地终止一个在调度程序(handlefunc)之外的请求?在下面的代码中,我会在响应中看到"Bad request"和"Not found"两个信息,但我希望f()函数能够中止请求,这样我就不需要在调度程序中添加错误检查和返回语句。

package main

import (
	"net/http"
)

func f(w http.ResponseWriter, r *http.Request, k string) string{
	if v, ok := r.Form[k]; !ok{		
		http.Error(w, "Bad request", http.StatusBadRequest)
		return ""
	}else{
		return v[0]
	}
}

func main(){
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
		foo := f(w, r, "foo") //确保传递了foo,如果没有,中止请求并返回"bad request",而不需要在此处显式返回
		bar := f(w, r, "bar") //确保传递了bar,如果没有,中止请求并返回"bad request",而不需要在此处显式返回
		//在数据库中查找...
		//如果在数据库中找不到,返回404
		http.NotFound(w, r)    		
	})	

	http.ListenAndServe(":6000", nil)
}

上述代码是对以下代码的重构:

func main(){
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
		if foo,ok:=r.Form["foo"];!ok{
			http.Error(w, "Bad request", http.StatusBadRequest)
			return
		}
		if bar,ok:=r.Form["bar"];!ok{
			http.Error(w, "Bad request", http.StatusBadRequest)
			return
		}
		//在数据库中查找...
		//如果在数据库中找不到,返回404
		http.NotFound(w, r)    		
	})	

	http.ListenAndServe(":6000", nil)
}
英文:

How can I safely terminate a request outside of a dispatcher (handlefunc) without an explicit return in handlefunc? In the code below, I end up seeing both "Bad request" and "Not found" in my response, but I'd like f() to abort the request -- so I wouldn't have to clutter dispatcher with error checks and returns.

package main

import (
	"net/http"
)

func f(w http.ResponseWriter, r *http.Request, k string) string{
	if v, ok := r.Form[k]; !ok{		
		http.Error(w, "Bad request", http.StatusBadRequest)
		return ""
	}else{
		return v[0]
	}
}

func main(){
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
		foo := f(w, r, "foo") //make sure foo is passed, if not abort with "bad request" without requiring an explicit return here
		bar := f(w, r, "bar") //make sure bar is passed, if not abort with "bad request" without requiring an explicit return here
		//lookup in db...
		//abort with a 404 if not found in db
		http.NotFound(w, r)    		
	})	

	http.ListenAndServe(":6000", nil)
}

The above was an attempt to refactor the following:

func main(){
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
		if foo,ok:=r.Form["foo"];!ok{
			http.Error(w, "Bad request", http.StatusBadRequest)
			return
		}
		if bar,ok:=r.Form["bar"];!ok{
			http.Error(w, "Bad request", http.StatusBadRequest)
			return
		}
		//lookup in db...
		//abort with a 404 if not found in db
		http.NotFound(w, r)    		
	})	

	http.ListenAndServe(":6000", nil)
}

答案1

得分: 2

我不认为在函数内部尝试中止操作是你想要做的。相反,可以使验证过程更简洁。你可以编写类似于package validate的代码,其中包含func Require(form url.Values, required ...string) error,然后在处理程序中使用if err := validate.Require(r.Form, "foo", "bar"); err != nil { ... }

或者你可以使用更通用的验证器:可以接受一个字段名称到验证类型(数字、字符串等)的映射,并返回另一个映射。如果没有错误,则返回nil,否则返回{"fieldName": error}

Reddit上有一些关于这个问题的讨论,其中一个人编写了一个使用结构标签进行验证的库(https://github.com/mccoyst/validate)。还有另一个基于结构体的验证实现在一个新的表单渲染/验证工具包中(https://github.com/cjtoolkit/form)。我都没有尝试过。Reddit上的人们提出了一个棘手的问题,即在应用程序变得更复杂时,你可以在多大程度上抽象验证,而不会遇到妨碍的“框架”,我没有一个简单的答案。


有些情况下,发生了一些非常意外的情况,服务器无法做出更好的响应,只能给用户返回一个不透明的“500 Internal Server Error”响应,而问题实际上出现在你的HandlerFunc之下。典型的例子是编程错误,比如空指针解引用。Go团队的建议是在“错误不可恢复”的情况下使用panic。(像negroni.Recovery这样的工具可以帮助记录panic等错误)

不必要的panic是不好的,因为:

  • panic使得人们更容易忘记必要的错误处理,因为它暗示事情可能会panic,但明确它们可能会err
  • panic/recover错误处理代码难以维护和理解
  • panic与标准的err返回不一致
  • panic使用堆栈展开,比正常的控制流程要慢得多

我快速地使用grep命令搜索了一下,基本上在标准库和其他官方存储库中,所有对panic的调用都是用来捕获编程/内部错误,而不是在遇到意外的外部条件(如错误的输入或I/O错误)时崩溃。像文件/对象未找到或无效的用户输入这样的问题通常可以更优雅地处理,而不是崩溃请求(至少可以返回特定的错误),而且它们并不是真正意外的情况(这是互联网;你会收到无效的输入和对不存在的内容的请求)。因此,panic不是用于正常的请求中止操作,而是用于在发生了“不可能发生”的情况下崩溃并且请求无法继续执行。


同样,在这里,我会在处理程序函数中使用标准的if块来处理无效的输入,但是重新排列你的验证逻辑,使其不需要大量的代码来处理每个必需的参数。

英文:

I don't think trying to abort from within a function is what you want to do here. Instead, make the validation less repetitive. You could write something like package validate with func Require(form url.Values, required ...string) error, and, in your handler, if err := validate.Require(r.Form, "foo", "bar"); err != nil { ... }.

Or you could do a more general validator: maybe take a map of field name to validation type (number, string, etc.) and return another map that's nil if no errors and {"fieldName": error} otherwise.

Redditors talked some about this and one wrote a library that uses struct tags to validate. There's another struct-based validation implementation in a new form rendering/validation toolkit. (I've tried neither.) The Redditors raised the tricky question of how much you can abstract validation before getting a "framework" that gets in the way when your app gets more complicated, and I don't have a simple answer.


There are cases where something really unexpected happens and the server can't do anything better than give the user an opaque "500 Internal Server Error" response, but the problem manifests deep below your HandlerFunc. The canonical example is a programming error like a nil pointer dereference. The advice from the Go team is to use panic when "the error is unrecoverable." (A tool like negroni.Recovery can help log panics, etc.)

Unnecessary panics are lame because:

  • panics make it easier to forget necessary error handling entirely, because it's implied that things can panic but explicit they can err
  • panic/recover error handling code is ugly and hard to maintain
  • panics introduce inconsistency with the standard err returns
  • panics use stack unwinding, which is much slower than normal control flow

I did a quick grep and essentially all calls to panic in the standard library and other official repositories are there to catch programming/internal errors, not crash on surprise external conditions like bad input or I/O errors. Things like file/object not found or invalid user input can usually be handled more gracefully than by crashing the request (it's at least possible to return a specific error), and they aren't really unexpected (it's the Internet; you'll get invalid input and requests for things that don't exist). So panic is not for normal request aborts--it's for crashing when what "can't happen" has happened and the request can't continue.


Again, here, I'd handle invalid input with a standard if block in the handler function, but rearrange your validation so that it doesn't require a lot of code per required argument.

huangapple
  • 本文由 发表于 2014年11月11日 22:35:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/26867307.html
匿名

发表评论

匿名网友

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

确定