How do you guys handle errors in your http handlers, and why?

agolangf · · 462 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>This blog post does a good job of discussing the problem:</p> <p><a href="http://elithrar.github.io/article/http-handler-error-handling-revisited/">http://elithrar.github.io/article/http-handler-error-handling-revisited/</a></p> <p>Do you guys have a central error handling system, as described in the above post, or do you deal with each error in every handler on its own?</p> <p><strong>EDIT</strong> I&#39;m surprised to know that many people actually use panic/recover directly in their http handlers to deal with errors.</p> <hr/>**评论:**<br/><br/>gomaleck13: <pre><p>I have been moving to something like what the article highlights. I think it is a pretty good pattern. </p></pre>daveddev: <pre><p>I use helper functions to &#34;centralize&#34; error handling despite it not allowing single line returns. On that note; It would be nice, within a subroutine which returns nothing, to be able to return with a call to another subroutine which returns nothing. However, I&#39;m not confident that&#39;s a reasonable idea.</p> <p>For the exercise... If I&#39;d like to return two different responses for the same HTTP error code based on some factor, bubbling all processing errors back up to the ServeHTTP method can bloat the act of branching out the control flow. Using helper functions keeps such logic near where it is most relevant.</p> <p>Further, in my opinion, saving a handful of lines of code per handler func is not worth becoming dependent on a custom handler func definition. If there is a more worthwhile trade-off I&#39;m missing, I&#39;d be thankful to learn of it.</p> <p>*Edited for clarity and to add an example.</p></pre>heraclmene: <pre><p>It took me a while but I enjoy the verbosity that Go&#39;s error handling brings, so I handle each error in every handler. </p></pre>neoasterisk: <pre><p>I like to use the technique described here: <a href="https://blog.golang.org/error-handling-and-go#TOC_3." rel="nofollow">https://blog.golang.org/error-handling-and-go#TOC_3.</a></p></pre>m3wm3wm3wm: <pre><p>I cannot see a web app which cannot benefit from the method described in that post. Why is this not in a common package?</p> <p>The list at the end of the post is specially requirements for any web app in production:</p> <blockquote> <ul> <li><p>give the error handler a pretty HTML template,</p></li> <li><p>make debugging easier by writing the stack trace to the HTTP response when the user is an administrator,</p></li> <li><p>write a constructor function for appError that stores the stack trace for easier debugging,</p></li> <li><p>recover from panics inside the appHandler, logging the error to the console as &#34;Critical,&#34; while telling the user &#34;a serious error has occurred.&#34; This is a nice touch to avoid exposing the user to inscrutable error messages caused by programming errors. See the Defer, Panic, and Recover article for more details.</p></li> </ul> </blockquote></pre>neoasterisk: <pre><p>My best guess is that Go gives you simple tools which you can combine to create a full solution the way it fits your needs. I&#39;ve also thought many times that it&#39;s kinda stupid that the normal http handler does not return an error. Then again nobody stops you from making it return an error. Also why should it return an error and not (int, error) or why not *appError? It is not easy to answer these questions and if somehow you do then you end up creating an opinionated web framework and there are already dozens of those.</p> <p>Also after using a custom handler that returns an error for many projects, I&#39;ve come to appreciate using a plain http handler and just have my own custom error function to call inside that functions similar to <code>http.Error</code>. Nevertheless a custom http handler signature is a very good pattern and I believe it is what Go is all about.</p></pre>karma_vacuum123: <pre><p>yeah, the <code>return</code> thing is annoying</p> <p>this is one case where a rich exception system could be useful...just throw well-typed exceptions in your handlers and have something at a higher level catching them. i don&#39;t think i would recommend <code>panic/recover</code> though</p></pre>gbitten: <pre><p>I just saw your comment now. I do the same. I don&#39;t know why people downvote this suggestion. At least, they could explain why.</p></pre>joncalhoun: <pre><p>A couple examples of the types of issues you might see:</p> <ol> <li>Testing is harder (imo).</li> <li>Memory leaks and rogue go routines. </li> </ol> <p>I haven&#39;t verified the second one, but I suspect if your handler first creates a go routine that it needs to tell to stop (like a <a href="https://golang.org/pkg/time/#NewTicker" rel="nofollow">time.Ticker</a>), and then (b) <code>panic</code>s, that the ticker will continue running forever. </p> <p>By using a <code>panic/recover</code> model, you are effectively not giving that handler a chance to clean up after itself when an error does occur, which is especially bad when concurrency is as easy to use as it is in Go.</p> <p>All of that said, I think most of us have probably written a Go server with something like <a href="https://github.com/urfave/negroni" rel="nofollow">negroni</a>&#39;s Recovery middleware and abused it without any issues, but it is possible to run into real issues if you continue using this pattern. </p></pre>gbitten: <pre><blockquote> <p>I haven&#39;t verified the second one, but I suspect if your handler first creates a go routine that it needs to tell to stop (like a time.Ticker), and then (b) panics, that the ticker will continue running forever.</p> </blockquote> <p>No, it won&#39;t. I a panic reaches the top level of a gorotine without a recover it goes panicking:</p> <p><em>&#34;While executing a function F, an explicit call to panic or a run-time panic terminates the execution of F. Any functions deferred by F are then executed as usual. Next, any deferred functions run by F&#39;s caller are run, and so on up to any deferred by the top-level function in the executing goroutine. At that point, the program is terminated and the error condition is reported, including the value of the argument to panic. This termination sequence is called panicking.&#34;</em></p> <p><a href="https://golang.org/ref/spec#Handling_panics" rel="nofollow">https://golang.org/ref/spec#Handling_panics</a></p></pre>joncalhoun: <pre><p>This only refers to deferred function calls. </p> <p>What if the stop function call wasn&#39;t a deferred function but was called some other way? It sounds like this could still happen.</p></pre>gbitten: <pre><p>That user case (error handling between two goroutines) is not appropriated for &#34;panic, defer and recover&#34;. Rob Pyke wrote about it:</p> <p>&#34;Another proposal may happen regarding handling/monitoring exceptions in goroutine A from the safety of goroutine B, but that is not part of this proposal and may not happen at all, although we believe it may be achievable entirely by library code given this proposal.&#34;</p> <p><a href="https://groups.google.com/forum/#!topic/golang-nuts/HOXNBQu5c-Q" rel="nofollow">https://groups.google.com/forum/#!topic/golang-nuts/HOXNBQu5c-Q</a></p> <p>Anyway, the same problem could happen with a conventional error handling and should be resolved case by case.</p></pre>daveddev: <pre><p><a href="https://blog.golang.org/errors-are-values" rel="nofollow">https://blog.golang.org/errors-are-values</a></p> <p>Obscuring (and likely jeopardizing) control flow over non-exceptional errors is reckless.</p></pre>seriouslulz: <pre><p>Practicality beats purity</p></pre>gbitten: <pre><p>Yes, errors are values, and I can have a lot better flow control using panic, defer and recover to handle those values. I don&#39;t understand why I can&#39;t use them when the language allows me to. </p></pre>daveddev: <pre><p>The idea is plainly not idiomatic. I don&#39;t downvote ideas that I don&#39;t like, I downvote ideas that are wrong. It is specifically prescribed to not use panic as a normal control flow mechanism.</p></pre>j7b: <pre><p>Idiomatic depends on context. I&#39;d suggest given an interface that doesn&#39;t define an error type as a return value, and functions that take that type as a parameter and in the course of calling the implementation recover panics, panic becomes a reasonable flow control mechanism and defining your own recovery mechanism can be viewed as an enhancement of the default recovery mechanism.</p> <p>I started using panic/recover in handlers because the code became easier to read and reason about and design errors became more glaring at runtime, which I think trumps idiomatic in every case.</p></pre>daveddev: <pre><p>Of course it depends on context. It is not appropriate in this context. </p> <p>If you are calling panic() for things which should not panic, it is wrong. Events which should panic are:</p> <ul> <li>logically impossible (covered for thoroughness/clarity/etc.)</li> <li>unrecoverable</li> </ul></pre>gbitten: <pre><p>&#34;In summary, the defer statement (with or without panic and recover) provides an unusual and POWERFUL mechanism for control flow. It can be used to model a number of features implemented by special-purpose structures in other programming languages. Try it out.&#34;</p> <p>By Andrew Gerrand</p> <p><a href="https://blog.golang.org/defer-panic-and-recover" rel="nofollow">https://blog.golang.org/defer-panic-and-recover</a> </p></pre>daveddev: <pre><p>Please read that over. It&#39;s not saying what you think it&#39;s saying.</p></pre>gbitten: <pre><p>Let me see if I understand. The standard JSON package (<a href="https://golang.org/src/encoding/json/decode.go" rel="nofollow">https://golang.org/src/encoding/json/decode.go</a>) makes use of dozens of panics to simplify its flow control. So, for you, those are not idiomatic too, right?</p></pre>daveddev: <pre><p>Again. Please read the code over. It&#39;s not doing what you think it&#39;s doing. There are common factors as to why panic is called. General control flow is not one of them. Please reconsider your position.</p> <p>From another reply of mine here:</p> <blockquote> <p>If you are calling panic() for things which should not panic, it is wrong. Events which should panic are:</p> <ul> <li>logically impossible (covered for thoroughness/clarity/etc.)</li> <li>unrecoverable</li> </ul> </blockquote></pre>ultra_brite: <pre><blockquote> <p>logically impossible unrecoverable</p> </blockquote> <p>What does the json package do that is logically impossible or unrecoverable so it has to use panics ? Same as reflection, why does reflect has API that panics ? it could return errors instead . Of course it doesn&#39;t, since panicking is easier. So what isn&#39;t idiomatic again ? the std lib ? or someone that told you that it is not idiomatic ? </p></pre>kaeshiwaza: <pre><p>I do also like this article.</p> <p>Also in Env i put a logger that i use to log some infos. If an error is returned from the handler I record all the infos and the error and send it to an other server (like sentry). If no error I don&#39;t print any log. If I start my app with -debug I print all the log every time.</p> <p>I also use the errors package of Dave Cheney to record the traceback and remove all the lines of the stack before the handler to keep it smaller.</p></pre>j7b: <pre><p>I have to start with objections to the blog post&#39;s implementation: *Env is being treated as a surrogate global, Handler is intended to be used as a pseudosingleton, and in many cases, certainly this one, you can accomplish what&#39;s accomplished with the H member of the Handler type with an interface. </p> <p>The underlying thought process appears to conflict with my design philosophy. I tend to start from easiest to test and maximum coverage, which enforces separation of concerns, so in general it results in a hybrid approach to handler implementations with a short circuit path to handle protocol and related common logic that handle http.ResponseWriter themselves and a panic/recover mechanism for functions and methods that return an error type, generally along the lines of:</p> <pre><code>defer Recover(w) if session(w,r) &amp;&amp; auth(w,r) &amp;&amp; postform(w,r) { data, err := doSomething(r.Form) if err != nil { PanicCode(500,err) } writeJSON(w,data) } </code></pre> <p>I believe handler implementations should mostly concern themselves with the protocol and be as short as possible with as few logic flows as possible. The example in the blog post makes implementations difficult to test and exhibits an unnecessarily complicated usage and type graph. </p></pre>gbitten: <pre><p>Instead of returning an error code, I prefer to call &#34;Panic&#34; at any level of my handler and &#34;Defer&#34; a &#34;Recover&#34; at the highest level of the handler.</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

462 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传