20190622のGoに関する記事は4件です。

Go言語クイズ: ややこしいインターフェイス

ここに2つのファイルからなるGoのプログラムがあります。
文法的には問題はなくビルドできるコードですが、実行すると何が起こるでしょうか。
Goのバージョンとともに答えてください。

main.go
package 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.go
package sub

type I interface {
        f()
}

type T struct {
        I
}

func New(i I) T {
        return T{i}
}

func (t T) F() {
        t.f()
}

答えはまた後日。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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 axios

App.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 server

main.go を作成します

main.go
package 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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語 runtime.Callerを使ってメッセージやerrorにソースファイル名、行番号を含める

概要

プログラムが大きくなるとメッセージやエラーがどのソースファイルのどの行で出しているか調べるのが大変になります。そこれ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

スクリプト言語のように便利です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goで超簡単API

概要

Goを使用してCRUDを行える簡単なAPIを作成します。(DBは使用しません。)

前提条件・設定

  1. Goの開発環境が整っている事を前提としています。
  2. ルートを設定する為にgorilla/muxを使用するのでインストールを行います。
go get -u github.com/gorilla/mux

手順

  1. 開発準備
  2. ルート(エンドポイント)の設定
  3. モデルの作成
  4. モックデータの作成
  5. CRUDの設定
  6. 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()の上に作成します。
titleauthorの情報を持つBookfirstnamelastnameを持つ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

関数を完成させる為に必要ないくつかのpackageimportします。

  • 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.go

Get(http://localhost:8000/api/books)

モックデータとして作成した2つのデータを取得する事ができました。

image.png

Get(http://localhost:8000/api/books/:id)

今度はidで指定して必要なデータのみを取得します。

image.png

POST (http://localhost:8000/api/books)

次にデータを作成してみましょう。
PostリクエストなのでURLを入力する右側の選択肢をGetからPostに変更し、json形式のデータを渡します。

image.png

PUT(http://localhost:8000/api/books/:id)

次は一度登録したデータをUpdate(更新)してみましょう。
URL右の選択肢をPUTに変更。

image.png

DELETE(http://localhost:8000/api/books/:id)

最後に一度登録したデータをDestroy(削除)してみましょう。
URL右の選択肢をDELETEに変更。

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む