0
0

More than 1 year has passed since last update.

GoでWebアプリケーションを作る(7)ーWebアプリケーション

Posted at

こんにちは。

今日は「Webアプリケーション」についてです。

これまで順番に作り上げてきたGyudon型をWebアプリケーションサーバとして起動する方法を確認してもらいます。
本章までを通して、Go言語の基本的な扱い方を学習した皆さんには、最も簡単な章かもしれません。

準備

複数のターミナルを用い、ハンズオンいただきます。
既存のコンテナに接続し利用するため、それぞれターミナルを起動し、以下コマンドを実行ください。
VSCodeをお使いの方はターミナルを追加で起動してください。

:# TERMINAL 1
$ docker exec -it go-tutor /bin/bash

:# TERMINAL 2
$ docker exec -it go-tutor /bin/bash

httpを起動する方法

Go言語の標準パッケージ net/httpを活用するだけで起動します。
特に細かい処理に拘らず、デフォルト動作でWebアプリケーションサーバをコーディングするのであれば、呼び出す側は以下の2行だけですみます。

func httphandler(w http.ResponseWriter, r *http.Request) {
	#...省略
}

func main() {
	http.HandleFunc("/", httphandler)   //どこのPathで、どんな処理をするか
	http.ListenAndServe("localhost:8080", nil) //どの接続元(ホスト名:ポート)で、サーバを起動するか
	if err != nil {
		panic(err)
	}
}

はじめてのGo言語Webアプリケーション起動

では、Webアプリケーションサーバの書き方も知ってもらったので、実際にコーディングしてもらいましょう。
理由は後ほど説明しますが、本講義では、net/httpパッケージではなく、講義用の下位互換httpパッケージ(zakohttp) を参照してもらいます。

:# TERMINAL 0
:# COPY /../6_struct/weakShop/shop/shop.go /../7_webapp/weakShop/shop/shop.go
:# COPY /../6_struct/weakShop/eaters.go /../7_webapp/weakShop/gyudon-httpd.go
:# WORKPATH /../7_webapp/weakShop/

$ cp /../6_struct/weakShop/shop/shop.go /../7_webapp/weakShop/shop/shop.go
$ cp /../6_struct/weakShop/eaters.go /../7_webapp/weakShop/gyudon-httpd.go
$ cd /../7_webapp/weakShop/
$ <お好きなエディタ> shop/shop.go
$ <お好きなエディタ> gyudon-httpd.go
$ go run gyudon-httpd.go


:# TERMINAL 1
$ curl http://localhost:8080/
:# 10秒程度待機する

  • /../7_webapp/weakShop/shop/shop.go
package shop

import (
	"fmt"
	"time"
	"../http"
)

type Gyudon struct {
	menu string
}

func NewGyudon() Gyudon {
	return Gyudon{
		menu: "NegitamaGyudon",
	}
}

func (self *Gyudon) Eat(w http.ResponseWriter, r *http.Request) { //引数をhttpdのセッション状態を受け取れるように追加
	if self.menu == "" {
		return
	}

	time.Sleep(time.Second * 10) //擬似食べてる時間
	fmt.Fprintf(w, "'%s'\n", self.menu) //食べた事を報告
	return
}

  • /../7_webapp/weakShop/gyudon-httpd.go
package main

import (
	"./shop"
	"./http"
)

func main() {
	myshop := shop.NewGyudon()
	http.HandleFunc("/", myshop.Eat)
	err := http.ListenAndServe("localhost:8080", nil)
	if err != nil {
		panic(err)
	}
}

結果

:# TERMINAL 0
:# WORKPATH /../7_webapp/weakShop/

$ <お好きなエディタ> shop/shop.go
$ <お好きなエディタ> gyudon-httpd.go
$ go run gyudon-httpd.go


:# TERMINAL 1
$ curl http://localhost:8080/
:# 10秒程度待機する
'NegitamaGyudon'

後処理

:# TERMINAL 0
:# Ctrl + C で、gyudon-httpd.goをKillしてください

Goroutineに触れる

Go言語の特徴でも述べた通り、Go言語では並行処理が簡単にかけます。
最後に、本講義で書いたWebアプリケーションサーバを用い、並行処理を体験してもらおうと思います。

突然ですが、座席が1つしかない牛丼屋に行かれたことはありますか?
執筆者は、大手牛丼チェーンの各社それなりに行きますが、今のところ座席が1つの店舗に巡り合ったことはありません。
平時の店舗で、1つの座席しか無いようでは、Aさんが食べ終わるまでBさんが牛丼を食べることができず、採算取れないからでしょうか。

ご存知の方も多いでしょうが、プログラムは、指定しない限り、処理を1つずつ順番に実行します。
そのため、1つの座席しか無い牛丼屋と同じ状況が起きます。
何も考えずに、HTTPサーバを開発すると、Aさんの画面が表示されるまで、Bさんの画面はずっと読み込み中でくるくる(待ち)になってしまいます。

めちゃくちゃ美味しい隠れた名店で、1席しか無いような状況であれば、執筆者は我慢できますが、
HTTPサーバで、他者の処理が終わらないと利用できないなんて、使えたものではありません。

1座席なWebアプリケーションサーバ体験

試しに、2つのリクエストを送ると、1つ目のリクエストが10秒、2つ目のリクエストが約20秒かかることがわかると思います。

:# TERMINAL 0
:# WORKPATH /../7_webapp/weakShop/

$ go run gyudon-httpd.go


:# TERMINAL 1
$ time curl http://localhost:8080/
:# 10秒程度待機する


:# TERMINAL 2
$ time curl http://localhost:8080/
:# 10秒程度待機する

結果

:# TERMINAL 0
:# WORKPATH /../7_webapp/weakShop/

$ go run gyudon-httpd.go
:# 何も出力されない場合、実行中です。続くハンズオンを実施ください


:# TERMINAL 1
$ time curl http://localhost:8080/
'NegitamaGyudon'
:# 10秒程度待機する

real    0m10.036s
user    0m0.014s
sys     0m0.013s

:# TERMINAL 2
$ time curl http://localhost:8080/
'NegitamaGyudon'
:# 10秒程度待機する

real    0m18.991s
user    0m0.004s
sys     0m0.010s

これは、2つ目のリクエストが、1つ目のリクエストが終わるまでの待ち時間+自身の実行時間となるためです。

並行プログラミング

AさんとBさんに、同時に牛丼を食べてもらう方法は、簡単です。
座席を2つ用意すれば良いのです。Cさん、Dさん.....と1000000人来店したら、来店と同時に座席を増やしてしまえば、誰も待たなくて良くなります。
これによりDさんは、AさんBさんCさんが食べ終わるのを待つことなく、食べ始めることができます。
(現実では難しいでしょうが、不思議なポッケで叶えてもらったと思ってください。)

プログラムでも、複数の処理をおおむね同時に実行する(厳密には、したように見せる。が正しい) 並行プログラミングがあります。
並行プログラミングなコードを自身で作成するためには、領域の管理を考えた数十行のコードを作成する必要があります。
牛丼屋の例で考えると、座席をどう確保すべきか、どういった要素が必要か。客が離れたら座席をどのように撤去するべきか。1つしかない厨房から牛丼をどのような形で提供するか。辺りです。

既に特徴で紹介していますが、Go言語では、Goroutine を用いることで、とても低コストに並行プログラミングを行えます。

🚀 Goroutineだけではなく、本講義では触れないChannelや、GCによって、低コストに並行プログラミングができます。

実際に、食べる処理(Eat関数) を、並行プログラミングにして、2つ目のリクエストが、1つ目のリクエストを待たなくても良いように、アップグレードしましょう

並行動作するWebアプリケーションサーバ体験

/../7_webapp/weakShop/http/zakohttp.go の、c.serve(self.ctx) が、Eat関数を呼び出しています。
ここをGoroutine化し、その先にあるEat関数を新しく誕生させた座席(Goroutine)で動作させるようにしましょう。

:# TERMINAL 0
:# WORKPATH /go/src/go_tutorial/7_webapp/weakShop/

$ <お好きなエディタ> http/zakohttp.go
$ go run gyudon-httpd.go
:# 何も出力されない場合、実行中です。続くハンズオンを実施ください


:# TERMINAL 1
$ time curl http://localhost:8080/
:# 10秒程度待機する

:# TERMINAL 2
$ time curl http://localhost:8080/
:# 10秒程度待機する
  • /../7_webapp/weakShop/http/zakohttp.go
c.serve(self.ctx)    //Line56 もともとの書かれ方
go c.serve(self.ctx) //Line56 変更後。go と、加筆する

結果

:# TERMINAL 0
:# WORKPATH /../7_webapp/weakShop/

$ <お好きなエディタ> http/zakohttp.go
$ go run gyudon-httpd.go
:# 何も出力されない場合実行中です続くハンズオンを実施ください


:# TERMINAL 1
$ docker exec -it go-tutor /bin/bash
$ time curl http://localhost:8080/
:# 10秒程度待機する
'NegitamaGyudon'


real    0m10.033s
user    0m0.019s
sys     0m0.009s

:# TERMINAL 2
$ docker exec -it go-tutor /bin/bash
$ time curl http://localhost:8080/
:# 10秒程度待機する
'NegitamaGyudon'


real    0m10.012s
user    0m0.019s
sys     0m0.009s

Tips: Goroutineは、注意して使いましょう

お手軽なgo func(){}()ですが、注意が必要です。
並行動作を簡単に行えますが、並行動作に対応した処理やデータの安全性は、プログラマ自身が考え、コード化しておく必要があります。
Goroutineが迷子になったり、データが壊れたり、リソースの奪い合いになったりと、危険なことが多くあります。
本番サービスとして、きちんと提供する場合は、並行プログラミングを学習してから利用することをお勧めします。

zakohttpパッケージについて

こちらは、Go言語標準パッケージ net/http (opens new window)の以下の処理を参考に作成しています。

実は、本講義を通して、Go標準パッケージの劣化版を、Go標準パッケージに近づけるコーディングをしてもらいました。
net/httpパッケージは、通信に関する処理や、同期処理、書き込み処理やハンドラの登録など、Goで触っておくと良さそうな表現が色々と存在します。
腕試しをされるのであれば、zakohttpの問題を考え、アップグレードし続けてみてください。

今日は以上です。
ありがとうございました。

次のPart 8は「テスト」です。
これまで作り上げてきたGyudon型にテストを追加してもらいます。

よろしくお願いいたします。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0