This is a creation in Article, where the information may have evolved or changed.
With the growth of mobile, now RESTful style API is very popular, in a variety of languages to write back-end API has a very mature and convenient solution, with Golang write back-end API is a productivity representative, you can not lose python/ruby such dynamic language speed, write high performance One or two orders of magnitude back-end APIs.
ECHO Frame
Because the Golang standard library is already very perfect in the network aspect, causes the frame to play the room to be not big. Many experts say, with what framework, with the standard library is written, the framework is only the grammatical sugar, but also restricts the development of the project.
But we are not a master, grammar sugar is also sugar, with a hand in the frame or can improve a lot of efficiency.
If in six months ago, you let me recommend the framework, I would say there are many, all have advantages and disadvantages, in addition to Beego casually choose one can. But in 2017, a framework called Echo stood out. This is my most recommended framework at the moment.
Echo's advocacy pragmatic is "high-performance, easy to expand, minimalist Go Web framework." Some of its features are as follows:
In these features, Http/2,auto HTTPS, listening very well? This is the Caddy I introduced earlier, because Golang is so easy to implement. There are also a lot of functions in the middleware. When we're doing microservices, these generic things are done by the API Gateway, and if you're writing a small, standalone app backend, these out-of-the-box features can be a great help.
In fact, today I would like to talk about the last feature mentioned, "Centralized HTTP error handling."
RESTful API Error returned
A team should have a specification for the RESTful API, and the response format should be normalized in the specification, including the format of all error responses. such as Microsoft's specifications, jsonapi.org recommendation specifications and so on.
Most of the time we don't need to implement the tedious, we specify a simple structure:
STATUS 400 Bad Request
{
"error": "InvalidID",
"message": "invalid id in your url query parameters"
}
The traditional error response may only have a string type of message that accompanies the HTTP Status code, and now we turn the normal response format into JSON, then return the error with JSON. In addition to using JSON, we have added an error field, which is a level key that is more detailed than the Status code, and the consumer can use the agreed key for more flexible error handling.
Well, let's go on with this simple example, and today's topic is about Echo's approach to unified processing.
How does Echo handle errors uniformly?
In fact, the Echo of the document although very beautiful, but not enough detail, in-depth a bit of content and examples are not. But a beautiful Golang project, the code is the document, we should have to go to http://godoc.org The habit of checking documents. We find Echo's Godocand look at the Echo type:
type Echo struct {
Server *http.Server
TLSServer *http.Server
Listener net.Listener
TLSListener net.Listener
DisableHTTP2 bool
Debug bool
HTTPErrorHandler HTTPErrorHandler
Binder Binder
Validator Validator
Renderer Renderer
AutoTLSManager autocert.Manager
Mutex sync.RWMutex
Logger Logger
// contains filtered or unexported fields
}
Sure enough, you can define Httperrorhandler, follow through,
// HTTPErrorHandler is a centralized HTTP error handler.
type HTTPErrorHandler func(error, Context)
It is a function that passes in the error and Context and has no return value. But you know this is a little dizzy? Do not know how to write this function ah. It's okay, this article is about how to write this function. Look down.
Defining the error structure
Since Golang is a static type, we need to define a structure first, the code is as follows:
type httpError struct {
code int
Key string `json:"error"`
Message string `json:"message"`
}
func newHTTPError(code int, key string, msg string) *httpError {
return &httpError{
code: code,
Key: key,
Message: msg,
}
}
// Error makes it compatible with `error` interface.
func (e *httpError) Error() string {
return e.Key + ": " + e.Message
}
We've done three things here.
- Defines the structure of the error, which contains Code,key and Message,key and the message can be exported as JSON.
- Make a new error structure function, so you can use a line of code to create a new error.
- The error function is added to this structure so that the structure becomes a Golang error interface.
Handling Errors
We can finally write the custom function mentioned above, first look at the sample code I'll explain, and then you can write your own:
package main
import (
"net/http"
"github.com/labstack/echo"
)
// httpErrorHandler customize echo's HTTP error handler.
func httpErrorHandler(err error, c echo.Context) {
var (
code = http.StatusInternalServerError
key = "ServerError"
msg string
)
if he, ok := err.(*httpError); ok {
code = he.code
key = he.Key
msg = he.Message
} else if config.Debug {
msg = err.Error()
} else {
msg = http.StatusText(code)
}
if !c.Response().Committed {
if c.Request().Method == echo.HEAD {
err := c.NoContent(code)
if err != nil {
c.Logger().Error(err)
}
} else {
err := c.JSON(code, newHTTPError(code, key, msg))
if err != nil {
c.Logger().Error(err)
}
}
}
}
The function is to assemble the appropriate HTTP response based on the error and context in which it is passed. But because the Golang error is an interface, that is, the first parameter may come in any strange things, we need to deal with it carefully.
In the first part we define the default values as the worst case scenario, in the HTTP API, if the consumer sees this worst case scenario, you're going to be deducted from the bonus, unless you can throw the pot to your dependent module or infrastructure.
In the second part, we first look at the errors that we have defined before, and if so, it would be great. If not, it seems to be an other unknown error, if the debug is open, it is OK, no deduction bonus, we put the error details directly back to the MSG convenient debugging. If you do not open Debug ... That had to bite the bullet back to 500 and what information is not given.
The third part you can basically copy, is to check the context of whether or not to declare that the response has been submitted, only when not submitted, we need to put our prepared error message in JSON format, by the way print error log. In addition, if the request is the HEAD method, according to the specification, you can only return status 204 and silently in the log record error.
Application
Well, we have written a unified error handling, how to use it? Let's look at a minimalist example:
func getUser(c echo.Context) error {
var u user
id := c.Param("id")
if !bson.IsObjectIdHex(id) {
return newHTTPError(http.StatusBadRequest, "InvalidID", "invalid user id")
}
err := db.C("user").FindId(bson.ObjectIdHex(id)).One(&u)
if err == mgo.ErrNotFound {
return newHTTPError(http.StatusNotFound, "NotFound", err.Error())
}
if err != nil {
return err
}
return c.JSON(http.StatusOK, u)
}
This is an example of taking the user from MongoDB,
- Check that the ID in the URL is not a valid ID, or return our previous custom error.
- Go to the database and return a 404 error if there is no record.
- If there are other errors in querying the database, there is nothing we can do about it, so we have to return the error directly.
- If everything is OK, we return the status 200 and JSON data.
We can see, after such a toss, in the writing API, the worry a lot. We can construct the error with a line of code, or we can directly return any error that we can't predict, and we don't have to construct a 500 error every time we bother.
What do you think? It's really convenient to go to the Amway partners and write the HTTP API with Echo.