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

【Golang】Go言語基礎 構造体について②(埋め込み)

今回は【Golang】Go言語の基礎 構造体について①の続編です。

Goにはクラスという概念がありません。したがって継承という概念もないです。しかし、その代わりに構造体の埋め込み(Embedded)というものがあります。

早速書いてみる

package main

import "fmt"

type Vertex struct {
    //小文字だと他のパッケージからはアクセスできなくなる。
    x, y int
}

func (v Vertex) Area() int {
    return v.x * v.y
}

func (v *Vertex) Scale(i int) {
    v.x = v.x * i
    v.y = v.y * i
}

//構造体VertexをVertex3Dに埋め込む
type Vertex3D struct {
    Vertex
    z int
}

func (v Vertex3D) Area3D() int {
    return v.x * v.y * v.z
}

func (v *Vertex3D) Scale3D(i int) {
    v.x = v.x * i
    v.y = v.y * i
    v.z = v.z * i
}

//コンストラクタ
func New(x, y int) *Vertex{
    return &Vertex{x, y}
}

func main() {
    v := New(3, 4)
    v.Scale(10)
    fmt.Println(v.Area()) //=> 1200



}

↑のように構造体を埋め込むことができます。Vertex3Dの構造体では新しくzを追加しています。
埋め込んだ構造体(Vertex3D)では Vertexで定義したx, yを使うことができます。継承とほぼ同じですね。

ちなみにfunc New の部分はコンストラクタです。rubyでいうinitializeのような処理をしていると思ってもらえると
理解しやすいと思います!

(ここまでは上と同じなので省略

func New(x, y, z int) *Vertex3D {
    return &Vertex3D{Vertex{x, y}, z}
}

func main() {
    //新しく5を追加する。
    v := New(3, 4, 5)
    v.Scale3D(10)
    fmt.Println(v.Area())
    fmt.Println(v.Area3D()) => 60000
}

試しに新しく定義したzに5を代入してみると60000が返ってきました!

いつも最後まで読んでいただきありがとうございます

ご指摘やご質問などあればコメントいただけると嬉しいです!

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

net/httpメモ

net/httpメモ 

目次

net/httpの概要

サーバとしての機能を備えている必須パッケージ。
ハンドラは勿論、
リクエストからデータを取り出すためのリクエストを表す各種構造体を提供してくれていたり、レスポンスを返すためのインターフェースも実装している。

Request編

httpリクエスト、レスポンスの構造について

  • リクエストもレスポンスも構造は基本的に同一で、順に
    • リクエスト行、またはレスポンス行
    • 0行以上のヘッダ
    • 空行
    • 任意指定のメッセージボディ

で構成されている

  • curl --verbose https://www.kyoto-u.ac.jp/jaでリクエストヘッダを見ると

GET /ja HTTP/1.1
Host: www.kyoto-u.ac.jp
User-Agent: curl/7.55.1
Accept: /
(空行)

となっており確かに基本構造に則っている

Request構造体について

  • httpパッケージにはHTTPリクエストについての情報を保持するための、そしてそれらを解析するための構造体Requestが存在している

  • Requestの重要な構成要素は次のようなものがある

    • URL
      • リクエスト行のURLに関する情報を保持する構造体
    • Header(ヘッダ)
      • リクエストヘッダ内のキーとバリューを保持
      • type Header map[string][]stringで定義されたマップ
    • Body(ボディ)
      • リクエストボディ内のキーとバリューを保持
      • io.ReadCloserであり、ReadメソッドとCloseメソッドを実装している
    • Form、PostForm、MultipartForm
      • URLかボディ、またはその両方からのパラメータを保持する(ただし読み取るには専用のメソッドでこれらのフィールドを更新する必要がある)
      • mapである(ただしMultipartFormはmapを二つ要素に持つ構造体)

パラメータを受け取る様々なメソッドとそれらの特徴

既にRequest構造体の重要な構成要素としてForm,PostForm,MultipartFormという三つのフィールドを伝えた。
Form,PostFormはキーが文字列、バリューが文字列のスライスであるマップで、
後で分かることだが前者はURLパラメータとボディパラメータの両方を含み、後者は(名前から分かることだが)ボディパラメータのみを含むことになる。
MultipartFormはForm,PostFormと同様のマップと、それに加えてファイルを格納するためのマップも備えた構造体だ。
マルチパートという特殊なコンテンツタイプを持つボディパラメータにのみ対応している。

  • 以上三つのフィールドにパラメータを格納するには、それぞれ特定のメソッドが必要になってくる

  • またそれらとは別に、一つずつパラメータを取り出すのに特化したメソッドが二つ存在するのでそれにも触れていく

補足

  • ボディパラメータのコンテンツタイプ、具体的には、実際のHTMLフォームのenctypeで指定されているのは通常URLエンコードかマルチパートの二種類

ParseForm

  • func (r *Request) ParseForm() error
  • ParseForm は URL から生のクエリを解析し、r.Form を更新する

  • ボディにパラメータが存在すればボディも読み込み対象となり結果を r.PostForm と r.Form の両方に入れる。

  • r.Formにおいて、ボディのパラメータは URLパラメータよりもよりも優先される。
    つまり、(r.Formは文字列:文字列のスライス のマップなので)同じキー名のバリューがURL、ボディそれぞれに存在していたらそのキーに対応するバリューのスライスの先頭にくるのはボディパラメータの方ということ

  • このことはFormValueで効いてくる

  • でもキー名が同名のパラメータがGET、POSTで同時に渡される機会なんてあるの?無さそう

ParseMultipartForm

  • func (r *Request) ParseMultipartForm(maxMemory int64) error

  • 引数で指定したバイト数分だけマルチパートのボディパラメータを取得し、MultipartForm構造体の一つ目のマップに格納する

  • MultipartForm構造体の二番目のマップにはファイルを格納することになる

FormValue

  • func (r *Request) FormValue(key string) string

  • これまでのパラメータを取り出す例では、

    1. ParseForm等のメソッドを呼び出し
    2. Form等にパラメータを格納する
  • といった二段階の処理をこちらで行っていたが、
    FormValueメソッド、そして次に紹介するPostFormValueメソッドではその必要がない

  • FormValueは必要に応じてParseMultipartFormとParseFormを呼び出し、 Formからキーに対応したバリューを一つだけ返す

PostFormValue

  • func (r *Request) PostFormValue(key string) string

  • PostFormValueは必要に応じてParseMultipartFormとParseFormを呼び出し、 PostFormからキーに対応したバリューを一つだけ返す

ファイルをアップロードする

  • ボディパラメータのコンテンツタイプがURLエンコードかマルチパートの二種類であることは説明した

  • 一般的にはURLエンコードが多いが、特定の場合にマルチパートが使われる

  • その特定の場合とはファイルのアップロードを目的としたリクエストの場合である

  • ファイルのアップロードができるHTMLは次のようになっている

<html>
 <head>
 <meta http-equiv="Content-Type" content="text/html;
charset=utf-8" />
 <title>Go Web Programming</title>
 </head>
 <body>
 <form action="http://localhost:8080/process"
method="post" enctype="multipart/form-data">
 <input type="file" name="uploaded">
 <input type="submit">
 </form>
 </body>
</html>
  • http://localhost:8080/processにPOSTメソッドで(method="post")コンテンツタイプをマルチパートとして(enctype="multipart/form-data")ファイルをsubmitしている

  • ハンドラの方は次の通り

package main
import (
 "fmt"
 "io/ioutil"
 "net/http"
)
func process(w http.ResponseWriter, r *http.Request) {
 r.ParseMultipartForm(1024)
 fileHeader := r.MultipartForm.File["uploaded"][0]
 file, err := fileHeader.Open()
 if err == nil {
    data, err := ioutil.ReadAll(file)
    if err == nil {
        fmt.Fprintln(w, string(data))
        }
    }
}
func main() {
 server := http.Server{
 Addr: "127.0.0.1:8080",
  }
 http.HandleFunc("/process", process)
 server.ListenAndServe()
}
  • 何度も出ていることだがMultipartFormは構造体で、マルチコンテンツのパラメータを保持するマップとは別にファイルを保持するマップを要素に持っている

  • r.ParseMultipartForm(1024)
    fileHeader := r.MultipartForm.File["uploaded"][0]
    file, err := fileHeader.Open()
    メソッドParseMultipartFormを呼び出すと、MultipartFormフィールドのFileフィールドからFileHeaderを取得し、そのOpenメソッドを呼び出してファイルを取得している

  • data, err := ioutil.ReadAll(file)
    if err == nil {
    fmt.Fprintln(w, string(data))
    }
    サーバにファイルを送るとハンドラがそのファイルを受け取ってバイト配列に読み込み、それをブラウザ上に表示する

image.png

image.png

  • さて、MultipartFormをより具体的に見るとこんな構造をしている
type Form struct {
    Value map[string][]string
    File  map[string][]*FileHeader

type FileHeader struct {
    Filename string
    Header   textproto.MIMEHeader
    Size     int64 // Go 1.9
    // contains filtered or unexported fields
}
  • 試しにFileHeaderの値が見たいのでハンドラ関数を変更する
func process(w http.ResponseWriter, r *http.Request) {
    r.ParseMultipartForm(1024)
    fileHeader := r.MultipartForm.File["uploaded"][0]
    fmt.Fprintln(w, fileHeader)
}
  • アクセスするとこんな感じになる

image.png

  • 送られるファイルが一つの時は、FormValueメソッドのみたくFormFileという簡単なメソッドがあるのでそちらがおすすめ
func process(w http.ResponseWriter, r *http.Request) {
    file, _, err := r.FormFile("uploaded")
    if err == nil {
        data, err := ioutil.ReadAll(file)
            if err == nil {
            fmt.Fprintln(w, string(data))
            }
    }
}

Response編

http.ResponseWriterと各メソッド

  • ハンドラ関数は必ずfunc(w http.ResponseWriter, r *http.Request)という形をとる。これは今まで例で見てきたとおり

  • Responseを返すにはハンドラ関数に第一引数として渡るhttp.ResponseWriterに内容を書き込んでいることが分かる

  • ResponseWriterは名前の通り、Writerメソッドを実装し、io.Writer型である。これはfmt.Fprint()が使えることからも分かる

  • また、ResponseWriterはio.Writer型であると同時に、独自のメソッドを要素に持つインターフェースである。

type ResponseWriter interface {

    Header() Header
    // type Header map[string][]string

    Write([]byte) (int, error)

    WriteHeader(statusCode int)
}
  • ここで説明する ResponseWriter interfaceのメソッドは

    • Write([]byte) (int, os.Error)
    • Header() Header
    • WriteHeader(statusCode int) の三種
  • Write([]byte) (int, os.Error)

    • 既に述べたとおりio.Writerのメソッド。 ResponseWriter.Write(バイトスライス)の形でレスポンス内容を流し込むのにつかえる。 そのうえio.Writer型を実装できるのでその他便利関数も使えるようになる
    • 内容はレスポンスのボディに書き込まれる
  • Header() Header

    • こちらで変更可能なヘッダのマップを返す(ヘッダはキー:バリューのマップに相応しい形式)
    • 返値のマップ.set("キー名":"バリュー")とすることでヘッダに追加できる
  • WriteHeader(statusCode int)

    • ステータスコードを引数にとり、それをレスポンスのステータスコードへ書き込む

参考

Goプログラミング実践入門 ―標準ライブラリでゼロからWebアプリを作る―
gihyo.jp - 第4章 標準パッケージ―JSON,ファイル,HTTP,HTMLを扱う
公式doc

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

TinyGo + `VSCode or Vim (もしくはそれ以外の LSP 対応エディタ)` で gopls 連携する方法

TinyGo 0.15 がリリースされて、 gopls との連携が簡単になりました。
逆に言うと今までは 設定がそれなりに面倒 でした。

このページでは、 TinyGo 0.15 以降での gopls 連携の方法を VSCode および Vim (もしくはそれ以外の LSP 対応エディタ) に対して記載します。

注意)
以降、 wioterminal というターゲットを例として記載します。
適宜、使いたいターゲットに読み替えてください。

VSCode

まずは、 VSCode の Extensions > TinyGo 0.2.0 をインストールします。

Screenshot from 2020-09-23 21-28-47.png

次に tinygo のソースコード (*.go) を開いている状態で Command palette > TinyGo targets を実行します。

Screenshot from 2020-09-23 21-22-51.png

pick a target... と表示されるので wioterminal を選択します。

Screenshot from 2020-09-23 21-21-01.png

Reload を促されるので Reload します。

Screenshot from 2020-09-23 21-21-46.png

この時点で machine.LED の定義が表示されていれば gols 連携は OK です。

Screenshot from 2020-09-23 21-23-24.png

ターゲットを切り替えたい場合は、 Command palette > TinyGo targets を再度実行してください。

Vim (もしくはそれ以外の LSP 対応エディタ)

まずは tinygo-edit をインストールします。
以下から実行体を DL し PATH の通った場所に置いてください。

もしくは、以下のコマンドでもインストールすることが出来ます。

$ go get github.com/sago35/tinygo-edit

その後、以下をソースコードのあるディレクトリで実行します。
エディタに合わせて少し設定が必要です。

# Vim
$ tinygo-edit --target xiao --editor vim --wait

# gVim
$ tinygo-edit --target xiao --editor gvim

# VSCode (VSCode は上記の TinyGo extensions を使ったほうが良い)
$ tinygo-edit --target xiao --editor code

この時点で machine.LED の定義が表示されていれば gopls 連携は」 OK です。

Screenshot from 2020-09-23 21-27-16.png

なお、この方法で開いた vim から立ち上げた terminal では tinygo buildtinygo flash が出来ません。
環境変数 GOROOT が設定されているためです。

Screenshot from 2020-09-23 21-32-31.png

以下のようにして GOROOT の設定を削除すると tinygo build 等を実施出来るようになります。

# windows / cmd.exe の場合
$ set GOROOT=

# bash の場合
$ unset GOROOT

Screenshot from 2020-09-23 21-35-29.png

ターゲットを切り替えたい場合は、 tinygo-edit を再度実行してください。

Link

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

【GORM】セッションを維持して別処理を呼び出す

共通処理を呼び出してテーブル更新するAPIをGo言語で実装していたときに、共通処理含めてロールバックする方法を作りました。
GORMガイドでは、トランザクションの説明はあるものの、処理をまたいだロールバックについては記載されていないようなので、記事としてまとめてみました。

環境

Golang 1.14.4

例題

図書館のシステムに、
「利用者が本を借りたとき、利用者の所持数を加算し、書架テーブルの貸出中フラグを1に更新する」処理があったとする。
この処理をコードで表すと以下の通り。

func bookRent() error {
    // リクエスト情報を受け取る。今回は割愛して固定値で指定する。
    userID := 1
    bookID := 23

    // DB接続用の情報を設定する。    
    DBMS := mysql
    CONNECT := root:mysql@tcp(localhost:3306)/testDB
    db, err := gorm.Open(DBMS,CONNECT)
    if err != nil {
        return err.ERROR()
    }

    // セッションを開始する。
    tx := db.Begin()

    // 予期せぬエラー時のロールバックを行う。
    defer func() {
        if r := recover(); r != nil {
            tx.Rollback()
        }
    }

    // 利用者の貸し出し冊数に加算する。
    var user model.User
    if err := tx.Model(&user).Where(user_id = ?  AND delete_flg = 0, userID).Update(rental_count, gorm.Expr(rental_count + 1)).Error; err != nil {
        tx.Rollback()
        return err.ERROR
    }

    // 蔵書に対して、貸し出し中フラグを「1:貸し出し中」に更新する。
    var liblary model.Library
    if err := tx.Model(&liblary).Where(book_id = ? AND delete_flg = 0, bookID).Update(rental_flg, 1).Error; err != nil {
        tx.Rollback()
        return err.ERROR
    }

    // すべての更新処理が終わったら、コミットを行う。
    tx.Commit()

    return nil
}

この処理に対し「貸出中フラグを更新する処理を共通化し、共通化処理を呼び出す形で実装したい」という課題が出た。
この時、正常にロールバックが機能する実装方法を考える。

解決方法

共通処理の引数に、セッション情報を持たせる。

// rentalBoook は、対象となった本を一括で貸し出し中に更新する共通処理です。
func rentalBook(tx *gorm.DB, bookIDs ...int) error {
    // 蔵書に対して、貸し出し中フラグを「1:貸し出し中」に更新する。
    var liblary model.Library
    if err := tx.Model(&liblary).Where(book_id IN (?) AND delete_flg = 0, bookIDs).Update(rental_flg, 1).Error; err != nil {
        tx.Rollback()
        return err.ERROR
    }

    return nil
}

もともとの処理であるbookRentは、以下のように修正する。(変更箇所近辺のみ抜粋)

    // セッションを開始する。
    tx := db.Begin()

    // 予期せぬエラー時のロールバックを行う。
    defer func() {
        if r := recover(); r != nil {
            tx.Rollback()
            return err.ERROR()
        }
    }

    // 利用者の貸し出し冊数に加算する。
    var user model.User
    if err := tx.Model(&user).Where(user_id = ?  AND delete_flg = 0, userID).Update(rental_count, gorm.Expr(rental_count + 1)).Error; err != nil {
        tx.Rollback()
        return err.ERROR()
    }

    // 共通処理を呼び出して、貸し出しフラグを「1:貸し出し中」に更新する。
    bookErr := rentalBook(tx,[]int{bookID}...)
    if bookErr != nil {
        // 共通処理側でロールバックしているので、こちらではロールバックしない。
        return bookErr.ERROR()
    }

    // すべての更新処理が終わったら、コミットを行う。
    tx.Commit()

共通処理を呼び出した際のロールバックについては、呼び出し元の処理で行うか、共通処理側でやるかという問題があると思います。
共通処理で問題が発生し次第ロールバックしたほうが安全だと思うので、上記の例ではそう記載しています。

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