質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.49%

Go言語でのRest APIの実装

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 2,190

score 78

ご閲覧いただきありがとうございます。

やりたいこと

Go言語で、標準パッケージのみでRest APIを実装出来るようになりたいです。
しかし、ネットにある情報ではGorilla muxを使ったものばかりで参考になりません。

やってみたこと

自分でも以下のように実装してみたのですが、これだとgorilla muxを使っていないのでHTTPリクエストメソッドと自前で用意したメソッドを関連づけられません。

package main

import (
    "fmt"
    "net/http"
)

func homePage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "home page hit")
}

func main() {
    // handleRequest()
    http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello,") //Gorilla muxを使えば、ここに .Methods("GET")というようにHTTPメソッドを紐づけられる
      })
      http.ListenAndServe(":8080", nil)
}

Gorilla muxを使った実装はこちらです。これでやりたいことは実現できているのですが、標準パッケージのみで出来るようになりたいです。

package main

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

    "github.com/gorilla/mux"
)

func homeLink(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Welcome home!")
}

func main() {
    router := mux.NewRouter().StrictSlash(true)
    router.HandleFunc("/", homeLink)
    router.HandleFunc("/events", getAllEvents).Methods("GET")
    router.HandleFunc("/events/{id}", getOneEvent).Methods("GET")

    http.ListenAndServe(":8080", router)
}

type event struct {
    ID          string `json:"ID"`
    Title       string `json:"Title"`
    Description string `json:"Description"`
}

type allEvents []event

var events = allEvents{
    {
        ID:          "11",
        Title:       "Introduction to Golang",
        Description: "Come join us for a chance to learn how golang works and get to eventually try it out",
    },
}

func getOneEvent(w http.ResponseWriter, r *http.Request) {
    eventID := mux.Vars(r)["id"]

    for _, singleEvent := range events {
        if singleEvent.ID == eventID {
            json.NewEncoder(w).Encode(singleEvent)
        }
    }
}

func getAllEvents(w http.ResponseWriter, r *http.Request) {
    json.NewEncoder(w).Encode(events)
}

もしお分かりになる方がいましたら、どうかお力をお貸しください。よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+3

要約するとルーターを標準パッケージでどうつくるかという質問ですね。

提示された例の程度であればほとんど標準のルーターで十分にまかなえます。

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", homeLink)
    mux.HandleFunc("/events", getAllEvents)
    mux.Handle("/events/", http.StripPrefix("/events/", http.HandlerFunc(getOneEvent)))

    http.ListenAndServe(":8080", mux)
}

{id}を取り出す部分は以下のコードで取り出せます。

func getOneEvent(w http.ResponseWriter, r *http.Request) {
    eventID := r.URL.Path
    ...
}


ポイントは予めhttp.StripPrefixでURLの先頭を削っておいたところですね。

goではhttpハンドリングに使える型に以下の2種類があります。

  • 「Handler」型(ServeHTTPメソッドをもつ)
  • 「func (w http.ResponseWriter, r *http.Request)」関数型

「func (w http.ResponseWriter, r *http.Request)」関数型は
HandlerFunc型にキャストすればHandlerとコンパチになることを覚えておくと良いでしょう。

いろんなミドルウェアはHandlerをラップしてHandlerを形成するパターンが多いので
Handler互換であればいろんなミドルウェアが使えます。
そのひとつが「http.StripPrefix」です。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/09/23 08:00

    同様にGET以外のメソッドをエラーにするミドルウェアを書いてハンドラをラップしておくと完全に互換のコードになりますので挑戦してみてください。

    キャンセル

  • 2019/09/24 10:07

    gorillaのmuxの「.Methods("GET")」というのは別に紐づけるとかではなく、Methodsに指定したメソッド以外をhttp.StatusMethodNotAllowedステータスを返すということです。
    そのような処理をハンドラ側に書いてもいいし、ミドルウェアでそのような処理を挟んでもいいわけです。
    やれる方法で書いてみていただけたらアドバイスします。

    キャンセル

  • 2019/09/24 10:08

    http.RequestにはMethodというフィールドがあります。そこをチェックするだけなので頑張ってみましょう。

    キャンセル

  • 2019/09/25 19:33

    丁寧なご回答ありがとうございます。自分のやり方を記入致しますので、アドバイスいただけると非常にありがたいです。

    キャンセル

checkベストアンサー

+1

上記回答例のように振り分け用にハンドラを一つ挟むやり方は最もオススメです。
応用が一番効くので。GETだけに反応させたければ以下のように書きましょう。
もちろんGETとPOSTそれぞれに違う処理を入れるのはアリです。

func handleTrustRequest(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case "GET":
        handleTrustGet(w, r)
    default:
        http.Error(w, r.Method + " method not allowed", http.StatusMethodNotAllowed)
    }
}


エラーを上記のように書いた場合、クライアントに届けるエラーのコンテンツがテキストになります。
JSONである必要があればContent-Typeにapplication/jsonを指定してから
JSONを文字列に変換したものを返すなどしましょう。

で、ミドルウェア方式は以下のように書きます。

func GetOnly(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    if r.Method != "GET" {
        http.Error(w, r.Method + " method not allowed", http.StatusMethodNotAllowed)
    }
    next.ServeHTTP(w, r)
  })
}

func main() {
    http.Handle("/trust", GetOnly(http.HandlerFunc(handleTrustGet)))
}

上記の例だとじゃぁPOSTの対応を増やすには?というと結局分岐を挟まないといけなくなって、
ミドルウェアの体裁で分岐を書くのはちょっと骨が折れます。なので質問者さんの回答の書き方を
基本に書いておくと良いと思います。(標準だけでMethod別に振り分けする方法としてはベストじゃないかと)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/09/26 12:01

    丁寧なご回答ありがとうございます。コードを確認していただけて、サンプルコードまで提示していただけて、とても助かりました。ありがとうございました。

    キャンセル

  • 2019/09/26 12:06

    それと、あと一つだけ教えていただきたいのですが、なぜ二つ目のコードはミドルウェア方式になるのでしょうか?
    ミドルウェアとは、「オペレーションシステム(OS)」と「アプリケーションソフト」の中間に入るソフトウェアのことだと思うのですが、2つ目のどこにコードにミドルウェア要素があるのかが分かりません・・・。
    参考になるリンクだけ提示していただくのでも良いので、簡単に教えていただけると助かります。

    キャンセル

  • 2019/09/26 12:39

    それはもう少し狭い意味の「ミドルウェア」ですね。
    https://www.alexedwards.net/blog/making-and-using-middleware
    このあたりなどで使われる「ミドルウェア」はPythonのデコレータのように呼び出しの間に差し込む小さな実装も含んでいます。

    キャンセル

  • 2019/09/26 17:46

    なるほど、それは知りませんでした。ありがとうございます。覚えておきます。

    キャンセル

0

nobonobo様

ここではベタ書きしたJSON形式の構造体のデータを使っていますが、一応リソースとURIを対応づけていますし、HTTPリクエストメソッドを通して内部処理をしているので、これだけでRest APIになっていると言えると思うのですが、いかがでしょうか?改善のためアドバイス頂けますと幸いです。

func main() {

    http.HandleFunc("/trust", handleTrustRequest
    http.HandleFunc("/belief", handleBeliefRequest)
    http.ListenAndServe(":8080", nil)
}

func handleTrustRequest(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case "GET":
        handleTrustGet(w, r)
    case "POST":

    }
}

type trust struct {
    ID string `json:"ID"`
    Title string `json:"Title"`
    Description string `json:"Description"`
}

type allTrusts []trust

var trusts = allTrusts{
    {
        ID:          "11",
        Title:       "trust",
        Description: "Come join us for a chance to learn how golang works and get to eventually try it out",
    },
}

func handleTrustGet(w http.ResponseWriter, r *http.Request) {
    json.NewEncoder(w).Encode(trusts)
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.49%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る