実用Go言語を読んで、どんどんメモをしていく記事です。
これからGoを勉強する人にも参考になるような記事を書けたら嬉しく思います。
本文の引用やソースコードを引用しつつ、メモを残していくようなスタンスの記事にします。
リンク
実用 Go言語 O'Reilly Japan
実用 Go言語 サポート用リポジトリ
Effective Go(日本語)
Hello World
最低限、文字を画面に出力するコードを書きます。
Goがインストールされていて、パスは通っているものとします。
空のフォルダを作成して、その中で次のコマンドを実行します。
Goのアプリケーションとライブラリは、それぞれ「モジュール」と呼ばれる塊になっています。
1つのフォルダが1つのモジュールになります。
次のコマンドを叩いて、プロジェクトの中心となるファイル(go.mod)を作ります。
フォルダはどこでも大丈夫です。
go mod init helloworld
最初に付与するのがパッケージ名です。
プログラムが大規模化して階層化するまでは出力ファイルに影響を与える程度ですので、気軽に命名してもOKです。
GitHubで外部に公開するときは、github.com/アカウント名/リポジトリ名を使います。
その中でファイルを作ります。
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello World")
}
コンパイルして実行してみます。
go build
./helloworld
"Hello World"
ビルドを実行しなくても、上記のソースコードであれば下記のコマンドでも実行可能です。
go run main.go
これで最初のGoのコードが実行出来ました。
HelloWorldを実行するときのまとめ
- 最初にパッケージ名が来ます。mainパッケージは特別なパッケージで、各実行形式には必ず含まれます。
- mainパッケージの中のmain()関数からプログラムが開始されます。
- 文字列をコンソールに出力するのは標準出力のfmtパッケージで機能が提供されているので、これを使います。
- fmtパッケージを使用するにはimport文で追加します。
- fmt.Println()で文字列を出力します。ダブルクォートで文字列を表現できます。
- fmt.Println()は関数のこと。()をつけると実行できます。
リテラル、変数
リテラルは数値や文字列などのデータをコード中で表現する文法のことです。
プリミティブなリテラルでよく使われるのは、整数、浮動小数点、文字列、nilが使われます。
nilは無効な参照先を表す値で、初期化されていない状態を表すのに利用されます。
/*
// literal
1 // 数字
1.23 // 浮動小数点数
"abc" // 文字列
`今日は
いい天気` // バッククォートも文字列(改行可能)
true // ブール値
nil // nil: 無効な参照先を表す値
// literal
*/
リテラルをそのまま関数やメソッドに渡すこともありますが、変数に格納し、そこから関数などに渡すことが多いです。
変数宣言はvarキーワードの後ろに、名前、型を書いて宣言します。
// 整数型の変数
var num1 int = 123
// 右辺で型が決まるなら型名は不要
var num2 = 123
// 変数宣言と代入を同時に行う短縮記法
// ただし関数内部でのみ利用可能
num3 := 123
名前
プログラムを書くという事は名前をつける事です。
メモリ片やメモリの参照に名前と型をつけ、処理の塊に関数名として名前をつけ、データの塊(構造体)にも名前をつけ、それらがまとまったパッケージ名にも名前をつけます。
- package文のパッケージ名は全て小文字でつけます。
パッケージレベルの関数や変数、型の命名は次のようなルールに従います。
- CamelCaseあるいはcamelCaseとして命名します。_(アンダースコア) は付けません。
- 先頭を大文字にするとパッケージ外からも使えるパブリックな要素になり、小文字にすると内でのみ使えるプライベートな要素になります。
関数内部の変数、変数の引数につける名前のルールは以下の通りです。
- 関数ローカルな変数や関数の引数で、宣言場所と利用場所が近く、説明がなくても良いものは短く書きます。
- エラーを表す変数はerrで命名し、コンテキストはctxで命名します。
コメント
コメントは複数行書けるブロックコメントと、行コメントがあります。
構造体などの型や関数、メソッドの定義の前に空行なしで書かれたコメントは、その要素のドキュメントとして利用されます。
// 行コメント
/*
ブロックコメント
*/
このコードをGoDocというgoコードからリファレンスを作成するツールを使って生成したドキュメントでは次のようになります。
実用Go言語のサポートリポジトリを例に紹介すると次のような形でGodocを試すことが出来ます。
gh repo clone oreilly-japan/practical-go-programming
cd practical-go-programming
go mod init go-programming
godoc -http=:8080
これで http://localhost:8080/pkg/ にアクセスすることでGoDocの内容を確認出来ます。
このように、GoDocを使って、パッケージの中身などを確認できます。
型と変換
Goの文法では、リテラルの状態では型を持っていません。変数に代入されると型が決まります。
Goの数値型はビット数、符号の有無などの条件で多数あります。多くの場合はintとfloat64で済みます。型を指定しないで代入するとintになります。同じ数値でもGoの場合は暗黙的な型変換がなく、明示的に型変換をしなければ他の変数に代入出来ません。
var i int = 123
fmt.Println(i) //123
// 数値同志の変換は、かっこでくくり型を前置します
var f float64 = float64(i)
fmt.Println(f) //123
// 64ビットOSで64ビットのintと、int64も明示的な変換が必要です
var i64 int64 = int64(i)
fmt.Println(i64) //123
// boolへの変換は比較演算子を使います
var b bool = i != 0
fmt.Println(b) //true
文字列との変換ではstrconvパッケージが必要です。
// 文字列との変換はstrconvパッケージを利用
in := 12345
fmt.Println(in) //12345
// strconvの数値入力はint64, uint64, float64なので
// それ以外の変数を使う時は型変換が必要
s := strconv.FormatInt(int64(in), 10)
fmt.Println(s) //"12345"
// Parse系はエラー変換失敗時にエラーを返す
// 成功時のerrはnil
f, err := strconv.ParseFloat("12.3456", 64)
fmt.Println(f) //12.3456
fmt.Println(err)//nil
ポインター
変数を作ってデータを格納すると、その変数はOSからもらったメモリのどこかに保存されます。
そのアドレスを扱う機能がポインターです。ポインターが指す実態を値と呼ぶ事もあります。
Goのポインターは次のように使います。
- 変数のポインター型には*を使います。
- 既存の変数のポインターを取り出すには&を利用します。
- ポインターから参照先の値を取り出す(デリファレンス)には*を利用します。
// ポインターの参照先となる普通の変数
var i int = 10
fmt.Println(i) //10
fmt.Println(&i) //0xc0000bd050
// ポインターを格納する変数(デフォルト値はnil)
var p *int
// pにはiのアドレスが入る
p = &i
// p経由でiの値を取得
fmt.Println(p) //0xc0000bd050
fmt.Println(*p) //10
ポインター型の初期値はnilで、nilの参照先を取り出そうとすると、プログラムがパニックを起こして終了します。
リテラルはそのままではポインターは取れません。一度変数に格納する事が必要です。
関数、スライス、マップなどは*を付けない場合もポインターと同等の動きをします。
これらは参照型と呼ばれます。
使う場合はそのままでリファレンスをせずに使いますが、コピーなどは参照のみがコピーされます。
ゼロ値
変数宣言したが、まだ値を設定していない変数はゼロ値で初期化されます。
構造体の各メンバー変数なども同様に初期化されます。
型 | ゼロ値 |
---|---|
数値 | 0 |
ブール型 | false |
文字列 | 空文字 |
ポインター,インターフェース | nil |
Goはコンパイラーが変数を自動で初期化するので、未初期化の変数が残ってバグる事を防ぐことが出来ます。
スライス
多くのプログラミング言語ではリスト、配列といったデータ構造があります。
同じ型のデータが複数並んでいるデータ構造です。
Goにも配列はありますが、スライスを多用します。
配列やスライスは複合型と呼ばれます。
型名 | 要素数 | リテラル | |
---|---|---|---|
配列 | 要素の方の前にブラケットを前置きしたもの | ブラケットの中に記述 | ブレース{}を後置するとインスタンスを作成 |
スライス | 要素の型の前にブラケットを前置きしたものが型名 | 不定 | ブレース{}を後置するとインスタンス作成 |
Goの配列は固定長です。プログラム中ではあらかじめデータの長さが分かっているケースは珍しいです。
外部から読み込むデータの長さなどはコンパイル時には長さがわからないので、固定長配列だけでプログラムを組むのは難しいでしょう。
// 要素数3の整数の配列
var nums [3]int = [3]int{1, 2, 3}
// 要素の値を取り出して表示
fmt.Println(nums[0])//1
// 長さを取得
fmt.Println(len(nums))//3
// use-array
fmt.Println(nums)//[1 2 3]
// use-slice
// スライスの変数宣言
var nums1 []int
fmt.Println(nums1) //[]
// 1, 2, 3の要素を持つスライスを作成して代入
nums2 := []int{1, 2, 3}
fmt.Println(nums2) //[1 2 3]
// あるいは既存の配列やスライスからも範囲アクセスでスライス作成
nums3 := nums[0:2] // 配列から
nums4 := nums2[1:3] // スライスから
fmt.Println(nums3) //[1 2]
fmt.Println(nums4) //[2 3]
// 配列と同じようにブラケットで要素取得可能
// 範囲外アクセスはパニック
fmt.Println(nums2[1]) // 2
// 要素の割り当ても可能
nums2[0] = 100
fmt.Println(nums2) //[100 2 3]
// 長さも取得可能
fmt.Println(len(nums2)) // 3
// スライスに要素を追加
// 再代入が必要
nums2 = append(nums2, 4)
fmt.Println(nums2) // [100 2 3 4]
スライスは、配列を参照する窓のようなデータ構造です。参照している配列、配列中の要素の長さ、容量の限界値という3つのデータを持った小さいデータです。
なお、ブレース{}を後置してインスタンスを初期化すると、固定長の配列もセットで作り、そこへの参照を持ったスライスが作成されます。
スライスも、配列と同様に参照している配列の任意の位置にあるデータを取得出来ます。
append()関数を使って、スライスの参照元の配列に値を追加していけます。
このappend()は背後の配列の容量がなくなると、自動的に2倍のサイズのメモリを確保してデータを載せ替え、新しいスライスを返します。
append()を使えば限界まで余裕があるかを気にせずに、自由に値を追加出来ます。
Goには存在しない「可変長配列」のように扱えます。
マップ
配列と並んで現代のプログラミング言語で組み込みのデータ構造として提供されることが多いのがマップ、辞書、ハッシュなどと呼ばれるデータ構造です。
Goでもマップを提供しています。マップも複合型です。
- 型はmap[キー]値の型です。
- mapの利用には初期化が必要です。ブレース{}をつけるか、make()関数で作成します。
- 値の割当と取得はブラケットを使用します。
// 数字がキーで値が文字列のマップ
// HTTPステータスを格納
hs := map[int]string{
200: "OK",
404: "Not Found",
}
fmt.Println(hs) //map[200:OK 404:Not Found]
// makeで作る
authors := make(map[string][]string)
fmt.Println(authors) //map[]
// ブラケットで要素アクセス
// 代入
authors["Go"] = []string{"Robert Griesemer", "Rob Pike", "Ken Thompson"}
fmt.Println(authors) //map[Go:[Robert Griesemer Rob Pike Ken Thompson]]
fmt.Println(authors["Go"]) //[Robert Griesemer Rob Pike Ken Thompson]
fmt.Println(authors["Go"][0]) //Robert Griesemer
// データ取得
status := hs[200]
fmt.Println(status) // "OK"
// 存在しない要素にアクセスするとゼロ値
fmt.Println(hs[0]) // ゼロ値 ""(空文字)
// あるかどうかの情報も一緒に取得
status, ok := hs[304]
fmt.Println(status) // ""(空文字)
fmt.Println(ok) //false
制御構文
Goがもつ制御構文はif,for,switchです。
if文
if文は条件によって実行文を切り替える、制御構文の一番基礎的なものです。
条件節はブール型でなければなりません。
他の言語にはfalsyという概念があり、ものによっては条件文中でtrueとして扱われたり、falseとして扱われるのですが、Goには暗黙の変換はありません。
statusCode := 1
if statusCode == 200 {
fmt.Println("no error")
} else if statusCode < 500 {
fmt.Println("client error") //ここが実行される
} else {
fmt.Println("server error")
}
通常if以外の使い方としては、変数宣言とセットの利用方法があります。この条件説の中で新しく変数に設定した変数は、このifブロック内でしか利用できません。また、コロンの後ろはブール節でなければなりません。
このマップの2つ目の返り値のように最初からブール値であればそのまま書けますし、書けない場合は、 err != nil などの評価式にします。
cache := map[int]int{
2: 4,
3: 9,
4: 16,
}
input := 2
//リソースの状態チェックを行いブロック内部でのみリソースを扱うif文
if result, ok := cache[input]; ok {
fmt.Println("cached value", result) //cached value 4
fmt.Println(ok) // true
}
for文
Goのfor文で一番使うのが、rangeを利用して、スライスやマップの全要素を取り出すループになるでしょう。
スライスはインデックスと値、マップの場合はキーと値がループ変数に格納されます。
変数を1つだけ書くとスライスの場合はインデックス、マップの場合はキーになります。
値だけを利用したい場合はブランク識別子(_)を使って読み飛ばします。
これを使って変数宣言すると、未定義エラーになりません。
読み飛ばす用途で使います。
ループの中ではbreakを使って強制終了したり、continueを使ってループの戦闘に戻る(ループを1回回る)こともできます。
scketches := []string{"Dead Parrot", "Killer joke", "Spanish Inquisition", "Spam"}
for i, s := range scketches {
fmt.Println(i, s)
}
/*
output
0 Dead Parrot
1 Killer joke
2 Spanish Inquisition
3 Spam
*/
// 1変数だけ書けばインデックスのみを受け取れる
for i := range scketches {
fmt.Println(i)
}
/*
output
0
1
2
3
*/
// ブランク識別子でインデックスを読み飛ばして値だけを使う
for _, s := range scketches {
fmt.Println(s)
}
/*
output
Dead Parrot
Killer joke
Spanish Inquisition
Spam
*/
for _, s := range scketches {
// もしスケッチ名がKから始まっていたら読み飛ばす
if strings.HasPrefix(s, "K") {
continue
}
// もしスケッチ名がnで終わっていたらループ終了
if strings.HasSuffix(s, "n") {
break
}
fmt.Println(s)
}
/*
output
Dead Parrot
*/
それ以外にはブール型の要素を1つだけ持つforループも書けます。これは条件がtrueの間は回り続けるループになります。
また、すべて省略すると、breakするまで回り続ける無限ループになります。
counter := 0
for counter < 10 {
fmt.Println(counter,"ブール値がtrueの間回り続けるループ")
counter += 1
}
/*
0 ブール値がtrueの間回り続けるループ
1 ブール値がtrueの間回り続けるループ
2 ブール値がtrueの間回り続けるループ
・・・
7 ブール値がtrueの間回り続けるループ
8 ブール値がtrueの間回り続けるループ
9 ブール値がtrueの間回り続けるループ
*/
end := time.Now().Add(time.Second)
for {
fmt.Println("breakやreturnで抜けないと終わらないループ")
if end.Before(time.Now()) {
break
}
}
/*
breakやreturnで抜けないと終わらないループ
breakやreturnで抜けないと終わらないループ
breakやreturnで抜けないと終わらないループ
breakやreturnで抜けないと終わらないループ
breakやreturnで抜けないと終わらないループ
breakやreturnで抜けないと終わらないループ
.
.
.
*/
ループ変数を使った、プリミティブなループも使えます。
こちらはセミコロンが2つあるループになります。
初期化節、ループ条件節、ループごとに行われる処理の節という3つの節で構成されます。
for i := 0; i < 10; i++ {
fmt.Println(i,"10回繰り返す")
}
/*
0 10回繰り返す
1 10回繰り返す
2 10回繰り返す
・・・
8 10回繰り返す
9 10回繰り返す
*/
switch文
if ... else if を羅列し書くのを簡単にしてくれるのがswitch文です。
式をswitchに書き、case節ごとに処理を書きます。
default節は他の条件にマッチしなかったときに実行されます。
他の言語と異なるのは、1つのcase節にマッチしてその節の処理を完了したあと、次の節へは処理が進まずswitch文を抜ける点です。
もし、次の節へと処理を継続したい場合は、fallthroughを置く必要があります。
s := "running"
switch s {
case "running":
fmt.Println("running")
fallthrough // "runningの時も実行中と表示される"
case "run":
fmt.Println("実行中")
case "stop":
fmt.Println("停止中")
default:
fmt.Println("その他")
}
/*
output
running
実行中
*/
関数
関数は処理をまとめて名前をつけたものです。すでにmain()関数が最初に登場しています。
これは言語のランタイムから呼ばれる関数です。
また、画面に出力する関数fmt.Println()もありました。
Goでソフトウェアを開発するということは、標準ライブラリや外部のライブラリを提供される関数や構造体などの方をimportしつつ、それを使う関数や型を作ることです。関数も複合型です。
関数は引数と返り値を持てます。終了時はreturnで関数を終了させます。返り値が必要な関数で返り値を返さなかったり、返り値の数や方が間違っているとエラーになります。
同じ型の返り値が続く場合は型を省略できます。後ろの方が前にもあるものとして扱われます。
//関数定義
func calc(x, y int) int {
return x + y
}
func main() {
r := calc(2,3)
fmt.Println(r) //5
}
返り値にも名前をつけられます。この場合はこの返り値用の変数が定義済となり、この変数に代入し、何もつけずにreturnすれば返り値を返しつつ関数を終了出来ます。
従来どおりのreturnも可能です。
返り値が複数個になったり、名前を付ける場合は返り値を()でくくります。
func calcAge(y int, m time.Month, d int) (age int, err error) {
b := time.Date(y, m, d, 0, 0, 0, 0, time.Local)
n := time.Now()
if b.After(n) {
err = errors.New("誕生日が未来です")
return
}
for {
b = time.Date(y+age+1, m, d, 0, 0, 0, 0, time.Local)
if b.After(n) {
return
}
age++
}
}
func main() {
//1980年11月14日生まれの人が何歳かを関数で計算して出力する
//第一引数に年、第二引数に月、第三引数に日を渡して計算している。
fmt.Println(calcAge(1980, time.November, 14)) //年齢(実行する日付によって変わってくる) <nil>
}
Goでは無名関数も作れます。何かのタイミングでコールバックされる処理はコールバックを受け取る場所で一緒に定義までも可能です。
名前付き関数はパッケージレベルでしか作れませんが、無名関数は関数の中でも使えます。
この関数を格納する変数の方はfunc(int,int)int(引数や返り値名は入れても省略してもOK)で、実装のブレース{}以下がない部分と同じ表現になります。コールバック関数として、無名関数をそのまま他の関数の引数にできますし、変数に入れることも出来ます。
// 名前付きで定義した関数以外に、無名関数として定義したものを渡せる
func doCalc(x, y int, f func(int, int) int) {
r := f(x,y)
fmt.Println(r)
}
func main() {
m := func(x, y int) int {
return x * y
}
// 名前付きで定義した関数以外に、無名関数として定義したものを渡せる
doCalc(10, 20, m) //200
}
func doCalc(x, y int, f func(int, int) int) {
r := f(x,y)
fmt.Println(r)
}
func main() {
// その場で定義した関数も渡せる
doCalc(10, 20, func(x, y int) int {
return x * y
}) //200
}
deferで後処理の関数の実行予約
プログラムの中では、とある処理の後に後片付けが必要なことがあります。
deferを使うと、その関数のブロックを抜けるタイミングで、その節が実行されます。
さまざまなプログラミング言語が「後処理を実行」するメカニズムを用意しています。
言語が変わっても、「後片付けは、準備と同じ場所で予約するのが一番確実だ」という原則は変わりません。
距離が離れると、整合が取れていない場合の見落としが増えます。
//ファイルを開く
f, err := os.Create("sample.txt")
if err != nil {
fmt.Println("err", err)
return
}
//この関数のスコープを抜けたら自動でファイルをクローズ
defer f.Close()
io.WriteString(f, "hello world")
deferを使って、f.Close()関数を予約することで、開いたファイルを閉じる処理をあらかじめ予約することが出来ます。
エラー処理
関数のサンプルで、ファイルを作成する関数os.Create()を呼んでいました。
この末尾の返り値をerrと呼ばれる変数に代入し、その次の行でerrがnilでない(何か値が入っている)かどうかを比較していました。
これがエラー処理の基本です。例外処理はありません。
Goとしての慣習は下記のとおりです。
- 失敗する可能性のある関数の末尾をerror型とする
- 成功時にはnilを、失敗時にはそこに詳細なエラーを割り当てて返す
- 関数を呼び出したあとは、 if err != nil というif文でerrをチェックし、追加の情報をラップしたり、そのままreturnで呼び出し元に返す
構造体
いくつかのデータをまとめて塊として扱えるようにしたものが構造体です。構造体も複合型です。
すでにtime.Now()関数の返り値としてtime.Time構造体のインスタンスをサンプルで使っています。
構造体は自分で作ることも出来ます。構造体の定義はtypeキーワード、名前、structキーワードを用いて作ります。
ブレース{}の中にフィールド(メンバーとなる変数)を列挙します。
構造体のフィールドも、大文字で始まる名前にしないと、外部パッケージから利用が出来ません。
構造体は他のプログラミングのクラスとは異なり、中にメソッドを定義することは出来ません。しかし、外にメソッドを定義できます。
この構造体は「型」であって、メモリ上に存在するものではありません。
メモリ上に「インスタンス」を作らなければデータを保存できません。複合リテラルを使って初期値を与えることも出来ます。
type Book struct {
Title string `json:"title"`
Author string `json:"author"`
Publisher string `json:"publisher"`
ReleasedAt time.Time `json:"release_at"`
ISBN string `json:"isbn"`
}
func main() {
// インスタンス作成(フィールドはすべてゼロ値に初期化)
var b Book
fmt.Println(b) //{ 0001-01-01 00:00:00 +0000 UTC }
//フィールドを初期化しながらインスタンス作成
b2 := Book{
Title: "本の名前",
}
fmt.Println(b2) //{本の名前 0001-01-01 00:00:00 +0000 UTC }
// フィールドを初期化しながらインスタンス作成
// 変数にはポインターを格納
b3 := &Book{
Title: "本の名前",
}
fmt.Println(b3) //&{本の名前 0001-01-01 00:00:00 +0000 UTC }
}
Goでは業務アプリケーションの実装では構造体をよく利用します。jsonタグを定義しておくと、この定義にしたがって、構造体のフィールドをJSONに書き出したり、JSONの情報をフィールドにマッピング出来ます。
encoding/jsonパッケージの関数を使って、book.jsonの中身を構造体にロードするコードが次のコードです。
type Book struct {
Title string `json:"title"`
Author string `json:"author"`
Publisher string `json:"publisher"`
ReleasedAt time.Time `json:"release_at"`
ISBN string `json:"isbn"`
}
func main() {
f, err := os.Open("book.json")
if err != nil {
log.Fatal("file open error: ", err)
}
d := json.NewDecoder(f)
var b Book
d.Decode(&b)
fmt.Println(b) //{本のタイトルです 著者です 出版社です 1979-10-12 09:00:00 +0900 JST 9780330508117}
}
{
"title": "本のタイトルです",
"author": "著者です",
"publisher": "出版社です",
"release_at": "1979-10-12T09:00:00+09:00",
"isbn": "9780330508117"
}
book.jsonを読み込んで、json.NewDcoderの引数として渡し、Bookの構造体に詰め込むことで、Go言語の中で解釈出来る値に変換することが出来ています。
ライブラリのインポート
他のパッケージで公開されている機能を利用するためには、import文を使います。
Visual Studio Codeを使っていれば、上記で触れてきたサンプルに書かれたコードを書いたときに利用可能な関数が選択肢として表示されて、選択するとimport文まで自動で入力されます。
標準ライブラリやすでにインストールしているライブラリであれば、importとタイプしなくても済みます。
Goでは中央集権的なライブラリのリポジトリなどはありません。github.comやgitlab.comなどのリポジトリや、独自ドメインで運用しているサーバなどを使ってライブラリの配布が行われます。
そのため、パッケージの識別子は、そのライブラリが取得可能なURLです。
プロジェクトのルートとなるフォルダで次のコマンドを実行します。
go get github.com/rs/zerolog
次のように表示されます。
go: downloading github.com/rs/zerolog v1.26.1
あとは、このライブラリのサンプルやドキュメントを読みながらコードを書いていきます。
ウェブアプリケーション
付録Aの最後は、ウェブアプリケーションの作成です。近年では業務システムもほぼウェブサービスとして実装します。
Goで作成したアプリは配布しやすいので、コマンドラインで使うツールを実装するのにも長けています。
次のコードは固定の文字列を返すだけのシンプルなウェブサービスです。
package main
import (
"fmt"
"io"
"net/http"
"os"
"github.com/rs/zerolog/log"
)
func main() {
// ①
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World")
log.Info().Msg("receive hello world request")
})
// ②
fmt.Println("Start listening at :8080")
// ③
err := http.ListenAndServe(":8080", nil)
if err != nil {
// ④
fmt.Fprintf(os.Stderr, "")
io.WriteString(os.Stderr, err.Error())
os.Exit(1)
}
}
- まず、URLごとの処理の登録をします。ここでは/helloというパスに文字列を返す処理を登録しています。
- ウェブサービスがどこで起動しているのかわからないとテストするのに不便なので、画面にテキストを表示しています。
- ここではhttpパッケージのListenAndServe()関数を使ってサーバーを起動します。
- もし、オープンしようとしているポートが占有されている場合は即座にエラーが帰ってきます。エラーを表示させるようにして、クローズさせます。
go runで実行するか、go buildで実行ファイルを作ってから実行してもOKです。
go run main.go
Start listening at :8080
http://localhost:8080/hello にアクセスすると、テキストが表示されます。
ここにJSON形式のリクエストやレスポンスを読み書きしたり、データベース機能や、情報の書き込みなどをすれば、実用的なアプリケーションとなります。
まとめ
以上で付録Aを読み終わりました。
何かの役に立てていれば嬉しいです。