Go言語
Go言語をご紹介します。
Go言語とは
C言語の歴史
Goは,2009年にGoogleにより発表されたオープンソースのプログラミング言語です。
C言語の開発者Ken Thompson,UTF-8の開発者Rob Pike,memcachedの開発者Brad Fitzpatrickといった名だたるエンジニアによって開発されています。
Goはシンプルな言語仕様であるため学習が比較的容易で,豊富な標準パッケージが同梱されているためすばやく目的を達成できます。
また,巨大なコードでも高速にコンパイルできるため大規模開発にも適しており, Windows,OS X,Linuxなどの環境に合わせた実行ファイルを生成するクロスコンパイルのしくみがあるため作成したプログラムを容易に配布できます。
並行処理のサポートも充実しており,ミドルウェアの開発などにも適しているとされています。
言語の特徴
Goは,CやC++などが使用されるシステムプログラミングの領域で,より効率良くプログラムを書くことを目的に作られました。 巨大なコードベースのプロダクトを,多人数の開発者によって開発・メンテナンスするような場面で,品質を保つための工夫が随所に施されています。
以降ではGoの基本的な特徴を紹介します。
言語をシンプルに保つ
Goは,ほかの言語が持つような機能の多くを削り,言語をシンプルに保っています。
こうした機能の排除は,コンパイルの高速化や,予期せぬミスを減らすことに役立っています。
RaspBerry PI 3B
Go言語のhttp パッケージによるWebサーバ作り
golangのHTTPサーバ
golangのHTTPサーバは、少量のコードで動くものを作ることができる。所謂、フレームワークみたいなものは不要である。
内部的には、net/httpパッケージのServerとHandlerで構成される。
Serverは名前の通り、Serveするもので、HTTPをどのネットワークソースで提供するかを定義する。
Handlerは、HTTPリクエストを実際にどう処理するかを抽象化したinterfaceで、以下のメソッドを提供する。
type Handler interface { ServeHTTP(w http.ResponseWriter, r *http.Request) }
しかし、これらは簡易に使う場合、見えなくなっている。どうしてそうなっているかを理解することで、幾つかのテクニックを知ることができる。
簡単なWebサーバ
少し、冗長であるが、簡単なWebサーバは、net/httpパッケージのServerとHandlerを使用して、以下のように記述できる。
ここで、http.Handlerは、「ServeHTTP」と言うメソッドを持つだけのインタフェースである。
つまり、HTTP リクエストを受けてレスポンスを返すことを責務とする。
type Handler interface { ServeHTTP(w ResponseWriter, r *Request) }
また、http.Handleとは、以下のように、表示する URL と、URL に対応する http.Handler を DefaultServeMux に登録する関数である。
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
DefaultServeMux とはデフォルトで http.ServeMux 型の構造体で URL に対応した http.Handler を実行するいわゆるルーターである。
また、DefaultServeMux はルーターであると同時に ServeHTTP 関数を持つ http.Handler である。
以下のWebサーバは、http://localhost:8080/indexで起動できる。
package main import ( "net/http" ) func main() { // DefaultServeMuxを生成する m := http.NewServeMux() // HelloHandlerを生成する h := new(HelloHandler) // http.Handleメソッドで、表示する URL と、URL に対応する http.Handler を DefaultServeMux に登録する m.Handle("/index", h) // http.Serverを生成する s := http.Server{ Addr: ":8080", Handler: m, } // http.Serverを起動する s.ListenAndServe() } // HelloHandler構造体の定義 type HelloHandler struct{} // HelloHandler構造体のServeHTTPメソッドの実装(これで、http.Handlerになれる) func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { w.Write([]byte("Hello world")) }
Go言語の http パッケージにある Handle とか Handler とか HandleFunc とか HandlerFunc とか
簡単なWebサーバ
http://localhost:8080/helloで動くWebサーバ
package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World") }) log.Fatal(http.ListenAndServe(":8080", nil)) }
http.Handlerと言うインタフェース定義
http.Handlerは、「ServeHTTP」と言うメソッドを持つだけのインタフェースである。
つまり、HTTP リクエストを受けてレスポンスを返すことを責務とする。
type Handler interface { ServeHTTP(w ResponseWriter, r *Request) }
http.Handleとは
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
http.Handle とは表示する URL と、URL に対応する http.Handler を DefaultServeMux に登録する関数である。
DefaultServeMux とはデフォルトで http.ServeMux 型の構造体で URL に対応した http.Handler を実行するいわゆるルーターである。
また DefaultServeMux はルーターであると同時に ServeHTTP 関数を持つ http.Handler である。
http.ListenAndServe の第二引数が nil の場合 DefaultServeMux が Handler として指定される。
分かりづらいのは、ここで http.ListenAndServe が Handler を受け取ったり gorilla/mux のようなルーターを受け取ったりしていたので混乱しやすい。
全てが http.Handler なんだと理解できてからはすっきりする。
以下の 1 と 2 と 3 はだいたい同じ意味の処理になります。
type AnyHandler struct {} func (a *AnyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {} anyHandler := &AnyHandler{} // 1 http.Handle("/any/", anyHandler) http.ListenAndServe(":8080", nil) // 2 mux := http.NewServeMux() mux.Handle("/any/", anyHandler) http.ListenAndServe(":8080", mux) // 3 http.ListenAndServe(":8080", http.HandleFunc(func(w http.ResponseWriter, r *http.Request){ if r.URL.Path == "/any/" { anyHandler.ServeHTTP(w, r) } })
http.HandlerFuncとは
func(ResponseWriter, *Request) の別名の型である。
ServeHTTP 関数を持つので、関数を定義して http.HandlerFunc にキャストするだけで構造体を宣言することなく http.Handler を用意することができる。
type HandlerFunc func(w ResponseWriter, r *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
http.HandleFunc とは
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }
URL と func(ResponseWriter, *Request) を渡して DefaultServeMux に登録する関数です。
内部で func(ResponseWriter, *Request) から http.HandlerFunc へのキャストが行われています。
httpパッケージ(Webサーバ関係)
https://golang.org/pkg/net/http/からtype ServeMux
func NewServerMux() *ServerMux
NewServeMux allocates and returns a new ServeMux.
func (mux *ServeMux) Handle(pattern string, handler Handler)
Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))
HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
Handler returns the handler to use for the given request, consulting r.Method, r.Host, and r.URL.Path. It always returns a non-nil handler. If the path is not in its canonical form, the handler will be an internally-generated handler that redirects to the canonical path. If the host contains a port, it is ignored when matching handlers.
The path and host are used unchanged for CONNECT requests.
Handler also returns the registered pattern that matches the request or, in the case of internally-generated redirects, the pattern that will match after following the redirect.
If there is no registered handler that applies to the request, Handler returns a “page not found” handler and an empty pattern.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
ServeHTTP dispatches the request to the handler whose pattern most closely matches the request URL.
WebSocketサーバを作ろう
簡単なWebサーバ
http://localhost:8080/helloで動くWebサーバ
package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World") }) log.Fatal(http.ListenAndServe(":8080", nil)) }
チャットサーバを作ろう
client.go
client型
client型は、room型のServerHTTP()メソッド内で生成される。
room型のServerHTTP()メソッドは、WebSocketのconnection処理を行う時にmain.goのmain()関数から呼び出される。
// clientはチャットを行っている1人のユーザーを表します。 type client struct { // socketはこのクライアントのためのWebSocketです。 socket *websocket.Conn // sendはメッセージが送られるチャネルです。 send chan *message // roomはこのクライアントが参加しているチャットルームです。 room *room // userDataはユーザーに関する情報を保持します //userData map[string]interface{} //userData string userName string }
client型ノメソッド
read() // 端末とのSocket通信処理(Read)を行う write() // 端末とのSocket通信処理(Write)を行う
read()メソッド
read()メソッドは、room型のServerHTTP()メソッド内から呼び出される。
read()メソッドは、room型のServerHTTP()メソッドの延長として永久ループをして、端末からのJSONデータを受信待ちする。
端末からのJSONデータは、「forward」キューに書き込む。
// 「room.ServeHTTP()」メソッドから呼ばれるSocket通信処理(Read) func (c *client) read() { // Socket通信処理(Read)を行う for { var msg *message if err := c.socket.ReadJSON(&msg); err == nil { // 受信した。データは「&msg」にあるんだ。 log.Println(APP2 + "read() 1.メッセージを受信 受信先id = ", c.userName + ", Message = " + msg.Message ) msg.When = time.Now() // 送信者による分類 switch msg.FromName { case "2001": msg.AvatarURL = "http://www.hiroict.com:" + PORT + "/papero_i.jpg" case "2002": msg.AvatarURL = "http://www.hiroict.com:" + PORT + "/IMG_5606m.jpg" default: msg.AvatarURL = "http://www.hiroict.com:" + PORT + "/hero-box-109.jpg" } // 受信メッセージをforwadチャネルに入れる c.room.forward <- msg } else { break } } c.socket.Close() }
write()メソッド
write()メソッドは、room型のServerHTTP()メソッド内からgoルーチンとして呼び出される。
write()メソッド内では、「send」キューのJSONメッセージを読み取り、当該端末にJSONメッセージを送信する。
// 「room.ServeHTTP()」メソッドから呼ばれるSocket通信処理(Write) func (c *client) write() { for msg := range c.send { if err := c.socket.WriteJSON(msg); err != nil { break } } c.socket.Close() }
room.go
room.goは、で紹介しているopkgでPaPeRo i にパッケージをインストールする事ができます。
package main import ( "log" "net/http" "github.com/gorilla/websocket" "github.com/oreilly-japan/go-programming-blueprints/chapter3/trace" "github.com/stretchr/objx" ) type room struct { // forwardは他のクライアントに転送するためのメッセージを保持するチャネルです。 forward chan *message // joinはチャットルームに参加しようとしているクライアントのためのチャネルです。 join chan *client // leaveはチャットルームから退室しようとしているクライアントのためのチャネルです leave chan *client // clientsには在室しているすべてのクライアントが保持されます。 clients map[*client]bool // tracerはチャットルーム上で行われた操作のログを受け取ります。 tracer trace.Tracer } // newRoomはすぐに利用できるチャットルームを生成して返します。 func newRoom() *room { return &room{ forward: make(chan *message), join: make(chan *client), leave: make(chan *client), clients: make(map[*client]bool), tracer: trace.Off(), } } func (r *room) run() { for { select { case client := <-r.join: // 参加 r.clients[client] = true r.tracer.Trace("新しいクライアントが参加しました") case client := <-r.leave: // 退室 delete(r.clients, client) close(client.send) r.tracer.Trace("クライアントが退室しました") case msg := <-r.forward: r.tracer.Trace("メッセージを受信しました: ", msg.Message) // すべてのクライアントにメッセージを転送 for client := range r.clients { select { case client.send <- msg: // メッセージを送信 r.tracer.Trace(" -- クライアントに送信されました") default: // 送信に失敗 delete(r.clients, client) close(client.send) r.tracer.Trace(" -- 送信に失敗しました。クライアントをクリーンアップします") } } } } } const ( socketBufferSize = 1024 messageBufferSize = 256 ) var upgrader = &websocket.Upgrader{ReadBufferSize: socketBufferSize, WriteBufferSize: socketBufferSize} func (r *room) ServeHTTP(w http.ResponseWriter, req *http.Request) { socket, err := upgrader.Upgrade(w, req, nil) if err != nil { log.Fatal("ServeHTTP:", err) return } authCookie, err := req.Cookie("auth") if err != nil { log.Fatal("クッキーの取得に失敗しました:", err) return } client := &client{ socket: socket, send: make(chan *message, messageBufferSize), room: r, userData: objx.MustFromBase64(authCookie.Value), } r.join <- client defer func() { r.leave <- client }() go client.write() client.read() }
チャットサーバの起動と停止
チャットサーバの起動
# initctl start yoshiko yoshiko start/running, process 10679
チャットサーバの停止
initctl stop yoshiko
チャットサーバの起動と停止の定義
/etc/init/yoshiko.conf
description "WebSocket Server" author "Toru Watanabe" start on runlevel [235] stop on runlevel [016] chdir /home/yoshiko/Work/go/src/github.com/toru.watanabe/chat exec ./chat respawn
お花畑1