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()
っぽい書き方ですが、そのとおりです。 変数name
とprice
のポインタを渡し、値を書きこんでもらいます- エラーでなく(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 由来だと思うんだけどなぁ…
すごく使い方は違うので、その由来はまちがい?