- 投稿日:2020-09-23T23:50:42+09:00
【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が返ってきました!
いつも最後まで読んでいただきありがとうございます
ご指摘やご質問などあればコメントいただけると嬉しいです!
- 投稿日:2020-09-23T22:56:22+09:00
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
これまでのパラメータを取り出す例では、
- ParseForm等のメソッドを呼び出し
- 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))
}
サーバにファイルを送るとハンドラがそのファイルを受け取ってバイト配列に読み込み、それをブラウザ上に表示する
- さて、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) }
- アクセスするとこんな感じになる
- 送られるファイルが一つの時は、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
- 投稿日:2020-09-23T21:40:21+09:00
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
をインストールします。次に tinygo のソースコード (*.go) を開いている状態で
Command palette > TinyGo targets
を実行します。
pick a target...
と表示されるのでwioterminal
を選択します。Reload を促されるので Reload します。
この時点で
machine.LED
の定義が表示されていれば gols 連携は OK です。ターゲットを切り替えたい場合は、
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 です。なお、この方法で開いた vim から立ち上げた terminal では
tinygo build
やtinygo flash
が出来ません。
環境変数 GOROOT が設定されているためです。以下のようにして GOROOT の設定を削除すると
tinygo build
等を実施出来るようになります。# windows / cmd.exe の場合 $ set GOROOT= # bash の場合 $ unset GOROOTターゲットを切り替えたい場合は、
tinygo-edit
を再度実行してください。Link
- 投稿日:2020-09-23T09:32:50+09:00
【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()共通処理を呼び出した際のロールバックについては、呼び出し元の処理で行うか、共通処理側でやるかという問題があると思います。
共通処理で問題が発生し次第ロールバックしたほうが安全だと思うので、上記の例ではそう記載しています。