20210414のGoに関する記事は2件です。

Golangでスライスから最大値、最小値を取得する方法

なぜ書いたか Goにはmax関数とmin関数が用意されていないので。 答え ソートして頭かお尻を取れば良い。 import "sort" func max(s []int) int { sort.IntSlice(a).Sort() return a[len(a)-1] } func min(s []int) int { sort.IntSlice(a).Sort() return a[0] }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go言語】net/httpだけでREST APIを立ててみる

最近Goに入門しました。 勉強のため外部パッケージなしの手作りREST API立ててみました。 フルスクラッチでRESTってあまりやっている人がいなかったので記事にしてみます。(go-json-restはいっぱいあったんですが) 入門者なので、イケてない部分があれば(優しく)教えてください。 やりたいこと 標準パッケージ("net/http")だけでREST APIを立てる。 外部パッケージは使わない。 最も単純な実装 ハンドラの中でr.Method条件分けすればOK。 func main() { http.HandleFunc("/", restHandler) http.ListenAndServe(":8080", nil) } func restHandler(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { fmt.Fprintln(w, "GET called!!") } else if r.Method == "POST" { fmt.Fprintln(w, "POST called!!") } else if r.Method == "PUT" { fmt.Fprintln(w, "PUT called!!") } else if r.Method == "DELETE" { fmt.Fprintln(w, "DELETE called!!") } } $ curl -X GET localhost:8080 GET called!! $ curl -X POST localhost:8080 POST called!! $ curl -X PUT localhost:8080 PUT called!! $ curl -X DELETE localhost:8080 DELETE called!! Todoリスト用APIの実装 上で終わり、あとは各自好きなようにハンドラ書いてね、っていうのは記事としていかがかと思うので、簡単なTodoAPIを立てて、Nuxt.jsで以前作ったフロントエンドから操作できるようにしてみました。 API仕様 エンドポイント リクエストメソッド 動作 /items GET 全てのタスクの取得 /items POST 新規アイテムの追加 /items DELETE 実行済み全タスクの削除 /items/:id DELETE 1つのタスクを削除 /items/:id/done PUT 1つのタスクを実行済みにする /items/:id/done DELETE 1つのタスクを実行済みに変更 テーブル TodoのItemsテーブルに以下の項目を用意。 id (主キー) name (Todoアイテムの名前) done (実行済みか否か) // 構造体 type Item struct { Id int `json:"id"` Name string `json:"name"` Done bool `json:"done"` } // テーブル func initDB(db *sql.DB) error { const sql = ` CREATE TABLE IF NOT EXISTS items ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, done BOOLEAN NOT NULL DEFAULT 0 );` _, err := db.Exec(sql) return err } リクエストハンドラ 2つのルート/items、/items/(/items/:idと/items/:id/done)のそれぞれにハンドラ関数を設定しました。各ハンドラの中でルートパラメタとリクエストメソッドで場合わけし、データベース操作を行います。 func main() { // (略)データベース初期化 // リクエストハンドラの追加 http.HandleFunc("/items", itemsHandler) // `/items`の処理() http.HandleFunc("/items/", itemsIdHandler) // `/items/:id`と`/items/:id/done`の処理 err = http.ListenAndServe(":4000", nil) if err != nil { log.Fatal(err) } } /*** リクエストハンドラ ***/ func itemsHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": getAllItems(w, r) // 全てのitemの取得 case "POST": addNewItem(w, r) // 新しいitemの追加 case "DELETE": deleteDoneItems(w) // 実行済みitemの削除 default: http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) } } func itemsIdHandler(w http.ResponseWriter, r *http.Request) { // ルートパラメータの取得(例: `/items/1/done` -> ["items", "1", "done"]) params := getRouteParams(r) if len(params) < 2 || len(params) > 3 { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // itemのidをintで取得 id, err := strconv.Atoi(params[1]) if err != nil || id < 1 { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } if len(params) == 2 { updateItem(id, w, r) } else if params[2] == "done" { updateDone(id, w, r) } else { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) } } func updateItem(id int, w http.ResponseWriter, r *http.Request) { switch r.Method { case "DELETE": deleteOneItem(id, w) default: http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) } } func updateDone(id int, w http.ResponseWriter, r *http.Request) { switch r.Method { case "PUT": doneItem(id, w, r) case "DELETE": unDoneItem(id, w, r) default: http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) } } データベースの操作はこんな感じです。 // 全アイテムの取得 func getAllItems(w http.ResponseWriter, r *http.Request) { var items []Item rows, err := db.Query(`SELECT * FROM items;`) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } for rows.Next() { var item Item rows.Scan(&item.Id, &item.Name, &item.Done) items = append(items, item) } var buf bytes.Buffer enc := json.NewEncoder(&buf) if err := enc.Encode(items); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, buf.String()) } // 新しいアイテムを追加 func addNewItem(w http.ResponseWriter, r *http.Request) { var reqBody struct { Name string `json:"name"` } dec := json.NewDecoder(r.Body) err := dec.Decode(&reqBody) if err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } _, err = db.Exec(`INSERT INTO items (name, done) values (?, ?)`, reqBody.Name, false) if err != nil { log.Print(err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } w.WriteHeader(http.StatusCreated) } // 1つのアイテムを削除 func deleteOneItem(id int, w http.ResponseWriter) { _, err := db.Exec(`DELETE FROM items WHERE id=?`, id) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } w.WriteHeader(http.StatusOK) } // 全ての実行済みアイテムを削除 func deleteDoneItems(w http.ResponseWriter) { _, err := db.Exec(`DELETE FROM items WHERE done=true`) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } w.WriteHeader(http.StatusOK) } // アイテムを実行済みにする func doneItem(id int, w http.ResponseWriter, r *http.Request) { if id == -1 { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } _, err := db.Exec(`UPDATE items SET done=true where id=?`, id) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } w.WriteHeader(http.StatusCreated) } // アイテムを未実行にする func unDoneItem(id int, w http.ResponseWriter, r *http.Request) { if id == -1 { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } _, err := db.Exec(`UPDATE items SET done=false where id=?`, id) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } w.WriteHeader(http.StatusOK) } ちなみにルートパラメータの取得もいい関数が見当たらなかったのでstrings.Split などを使って自分で書きました。(イケてるかあまり自信ない。) func getRouteParams(r *http.Request) []string { splited := strings.Split(r.RequestURI, "/") var params []string for i := 0; i < len(splited); i++ { if len(splited[i]) != 0 { params = append(params, splited[i]) } } return params } 動作確認 curlで動作確認。 # タスクを5つ追加 $ curl -d '{"name":"item1"}' localhost:4000/items $ curl -d '{"name":"item2"}' localhost:4000/items $ curl -d '{"name":"item3"}' localhost:4000/items $ curl -d '{"name":"item4"}' localhost:4000/items $ curl -d '{"name":"item5"}' localhost:4000/items # 全タスクの取得 $ curl localhost:4000/items [{"id":1,"name":"item1","done":false},{"id":2,"name":"item2","done":false},{"id":3,"name":"item3","done":false},{"id":4,"name":"item4","done":false},{"id":5,"name":"item5","done":false}] # item2, 3, 4を実行済みにする $ curl -X PUT localhost:4000/items/2/done $ curl -X PUT localhost:4000/items/3/done $ curl -X PUT localhost:4000/items/4/done $ curl localhost:4000/items [{"id":1,"name":"item1","done":false},{"id":2,"name":"item2","done":true},{"id":3,"name":"item3","done":true},{"id":4,"name":"item4","done":true},{"id":5,"name":"item5","done":false}] # item3を未実行にする $ curl -X DELETE localhost:4000/items/3/done $ curl localhost:4000/items [{"id":1,"name":"item1","done":false},{"id":2,"name":"item2","done":true},{"id":3,"name":"item3","done":false},{"id":4,"name":"item4","done":true},{"id":5,"name":"item5","done":false}] # 実行済みを全て削除する $ curl -X DELETE localhost:4000/items $ curl localhost:4000/items/ [{"id":1,"name":"item1","done":false},{"id":3,"name":"item3","done":false},{"id":5,"name":"item5","done":false}] # アイテムを1つ削除する $ curl -X DELETE localhost:4000/items/3 $ curl localhost:4000/items [{"id":1,"name":"item1","done":false},{"id":5,"name":"item5","done":false}] 基本的なCRUDはうまくいっているようです。 CORSの対応 curl叩けば行くのですが、ブラウザからaxios経由でやるとうまくいきません。 CORS(オリジン間リソース共有)を許可するために、レスポンスヘッダを追加します。 オリジン間アクセスを許可するAccess-Control-Allow-Originヘッダ。 axios(の中で呼ばれているXMLHttpRequest)がPOST/PUT/DELETEのリクエストする前にはOPTIONSリクエストが飛んでくる(Pre-Flightリクエスト)それに対するAccess-Control-Allow-HeadersとAccess-Control-Allow-Methodsヘッダ。 func itemsHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000") // localhost:3000からのオリジン間アクセスを許可する switch r.Method { case "GET": getAllItems(w, r) // 全てのitemの取得 case "POST": addNewItem(w, r) // 新しいitemの追加 case "DELETE": deleteDoneItems(w) // 実行済みitemの削除 case "OPTIONS": w.Header().Set("Access-Control-Allow-Headers", "Content-Type") // Content-Typeヘッダの使用を許可する w.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS") // pre-flightリクエストに対応する default: http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) } } func itemsIdHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000") // localhost:3000からのオリジン間アクセスを許可する // ルートパラメータの取得(例: `/items/1/done` -> ["items", "1", "done"]) params := getRouteParams(r) if len(params) < 2 || len(params) > 3 { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // itemのidをintで取得 id, err := strconv.Atoi(params[1]) if err != nil || id < 1 { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } if len(params) == 2 { updateItem(id, w, r) } else if params[2] == "done" { updateDone(id, w, r) } else { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) } } func updateItem(id int, w http.ResponseWriter, r *http.Request) { switch r.Method { case "DELETE": deleteOneItem(id, w) case "OPTIONS": w.Header().Set("Access-Control-Allow-Headers", "Content-Type") // Content-Typeヘッダの使用を許可する w.Header().Set("Access-Control-Allow-Methods", "DELETE, OPTIONS") // pre-flightリクエストに対応する default: http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) } } func updateDone(id int, w http.ResponseWriter, r *http.Request) { switch r.Method { case "PUT": doneItem(id, w, r) case "DELETE": unDoneItem(id, w, r) case "OPTIONS": w.Header().Set("Access-Control-Allow-Headers", "Content-Type") // Content-Typeヘッダの使用を許可する w.Header().Set("Access-Control-Allow-Methods", "PUT, DELETE, OPTIONS") // pre-flightリクエストに対応する default: http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) } } できました。 まとめ Goのnet/httpパッケージのみで簡単なREST APIを立ててみました。フルスクラッチでも割と簡単にかけてしまうので良いですね。 今、42Tokyoの課題でC++のフルスクラッチWebサーバも開発しているのですが手軽さでは比較になりませんね。標準パッケージでこんなに簡単に書けて素晴らしい。(net/httpの再実装をしたらすごく勉強になりそう) 完全なるコードはこちら。改善点などあればコメントいただけますと幸いです。 API:https://github.com/dai65527/go_simpleREST フロントエンド:https://github.com/dai65527/tstodo-client
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む