dongrang9300 2016-03-13 22:45
浏览 52
已采纳

在REST 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 necessaryCustomerLocationChange(..)` handler to handle errors properly so that if an error occurs, my entire API doesn't go down?

  • 写回答

1条回答 默认 最新

  • duanri1985 2016-03-14 00:39
    关注

    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.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 MATLAB动图的问题
  • ¥15 求差集那个函数有问题,有无佬可以解决
  • ¥15 【提问】基于Invest的水源涵养
  • ¥20 微信网友居然可以通过vx号找到我绑的手机号
  • ¥15 寻一个支付宝扫码远程授权登录的软件助手app
  • ¥15 解riccati方程组
  • ¥15 display:none;样式在嵌套结构中的已设置了display样式的元素上不起作用?
  • ¥15 使用rabbitMQ 消息队列作为url源进行多线程爬取时,总有几个url没有处理的问题。
  • ¥15 Ubuntu在安装序列比对软件STAR时出现报错如何解决
  • ¥50 树莓派安卓APK系统签名