post Image
echoのAPIサーバ実装とエラーハンドリングの落とし穴

基本的な実装方法について

公式ドキュメントから転記

package main

import (
    "net/http"
    "strconv"

    "github.com/labstack/echo"
    "github.com/labstack/echo/engine/standard"
    "github.com/labstack/echo/middleware"
)

type (
    user struct {
        ID   int    `json:"id"`
        Name string `json:"name"`
    }
)

var (
    users = map[int]*user{}
    seq   = 1
)

//----------
// Handlers
//----------

func createUser(c echo.Context) error {
    u := &user{
        ID: seq,
    }
    if err := c.Bind(u); err != nil {
        return err
    }
    users[u.ID] = u
    seq++
    return c.JSON(http.StatusCreated, u)
}

func getUser(c echo.Context) error {
    id, _ := strconv.Atoi(c.Param("id"))
    return c.JSON(http.StatusOK, users[id])
}

func updateUser(c echo.Context) error {
    u := new(user)
    if err := c.Bind(u); err != nil {
        return err
    }
    id, _ := strconv.Atoi(c.Param("id"))
    users[id].Name = u.Name
    return c.JSON(http.StatusOK, users[id])
}

func deleteUser(c echo.Context) error {
    id, _ := strconv.Atoi(c.Param("id"))
    delete(users, id)
    return c.NoContent(http.StatusNoContent)
}

func main() {
    e := echo.New()

    // Middleware
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())

    // Routes
    e.POST("/users", createUser)
    e.GET("/users/:id", getUser)
    e.PUT("/users/:id", updateUser)
    e.DELETE("/users/:id", deleteUser)

    // Start server
    e.Run(standard.New(":1323"))
}

これでOKではない!!

これはあくまでも、Routingの仕方とHandlerの実装方法を書いているだけで例外パターンは想定されていません。
実際にサービスを作るときにはこれだけでは足りないのです。

ではどうするか

1. エラーハンドラをカスタマイズする

APIサーバを構築する場合、正常でも異常でも同じ形式で結果を返すべきだと考えます。
(正常系がJSONで返すなら、異常でもJSONを返すべき)

しかし、デフォルトのエラーハンドラはtext形式 (echo.Context.String) で
エラーレスポンスを返します。
これは、echoが全ての形式を統一するのではなく、ハンドラ毎にレスポンスの形式を設定できるようになっているからです。

例えば、公式ドキュメントの使用例の場合

GET_(/users/1)にアクセス
{
    name: "foobar"
}

などになるのに対して、エラーの場合、次のようになります。

Routingされてないエンドポイントにアクセス(例:/contents/1)
Not Found

これを解消するためにカスタマイズしたエラーハンドラを設定します。

type APIError struct {
    Code int
    Message string
}

func JSONErrorHandler(err error, context *echo.Context) {
    code := http.StatusInternalServerError
    msg := http.StatusText(code)

    if he, ok := err.(*HTTPError); ok {
        code = he.Code
        msg = he.Message
    }
    if e.debug {
        msg = err.Error()
    }

    var apierr APIError
    apierr.Code    = code
    apierr.Message = msg

    if !c.Response().Committed() {
        c.JSON(code, apierr)
    }
    e.logger.Debug(err)
}

これをRouter( main() )で設定します。

func main() {
    e := echo.New()

    // Middleware
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())

    // 今回追加したところ
    e.SetHTTPErrorHandler(JSONErrorHandler)

    // Routes
    e.POST("/users", createUser)
    e.GET("/users/:id", getUser)
    e.PUT("/users/:id", updateUser)
    e.DELETE("/users/:id", deleteUser)

    // Start server
    e.Run(standard.New(":1323"))
}

2. エラーは error で返すだけでは不十分

エラーを返す場合は

if err := c.Bind(u); err != nil {
    return err
}

のような箇所を

if err := c.Bind(u); err != nil {
    var apierr APIError
    apierr.Code    = 100
    apierr.Message = "invalid request"

    c.JSON(htt.StatusBadRequest, apierr)
    return err
}

とするか
もしくはWrapperを作った方がいいです

func JSONError(c *echo.Context, status int err error, code int, msg string) error {
    var apierr APIError
    apierr.Code    = code
    apierr.Message = msg

    c.JSON(status, apierr)
    return err
}

if err := c.Bind(u); err != nil {
    return JSONError
}

先ほどの作ったエラーハンドラでは、HTTP Statusからcodeとmessageを生成していました。
この場合であれば、先ほどのエラーハンドラでだけでも問題ありません。
しかし、実際APIを作る場合は独自のエラーコードとメッセージを返したり、
ユーザ向けのエラーメッセージとその詳細を返す場合があります。
この場合だと、先ほど作ったエラーハンドラでは処理が足りません。

エラー毎にきちんとエラーレスポンスを書くのにはまだ利点があります。
仮にエラーハンドラ内で独自のエラーコードや詳細など付随する情報を自動生成したとしても
特定のエラーの場合には例外的に通常とは異なるエラーを返したい場合があります。
このような場合に柔軟に対応するためにも個別にエラーレスポンスを返せるようにしておいたほうがいいのです。

もう一つ注目すべきなのが改修後も err を返しているところです。
これは echo.Context.JSONecho.Context.String がHTTP Statusに関係なく nil を返してしまい、Unitテストの弊害になってしまうためです。
Unitテストに関しては別の機会に書きます。

labstack/echo の構築例はパターンが多く、とても良く書かれていますが、
それでも機能説明に最低限のところしか書かれていません。

実際に使用する場合は関数をよく理解したうえで使いましょう。


『 Go 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。