Server Build Exercises on AWS (Amazon Web Services) 2022 edition

www.go ソースコード注解

www.goのソースコード解説です。 注意: ソースコードの各行左端に4桁の数字がふってあり、以下では適宜この行番号を使い解説します。 この行番号は、この解説のために追加された部分で、もちろんソースコードには本来ありません

次の3つのファイルコンパイルしてwww.goを生成しています。 3つのファイル合計で88行たらずです

  • main.go
  • form.go
  • mysql.go

main.go

0001 package main

パッケージmainのみです。 パッケージ名はファイルと対応関係はありません。 3個のファイルすべてがパッケージmainです。

0003 import (
0004 	"log"
0005 	"net/http"
0006 )

ライブラリのインポート命令

0008 var HTDOCS_DIR = "/var/www/html"

var 変数 = 初期値というスタイルです。 Go言語は静的な型言語なのですが、 こういった場合は自動的に推定値のstring型として HTDOCS_DIR を扱ってくれます

0010 func main() {

コマンドを作るにはmainが必要です。 これはC言語以来の伝統。

0011 	http.Handle("/", http.FileServer(http.Dir(HTDOCS_DIR)))
0012 	http.HandleFunc("/search", formSearchHandler)

まぁPythonとかも同じような感じなので、なんとなくわかってもらえそうですけど、

http.Handle(URLのパス, 関数)

と書けば、 URLのパスにマッチした場合に、その関数を呼び出します。 この関数の型や引数についてはform.go節で後述

  • 0012行目: /searchにマッチした場合はformSearchHandlerという関数を呼びます
  • 0011行目: /search以外のすべての場合、 http.FileServer(http.Dir(HTDOCS_DIR)という関数を呼び出すよう登録しました。 これはHTDOCS_DIR以下のファイルを送り返すというWWWサーバの基本動作をする関数です。
0015 	err := http.ListenAndServe(":8080", nil)

第1引数にはホスト:ポート番号を書きます。省略すると*の意味なので、 0.0.0.0:8080/tcp でHTTPセッションを待ち受けせよという意味です。 第2引数に関数ではなくnilを指定するとデフォルトの関数を使います

0016 	if err != nil {
0017 		log.Fatal(err)
0018 	}

エラー判定の定番の書き方です。 ここはサーバを起動出来なかった場合なので、 致命的なエラーということでlog.Fatal()を呼び、 エラーを表示して異常終了します。

form.go

0027 func formSearchHandler(w http.ResponseWriter, r *http.Request) {
0028 	// func (r *Request) FormValue(key string) string
0029 	key := r.FormValue("key")
0030 	if len(key) > 0 {
0031 		fmt.Fprintf(w, "%s\n", sql_query(key))
0032 	} else {
0033 		fmt.Fprintf(w, "no query\n")
0034 	}
0035 } 

formSearchHandler(w http.ResponseWriter, r *http.Request)は、 HTTPリクエストをさばく典型的な関数です。 変数wがクライアントへの返すデータを書きこむ場所 (C言語でいえば、STDOUTなどのデータストリームの指定を書くところ)で、 変数rがHTTPリクエストです。

r.FormValue("key")で、 FORM文からPOSTメソッドで送り込まれた変数keyに対する値を取り出せます。 文字列の長さlen(key)で取り出したkeyに値があるかないか調べ、 値が入っているなら、SQLのクエリを投げています。 クエリの返事をそのままFprintf文で変数wへ出力しています。 ようするに、たんにPrintf()などを使って出力すれば、 WWWクライアント(ブラウザ)に返事が返ります

mysql.go

0038 import (
           ... 省略 ...
0042 	_ "github.com/go-sql-driver/mysql"
0043 )

import 名前 パッケージという指定でパッケージを別名であつかえるのですが、 パッケージを読みこむが、初期化に使うだけでお明示的には使わないという場合、 _を使い、その値を捨てることができます。 これは blank import と呼ばれています

以下 mysql.go では典型的なGo言語の書き方が繰り返されます。 先に進む前に、まとめておきましょう

値, err := 関数
if err != nil {  // エラーがある(nilではない)
  エラー処理
}
defer 関数の終了処理(openする関数ならcloseを呼ぶ)
  • C言語と異なり、 関数は複数の値をreturnでき、たいてい、エラー判定とともに返してきます。 関数呼び出しの直後には、そのエラー判定を行う処理を書きます
  • その直後に、関数が終わるときに行うべき処理を書き、defer 宣言をつけておきます。 defer宣言をつけると、 関数のスコープを抜けるときに自動的に実行されます( ちなみに defer はスタックとして扱われるので、 複数の defer 宣言がある場合は逆順に実行されます)

0045-0051 行は典型的なGo言語の書き方です。

0045 func sql_query(key string) string {
0046 	db, err := sql.Open("mysql", "root:1qaz2wsx@(mysql:3306)/test")
0047 	if err != nil {
0048 		fmt.Println(err)
0049 		return err.Error()
0050 	}
0051 	defer db.Close()

sql.Open()関数だけ説明します。 sqlファミリーはsqlで抽象化され、どれでもこの書き方で利用可能です。

  • 第1引数で種類、いまの場合mysqlを指定
  • 第2引数で認証情報を書きます。認証情報は一つの文字列で次の形式です
ユーザ名:パスワード@(ホスト:ポート番号)/データベース名

接続できなければ0049行でreturnしてしまうから、 0054行目にたどりついている以上、接続はできているはずです。 でも、いちおう生死確認としてサーバに ping を打ってみましょう

0054 	err = db.Ping()
0055 	if err != nil {
0056 		fmt.Println(err)
0057 		return err.Error()
0058 	}

さて、いよいよクエリを投げます

0061 	query := fmt.Sprintf("SELECT name,price FROM shop_menu WHERE name='%s';", key)
0062 	rows, err := db.Query(query)
0063 	if err != nil {
0064 		fmt.Println(err)
0065 		return err.Error()
0066 	}

変数queryにクエリ文を作り(0061)、db.Query()関数でクエリを投げます(0062)。 返事があれば、それは変数rowsに入ります(0062)。 ちなみにrowsは**Rows構造体へのポインタ**です。

0068 	var name string
0069 	var price int
0070 	var r string
0071 	for rows.Next() {
0072 		err := rows.Scan(&name, &price)
0073 		if err != nil {
0074 			fmt.Println(err)
0075 		} else {
0076 			if price > 0 {
0077 				r = fmt.Sprintf("%s\t=>\t%d\n", name, price)
0078 			}
0079 		}
0080 	}
  • Rows構造体に紐づいているメソッド群には、 次の行を読み出すNext()や値を読み取るScan()などがあります。 これらを使い上のように書くのが定番の書き方です。
  • 読み出すものがなくなるとrows.Next()nilを返しforループは終了します。
  • rows.Scan(&name, &price)はC言語のscanf()っぽい書き方ですが、そのとおりです。 変数namepriceのポインタを渡し、値を書きこんでもらいます
  • エラーでなく(0073)、値priceが何かある(0076)なら、 返事を返す変数rの文字列を組み立てます(0077)
0082 	if len(r) == 0 {
0083 		r = fmt.Sprintf("sorry, not selling %s\n", key)
0084 	}

変数rの中身がない(文字列の長さが0)場合は、 「すいません、それ売っていません」というメッセージをくみたてておきます

0086 	return r

値を返します。 ここではC言語らしいスタイルになってしまっていますが、 本当はGo言語らしくr, errを返すほうがいいですね

おまけコラム

_ (blank)という名前の変数は特別な扱いをしています。 特別といってもも捨てる役担当ですけれも… _ (blank)がデフォルト変数名というのは Perl 由来だと思うんだけどなぁ… すごく使い方は違うので、その由来はまちがい?

Last updated on 12 Dec 2021
Published on 12 Dec 2021
Copyright (C) 2021-2022 Ken'ichi Fukamachi, All rights reserved. CC BY-NC-SA 4.0
We appreciate AWS Academy Japan for the offer of the learning environment.

Powered by Hugo. Theme by TechDoc. Designed by Thingsym.