- 投稿日:2019-06-22T20:52:44+09:00
Go言語クイズ: ややこしいインターフェイス
ここに2つのファイルからなるGoのプログラムがあります。
文法的には問題はなくビルドできるコードですが、実行すると何が起こるでしょうか。
Goのバージョンとともに答えてください。main.gopackage main import ( "./sub" "fmt" ) type T struct { sub.T } func (T) f() { fmt.Println("main.T.f()") } func main() { s := sub.New(T{}) s.F() }sub/sub.gopackage sub type I interface { f() } type T struct { I } func New(i I) T { return T{i} } func (t T) F() { t.f() }答えはまた後日。
- 投稿日:2019-06-22T18:49:11+09:00
【Vue.js】zip ファイルの送受信【Go】
概要
クライアントの Vue.js とサーバーの Go との間で zip ファイルをやりとりします。
- クライアント:FormData に zip ファイルを入れてポストする
- サーバー :受け取った zip ファイルをそのまま返す
- クライアント:返ってきたデータをダウンロードする
環境
$ vue --version 3.8.4 $ go version go version go1.11.2 windows/amd64クライアント
プロジェクトを作成します。
$ vue create client $ cd client $ npm install axiosApp.vue を変更します。
App.vue<template> <div id="app"> <input @change="select" type="file" accept="application/zip"><br/> <button @click="upload">Upload</button> </div> </template> <script> import axios from 'axios' export default { name: 'app', data () { return { file: null } }, methods: { select(e) { this.file = e.target.files[0] }, upload() { const url = 'http://localhost:3000' let data = new FormData() data.append('zip', this.file) const config = { headers: { 'Content-Type': 'application/multipart/form-data' }, responseType: 'arraybuffer' } axios.post(url, data, config).then(res => { this.download(res) }) }, download(res) { const name = res.headers['content-disposition'].split('=')[1] const type = res.headers['content-type'] const blob = new Blob([res.data], { type: type }) const link = document.createElement("a") link.href = window.URL.createObjectURL(blob) link.download = name link.click() } } } </script>実行
$ npm run serveクライアント補足説明
今回は zip ファイルのみを扱うので input タグの accept で zip に制限しています。
<input @change="select" type="file" accept="application/zip">ファイルを送信するので Content-Type に application/multipart/form-data を設定し、
ダウンロード後に zip が解凍できるように responseType: 'arraybuffer' を設定しています1。const config = { headers: { 'Content-Type': 'application/multipart/form-data' }, responseType: 'arraybuffer' }サーバー
プロジェクトを作成します
$ mkdir server $ cd servermain.go を作成します
main.gopackage main import ( "io/ioutil" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { //data.append('zip', this.file) の "zip" file, header, _ := r.FormFile("zip") defer file.Close() bytes, _ := ioutil.ReadAll(file) //確認用に main.go と同じディレクトリに保存する ioutil.WriteFile(header.Filename, bytes, 077) w.Header().Set("Access-Control-Allow-Origin", "http://localhost:8080") w.Header().Set("Access-Control-Expose-Headers", "Content-Disposition") w.Header().Set("Content-Disposition", "attachment;filename="+header.Filename) w.Header().Set("Content-Type", "application/zip") w.Write(bytes) }) http.ListenAndServe(":3000", nil) }実行
$ go run main.go
- 投稿日:2019-06-22T17:26:46+09:00
Go言語 runtime.Callerを使ってメッセージやerrorにソースファイル名、行番号を含める
概要
プログラムが大きくなるとメッセージやエラーがどのソースファイルのどの行で出しているか調べるのが大変になります。そこれ
runtime.Caller()
を使ってソースファイル名や行番号を取得してメッセージに含めるようにします。環境
- Windows10
- go version go1.12.5 windows/amd64
- runtime.Callerドキュメント
テスト
package main import ( "fmt" "path/filepath" "runtime" ) func main() { _, file, line, ok := runtime.Caller(0) if ok { fname := filepath.Base(file) fmt.Printf("file: %s, line: %d\n", fname, line) } }ソースのフルパスではなくソースファイル名だけ表示するようにしています。
実行go run main.go file: main.go, line: 10確かに、
runtime.Caller(0)
を実行したソース名と行番号が表示されました。
次にerror
に含めてみます。package main import ( "fmt" "path/filepath" "runtime" ) func check(a, b string) error { if a == b { return nil } _, file, line, ok := runtime.Caller(0) if ok { fname := filepath.Base(file) return fmt.Errorf(`check error. "%s" != "%s"; file: %s, line: %d`, a, b, fname, line) } return fmt.Errorf(`check error. "%s" != "%s"`, a, b) } func func01() error { return check("a", "b") } func main() { err := func01() if err != nil { fmt.Println(err) } err = check("c", "d") if err != nil { fmt.Println(err) } }go run main.go check error. "a" != "b"; file: main.go, line: 13 check error. "c" != "d"; file: main.go, line: 13これではソースファイル名、行番号を含めた意味がありません。出来れば
check
を呼び出した行番号が欲しいのでruntime.Caller
の引数を1
に変えます。_, file, line, ok := runtime.Caller(1)go run main.go check error. "a" != "b"; file: main.go, line: 22 check error. "c" != "d"; file: main.go, line: 30希望通りに
check
を呼び出した行番号が表示されました。runtime.Caller
の引数の値によって関数呼び出しの階層を戻ることになります。runtime.Caller
の使い方が分かったところでもう少し汎用性を持たせた関数にします。さらに、check
を呼び出した関数名も追加します。package main import ( "fmt" "path/filepath" "runtime" ) func makeError(msg string, skip int) error { pc, file, line, ok := runtime.Caller(skip) if ok { fname := filepath.Base(file) return fmt.Errorf(`%s; file: %s, line: %d, func: %s`, msg, fname, line, runtime.FuncForPC(pc).Name()) } return fmt.Errorf("%s", msg) } func check(a, b string) error { if a == b { return nil } msg := fmt.Sprintf(`check error. "%s" != "%s"`, a, b) return makeError(msg, 2) } func func01() error { return check("a", "b") } func main() { err := func01() if err != nil { fmt.Println(err) } err = check("c", "d") if err != nil { fmt.Println(err) } }go run main.go check error. "a" != "b"; file: main.go, line: 27, func: main.func01 check error. "c" != "d"; file: main.go, line: 35, func: main.mainスクリプト言語のように便利です。
- 投稿日:2019-06-22T11:54:06+09:00
Goで超簡単API
概要
Goを使用してCRUDを行える簡単なAPIを作成します。(DBは使用しません。)
前提条件・設定
- Goの開発環境が整っている事を前提としています。
- ルートを設定する為にgorilla/muxを使用するのでインストールを行います。
go get -u github.com/gorilla/mux手順
- 開発準備
- ルート(エンドポイント)の設定
- モデルの作成
- モックデータの作成
- CRUDの設定
- postmanを使用して動作確認
開発準備
API用のディレクトリを作成し、
main.go
ファイルの作成を行います。$ mkdir goApi $ cd goApi $ touch main.goルート(エンドポイント)の設定
先ほどインストールした
gorilla/mux
を使用して以下のようにルート(エンドポイント)を設定します。package main import ( "github.com/gorilla/mux" "log" ) func main() { // ルーターのイニシャライズ r := mux.NewRouter() // ルート(エンドポイント) r.HandleFunc("/api/books", getBooks).Methods("GET") r.HandleFunc("/api/books/{id}", getBook).Methods("GET") r.HandleFunc("/api/books", createBook).Methods("POST") r.HandleFunc("/api/books/{id}", updateBook).Methods("PUT") r.HandleFunc("/api/books/{id}", deleteBook).Methods("DELETE") log.Fatal(http.ListenAndServe(":8000", r)) }モデルの作成
データの型となるモデル(struct)を
func main()
の上に作成します。
title
とauthor
の情報を持つBook
とfirstname
とlastname
を持つAuthor
を作成します。package main import ( /*省略*/ ) type Book struct { ID string `json:"id"` Title string `json:"title"` Author *Author `json:"author"` } type Author struct { FirstName string `json:"firstname"` LastName string `json:"lastname"` } func main() { /*省略*/ }モックデータの作成
データの型となるモデルをもとにAPIの動作確認用のモックデータを作成します。
package main import ( /*省略*/ ) type Book struct { /*省略*/ } type Author struct { /*省略*/ } // Bookのデータを保持するスライスの作成 var books []Book func main() { // ルーターのイニシャライズ /*省略*/ // モックデータの作成 books = append(books, Book{ID: "1", Title: "Book one", Author: &Author{FirstName: "Philip", LastName: "Williams"}}) books = append(books, Book{ID: "2", Title: "Book Two", Author: &Author{FirstName: "John", LastName: "Johnson"}}) // ルート(エンドポイント) /*省略*/ }CRUDの設定
以下の関数を設定していきます。
getBooks()
: 全ての本を取得する。getBook()
: 特定の本を取得する。createBook()
: 新しい本を作成する。updateBook()
: 特定の本の情報を更新する。deleteBook()
: 特定の本を削除する。// Get All Books func getBooks(w http.ResponseWriter, r *http.Request) { } // Get Single Book func getBook(w http.ResponseWriter, r *http.Request) { } // Create a Book func createBook(w http.ResponseWriter, r *http.Request) { } // Update a Book func updateBook(w http.ResponseWriter, r *http.Request) { } // Delete a Book func deleteBook(w http.ResponseWriter, r *http.Request) {\ }packageのimport
関数を完成させる為に必要ないくつかの
package
をimport
します。
- jsonデータを作成、取得する為に
encoding/json
- ランダムなidを作成する為に
math/rand
- 作成したidを
string
型に変更する為にstrconv
- http通信を可能にする為に
net/http
package main import ( "encoding/json" "github.com/gorilla/mux" "log" "math/rand" "net/http" "strconv" ) /*省略*/関数の作成
ここでの詳細の説明は省略しますが、他言語でAPIを作成する際に用いるCRUDの関数の構造と特に大き違いはないかと思います。
package main import ( "encoding/json" "github.com/gorilla/mux" "log" "math/rand" "net/http" "strconv" ) // Book Struct (Model) type Book struct { /*省略*/ } type Author struct { /*省略*/ } // Init Books var as a slice Book struct var books []Book // Get All Books func getBooks(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(books) } // Get Single Book func getBook(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") params := mux.Vars(r) // Loop through books and find with id for _, item := range books { if item.ID == params["id"] { json.NewEncoder(w).Encode(item) return } } json.NewEncoder(w).Encode(&Book{}) } // Create a Book func createBook(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var book Book _ = json.NewDecoder(r.Body).Decode(&book) book.ID = strconv.Itoa(rand.Intn(10000)) // Mock ID - not safe in production books = append(books, book) json.NewEncoder(w).Encode(book) } // Update a Book func updateBook(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") params := mux.Vars(r) for index, item := range books { if item.ID == params["id"] { books = append(books[:index], books[index+1:]...) var book Book _ = json.NewDecoder(r.Body).Decode(&book) book.ID = params["id"] books = append(books, book) json.NewEncoder(w).Encode(book) return } } json.NewEncoder(w).Encode(books) } // Delete a Book func deleteBook(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") params := mux.Vars(r) for index, item := range books { if item.ID == params["id"] { books = append(books[:index], books[index+1:]...) break } } json.NewEncoder(w).Encode(books) } func main() { /*省略*/ }最後の動作確認に移る前にここまでの
main.go
のコードは以下です。package main import ( "encoding/json" "github.com/gorilla/mux" "log" "math/rand" "net/http" "strconv" ) // Book Struct (Model) type Book struct { ID string `json:"id"` Title string `json:"title"` Author *Author `json:"author"` } type Author struct { FirstName string `json:"firstname"` LastName string `json:"lastname"` } // Init Books var as a slice Book struct var books []Book // Get All Books func getBooks(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(books) } // Get Single Book func getBook(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") params := mux.Vars(r) // Loop through books and find with id for _, item := range books { if item.ID == params["id"] { json.NewEncoder(w).Encode(item) return } } json.NewEncoder(w).Encode(&Book{}) } // Create a Book func createBook(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var book Book _ = json.NewDecoder(r.Body).Decode(&book) book.ID = strconv.Itoa(rand.Intn(10000)) // Mock ID - not safe in production books = append(books, book) json.NewEncoder(w).Encode(book) } // Update a Book func updateBook(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") params := mux.Vars(r) for index, item := range books { if item.ID == params["id"] { books = append(books[:index], books[index+1:]...) var book Book _ = json.NewDecoder(r.Body).Decode(&book) book.ID = params["id"] books = append(books, book) json.NewEncoder(w).Encode(book) return } } json.NewEncoder(w).Encode(books) } // Delete a Book func deleteBook(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") params := mux.Vars(r) for index, item := range books { if item.ID == params["id"] { books = append(books[:index], books[index+1:]...) break } } json.NewEncoder(w).Encode(books) } func main() { // Initiate Router r := mux.NewRouter() // Mock Data books = append(books, Book{ID: "1", Title: "Book one", Author: &Author{FirstName: "Philip", LastName: "Williams"}}) books = append(books, Book{ID: "2", Title: "Book Two", Author: &Author{FirstName: "John", LastName: "Johnson"}}) // Route Hnadlers / Endpoints r.HandleFunc("/api/books", getBooks).Methods("GET") r.HandleFunc("/api/books/{id}", getBook).Methods("GET") r.HandleFunc("/api/books", createBook).Methods("POST") r.HandleFunc("/api/books/{id}", updateBook).Methods("PUT") r.HandleFunc("/api/books/{id}", deleteBook).Methods("DELETE") log.Fatal(http.ListenAndServe(":8000", r)) }postmanを使用して動作確認
動作を確認する為に
main.go
を実行し、postmanを開きます。$ go run main.goGet(http://localhost:8000/api/books)
モックデータとして作成した2つのデータを取得する事ができました。
Get(http://localhost:8000/api/books/:id)
今度は
id
で指定して必要なデータのみを取得します。POST (http://localhost:8000/api/books)
次にデータを作成してみましょう。
Post
リクエストなのでURLを入力する右側の選択肢をGet
からPost
に変更し、json形式のデータを渡します。PUT(http://localhost:8000/api/books/:id)
次は一度登録したデータを
Update(更新)
してみましょう。
URL右の選択肢をPUT
に変更。DELETE(http://localhost:8000/api/books/:id)
最後に一度登録したデータを
Destroy(削除)
してみましょう。
URL右の選択肢をDELETE
に変更。