在REST API中进行Go错误处理

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

Go Error handling on a REST API

问题

我非常新手Go语言,并且已经部署了一个带有API端点的小型服务。

我听说/读到Go语言不使用try/catch,所以我正在尝试找出如何从我的API中“捕获”任何发生的问题,并确保资源服务器不会崩溃。

我的API代码如下所示:

我有一个名为routes.go的文件,内容如下:

package main

import (
    "net/http"

    "github.com/gorilla/mux"
)

type Route struct {
    Name        string
    Method      string
    Pattern     string
    HandlerFunc http.HandlerFunc
}

type Routes []Route

func NewRouter() *mux.Router {
    router := mux.NewRouter().StrictSlash(true)
    for _, route := range routes {
        router.
            Methods(route.Method).
            Path(route.Pattern).
            Name(route.Name).
            Handler(route.HandlerFunc)
    }

    return router
}

var routes = Routes{
    Route{
        "CustomerLocationCreate",
        "POST",
        "/tracking/customer",
        CustomerLocationCreate,
    },
}

我还有一个名为handlers.go的文件,内容如下:

package main

import (
    "encoding/json"
    "net/http"
    "io"
    "io/ioutil"
)

//curl -H "Content-Type: application/json" -d '{"userId":"1234"}' http://localhost:8181/tracking/customer
func CustomerLocationCreate(w http.ResponseWriter, r *http.Request) {
    var location CustomerLocation
    body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
    if err != nil {
        panic(err)
    }
    if err := r.Body.Close(); err != nil {
        panic(err)
    }
    if err := json.Unmarshal(body, &location); err != nil {
        w.Header().Set("Content-Type", "application/json; charset=UTF-8")
        w.WriteHeader(422) // unprocessable entity
        if err := json.NewEncoder(w).Encode(err); err != nil {
            panic(err)
        }
    }

    c := RepoCreateCustomerLocation(location)
    w.Header().Set("Content-Type", "application/json; charset=UTF-8")
    w.WriteHeader(http.StatusCreated)
    if err := json.NewEncoder(w).Encode(c); err != nil {
        panic(err)
    }
    
    HandleCustomerLocationChange(c);
}

我还有一个名为bus.go的文件,其中包含HandleCustomerLocationChange(...)函数。

func HandleCustomerLocationChange(custLoc CustomerLocation) {
    
    endpoint := og.Getenv("RABBIT_ENDPOINT")
    conn, err := amqp.Dial("amqp://guest:guest@" + endpoint)
    failOnError(err, "Failed to connect to RabbitMQ")
    defer conn.Close()

    ch, err := conn.Channel()
    failOnError(err, "Failed to open a channel")
    defer ch.Close()

    topic := "locationChange"
    err = ch.ExchangeDeclare(
        topic,   // name
        "topic", // type
        true,    // durable
        false,   // auto-deleted
        false,   // internal
        false,   // no-wait
        nil,     // arguments
    )
    failOnError(err, "Failed to declare an exchange")

    // Create JSON from the instance data.
    body, _ := json.Marshal(custLoc)
    // Convert bytes to string.

    err = ch.Publish(
        topic, // exchange
        "",    // routing key
        false, // mandatory
        false, // immediate
        amqp.Publishing{
            ContentType: "text/plain",
            Body:        body,
        })
    failOnError(err, "Failed to publish a message")

    log.Printf(" [x] Sent %s", body)

}

我的问题是,我应该如何修改HandleCustomerLocationChange(...)函数以及必要时修改CustomerLocationChange(...)处理程序,以正确处理错误,以便如果发生错误,我的整个API不会崩溃?

英文:

I am very new to go and have deployed a small service with an API endpoint.

I have heard/read that go doesn't use try/catch so I am trying to figure out how I can "catch" any problems happening from my service call from my API and make sure that the resource server doesn't go down.

My code for my API looks like the following..

I have a routes.go file with the following

package main

import (
    "net/http"

    "github.com/gorilla/mux"
)

type Route struct {
    Name        string
    Method      string
    Pattern     string
    HandlerFunc http.HandlerFunc
}

type Routes []Route

func NewRouter() *mux.Router {
    router := mux.NewRouter().StrictSlash(true)
    for _, route := range routes {
        router.
            Methods(route.Method).
            Path(route.Pattern).
            Name(route.Name).
            Handler(route.HandlerFunc)
    }

    return router
}

var routes = Routes{
    Route{
        "CustomerLocationCreate",
        "POST",
        "/tracking/customer",
        CustomerLocationCreate,
    },
}

I have a handlers.go

package main

import (
    "encoding/json"
    "net/http"
    "io"
    "io/ioutil"
)

//curl -H "Content-Type: application/json" -d '{"userId":"1234"}' http://localhost:8181/tracking/customer
func CustomerLocationCreate(w http.ResponseWriter, r *http.Request) {
    var location CustomerLocation
    body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
    if err != nil {
        panic(err)
    }
    if err := r.Body.Close(); err != nil {
        panic(err)
    }
    if err := json.Unmarshal(body, &location); err != nil {
        w.Header().Set("Content-Type", "application/json; charset=UTF-8")
        w.WriteHeader(422) // unprocessable entity
        if err := json.NewEncoder(w).Encode(err); err != nil {
            panic(err)
        }
    }

    c := RepoCreateCustomerLocation(location)
    w.Header().Set("Content-Type", "application/json; charset=UTF-8")
    w.WriteHeader(http.StatusCreated)
    if err := json.NewEncoder(w).Encode(c); err != nil {
        panic(err)
    }
    
    HandleCustomerLocationChange(c);
}

and I have a bus.go which has the HandleCustomerLocationChange(...) function.

func HandleCustomerLocationChange(custLoc CustomerLocation) {
    
    endpoint := og.Getenv("RABBIT_ENDPOINT")
    conn, err := amqp.Dial("amqp://guest:guest@" + endpoint)
    failOnError(err, "Failed to connect to RabbitMQ")
    defer conn.Close()

    ch, err := conn.Channel()
    failOnError(err, "Failed to open a channel")
    defer ch.Close()

    topic := "locationChange"
    err = ch.ExchangeDeclare(
        topic,   // name
        "topic", // type
        true,    // durable
        false,   // auto-deleted
        false,   // internal
        false,   // no-wait
        nil,     // arguments
    )
    failOnError(err, "Failed to declare an exchange")

    // Create JSON from the instance data.
    body, _ := json.Marshal(custLoc)
    // Convert bytes to string.

    err = ch.Publish(
        topic, // exchange
        "",    // routing key
        false, // mandatory
        false, // immediate
        amqp.Publishing{
            ContentType: "text/plain",
            Body:        body,
        })
    failOnError(err, "Failed to publish a message")

    log.Printf(" [x] Sent %s", body)

}

My question is how should I modify both the HandleCustomerLocationChange(...) function and if necessary CustomerLocationChange(..)` handler to handle errors properly so that if an error occurs, my entire API doesn't go down?

答案1

得分: 29

Go提出了一种不同的方法,即错误不是异常,而是正常事件,只是不太常见。

以上面的代码示例为例:

body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
if err != nil {
    panic(err)
}

在这里,一个没有恢复的panic会终止进程,关闭Web服务器。这似乎是对未完全读取请求的过度严厉的响应。

你想要做什么?告诉发出请求的客户端可能是合适的:

body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
if err != nil {
    http.Error(w, err.Error(), http.StatusBadRequest)
    return
}

你可能想返回一个JSON编码的响应,或者给客户端一个通用的消息,避免暴露太多细节,并记录具体的错误详情。

对于一般的函数,惯用的做法是将错误作为最后一个返回参数。在你提到的具体示例中:

func HandleCustomerLocationChange(custLoc CustomerLocation) 
...
    conn, err := amqp.Dial(...)
    failOnError(err, "Failed to connect to RabbitMQ")

相反,检查连接是否失败,并将错误返回给调用者。在调用函数中处理它,或者添加信息并将其传播到调用堆栈上。

func HandleCustomerLocationChange(custLoc CustomerLocation) error
...
    conn, err := amqp.Dial(...)
    if err != nil {
        return fmt.Errorf("failed to connect to RabbitMQ: %s", err)
    }

以这种方式传播错误可以简明地解释根本原因,就像5个为什么技术一样,例如:

"未更新客户端位置:未连接到RabbitMQ:网络地址1.2.3不可达"

另一个惯例是首先处理错误并尽早返回。这有助于减少嵌套。

还可以参考许多错误处理资源,例如Web应用程序中的错误处理Go by Example错误处理和Go错误是值延迟、恐慌和恢复错误包的源代码很有趣,以及Russ Cox关于错误处理的评论Nathan Youngman的To Err is Human

还有一个有趣的是Upspin的操作跟踪概念,而不是堆栈跟踪。

英文:

Go suggests a different approach, that errors are not exceptional, they're normal events, just less common.

Taking an example from the code above:

body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
if err != nil {
    panic(err)
}

Here, a panic (without recovery) terminates the process, shutting down the web server. Seems an overly severe response to not fully reading a request.

What do you want to do? It may be appropriate to tell the client who made the request:

body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
if err != nil {
    http.Error(w, err.Error(), http.StatusBadRequest)
    return
}

You might want to return a json encoded response, or give a generic message to the client avoid exposing too much, and log the specific error details.

For general functions it's idiomatic to return the error as the last return parameter. In the specific example you mentioned:

func HandleCustomerLocationChange(custLoc CustomerLocation) 
...
    conn, err := amqp.Dial(...)
    failOnError(err, "Failed to connect to RabbitMQ")

Instead, check if the connection failed, and return the error to the caller. Handle it in the calling function, or add information and propagate it up the call stack.

func HandleCustomerLocationChange(custLoc CustomerLocation) error
...
    conn, err := amqp.Dial(...)
    if err != nil {
        return fmt.Errorf("failed to connect to RabbitMQ: %s", err)
    }

Propagating the error in this way gives a concise explanation of the root cause, like the 5 whys technique, eg:

"did not update client location: did not connect to rabbitmq: network address 1.2.3 unreachable"

Another convention is to deal with errors first and return early. This helps to reduce nesting.

See also the many error handling resources, like error handling in a web application, Go by Example, Error Handling and Go, errors are values and Defer, Panic & Recover. The source code of the error package is interesting, as is Russ Cox's comment on error handling, and Nathan Youngman's To Err is Human.

Also interesting is Upspin's concept of an operational trace, rather than a stack trace.

huangapple
  • 本文由 发表于 2016年3月14日 06:45:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/35976840.html
匿名

发表评论

匿名网友

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

确定