golang勉強するンGo(3) ~ リクエスト処理

Goのnet/httpパッケージを使って簡単な通信をテストしてみる。

ともかく動かす

前回作成したプロジェクト用ディレクトリにmain.goという名前でソースコードを作成する。

package main

import (
    "fmt"
    "net/http"
)

func main() {
    mux := http.NewServeMux()

    // A
    handler := new(MyHandler)

    mux.Handle("/A", handler)

    // B
    mux.HandleFunc("/B", MyFunc)

    // サーバ起動
    http.ListenAndServe(":8080", mux)
}

// MyHandler : パターンA(構造体を定義)
type MyHandler struct{}

func (handler *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "A")
}

// MyFunc : パターンB(関数を定義)
func MyFunc(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "B")
}

パターンAとパターンBの二つの方法でリクエストに対する応答を作成してみた。

このコードを保存して$ go run main.goで実行する。 http://localhost:8080/Ahttp://localhost:8080/Bにアクセスしてみると、それぞれABと表示される。

解説

関数mainの最後でhttp.ListenAndServeを呼び出している。 この関数により第一引数に指定した8080ポートでサーバが起動する。

第二引数に渡したmuxtype ServeMux)はHTTP request multiplexerとかいうもの。 リクエストに応じたハンドラーを呼び出すらしいが、よくわからん。

とにかくこのServeMuxURLと呼び出したいハンドラーを登録すればリクエストを処理できるみたいだ。

で、ServeMuxに関数を登録するのがHandleという関数らしい。

公式ドキュメントのソースを見てみると、

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()

    if pattern == "" {
        panic("http: invalid pattern")
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if _, exist := mux.m[pattern]; exist {
        panic("http: multiple registrations for " + pattern)
    }

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    mux.m[pattern] = muxEntry{h: handler, pattern: pattern}

    if pattern[0] != '/' {
        mux.hosts = true
    }
}

ごちゃごちゃと書いてあるが、色々とチェックをした後にpatternhandlerをマップに対応づけている。

patternはURLのようなものだとイメージできるが、このHandler型のhandlerは何者なのか?

こちらも公式ドキュメントをみると、

type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
}

どうやらインターフェースのようだ。 ServeHTTP(ResponseWriter, *Request)を実装しているものであればHandlerとして扱える。

そこで今回書いたmain.goに戻って見てみると、パターンAで使用しているstructのMyHandlerはたしかにServeHTTPを実装している。 これによりHandlerインターフェースを満たしたことになるのでハンドラーとして登録できる。

では、パターンBの方も同様に見ていこう。

パターンBの方ではHandleFuncという関数を呼び出している。 こちらも公式ドキュメントを見てみると、

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}

パターンの文字列とhandler func(ResponseWriter, *Request)を引数にとり、handlerに対してHandlerFuncを噛ませてから先ほど同様にHandleで登録するような処理の流れだ。

このHandlerFuncの部分は一見、handlerを引数として関数を呼び出しているように見えるが、

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

このように定義されているHandlerFunc型にキャストしている。

HandlerFunc型にはServeHTTPが定義されており、その処理としてはHandlerFunc(自分自身)を呼び出すような内容になっている。

ServeHTTPが定義されているということはHandlerインターフェースを満たしていることになる。 これにより、ただの関数がHandle関数でハンドラーとして登録できるようになっている。


簡単なリクエスト処理を書いてnet/httpパッケージの学習を行った。

今回は公式のServeMuxを見ていったが、これを他パッケージのmuxなどで置き換えることでさらに強力なルーティングなども行えるらしい。 これらも順次調べていきたい。

golang勉強するンGo(2) ~ 準備

ディレクトの用意

これから開発するブログプロジェクト用のディレクトリを用意する。

はじめにgithubの方であらかじめリポジトリを作成しておく。

今回はblog-golangという名前で空のリポジトリを作成した。

下記のコマンドで自分のgithubユーザ名のディレクトリをGOPATH配下に作成する。

$ mkdir $GOPATH/src/github.com/{githubユーザ名}

その後上記で作成したユーザディレクトリに移動して、

$ git clone {リポジトリのURL}

これで開発をするディレクトリは用意できた。

管理ツールの導入

今回は使用しないが、後々のために管理ツールを導入する。

golangではdepという依存管理ツールが流行っているらしいのでインストールする。

$ go get -u github.com/golang/dep/cmd/dep

先ほど用意した開発ディレクトリ上で以下のコマンドを実行する。

$ dep init

これによりGopkg.lockGopkg.tomlvendorが作成され、プロジェクトの初期化が行われた。

忘れずにgitにあげておく。

$ git add --all

$ git commit

$ git push origin master

の順番でgithubにプッシュできたはずだ。


今回までの作業でプログラムを各環境が一通り整った。

次回からは実際にコードを書きながらAPIサーバを作成していきたい。

golang勉強するンGo(1) ~ 環境構築

Go言語を勉強するためにお題としてブログを作成しながら学んでいこうと思う。

環境構築

Macなのでhomebrewで管理する。

$ brew install go

上記のコマンドでGoがインストールされたはずである。

確認のためバージョンを出力する。

$ go version
go version go1.11.4 darwin/amd64

無事にインストールされたGoの情報が表示された。

GOPATHの設定

続いてGOPATHを設定する。

~/.bash_profileに環境設定を記述すればよいので、自分の場合は以下のように記述した。

export WORK=$HOME/Documents/work

export GOPATH=$WORK/go
export PATH=$PATH:$GOPATH/bin

そして.bash_profileを再読み込みする。

$ source ~/.bash_profile

これによりDocuments/workがGoの作業スペースになる。

VSCodeの設定

最初にVSCodeにGOPATHを設定する。

VSCode上で + ,キーで設定が開けるので、そこからGoに関する設定を探す。

"go.gopath": "/Users/{ユーザ名}/Documents/work/go"を追加する。

これによりVSCodeがGOPATHを認識してくれるようになる。

VSCodeのエディタ拡張を導入する。

Microsoftから配布されているGo for Visual Studio CodeVSCodeでインストール。

f:id:kituman:20190113203101p:plain

依存しているパッケージがあるため順番にインストールする。

go get -u -v github.com/ramya-rao-a/go-outline
go get -u -v github.com/acroca/go-symbols
go get -u -v github.com/mdempsky/gocode
go get -u -v github.com/rogpeppe/godef
go get -u -v golang.org/x/tools/cmd/godoc
go get -u -v github.com/zmb3/gogetdoc
go get -u -v golang.org/x/lint/golint
go get -u -v github.com/fatih/gomodifytags
go get -u -v golang.org/x/tools/cmd/gorename
go get -u -v sourcegraph.com/sqs/goreturns
go get -u -v golang.org/x/tools/cmd/goimports
go get -u -v github.com/cweill/gotests/...
go get -u -v golang.org/x/tools/cmd/guru
go get -u -v github.com/josharian/impl
go get -u -v github.com/haya14busa/goplay/cmd/goplay
go get -u -v github.com/uudashr/gopkgs/cmd/gopkgs
go get -u -v github.com/davidrjenni/reftools/cmd/fillstruct
go get -u -v github.com/alecthomas/gometalinter
gometalinter --install

Goのパッケージをインストールしたら自身が設定したGOPATHにbinpkgsrcディレクトリが作成されたことが確認できる。

動作テスト

適当なプログラムファイルを作成して動作を確認する。

$GOPATH/src/helloディレクトリを作成し、そこに以下のようなhello.goというファイルを作成する。

package main

import "fmt"

func main() {
    fmt.Println("Hello golang !!")
}

動かしてみる。

$ go run hello.go 
Hello golang !!

動いた。環境構築終わり。

Dockerを触ってみたい(2)

前回 → Dockerを触ってみたい(1) - kituman備忘録 の続き。

設定ファイルのクローン

以下のコマンドでgitから環境作成に必要なファイルをクローンしてくる。

$ git clone git@github.com:koni/docker-php-nginx-mysql-memcached.git

ちなみMacだと最初からgitがインストールされているので便利。

補足として、

クローンする際にディレクトリ名を変更したい場合は、

$ git clone [リポジトリ] [好きなディレクトリ名]

のようにするとよい。

すると以下のエラーが...

git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

ググった結果どうやら公開鍵の登録が必要らしい。

GitHubでssh接続する手順~公開鍵・秘密鍵の生成から~ - Qiita

そういえば新しく買ったMacでgitを使うのは初めてだったので上記リンクに従い登録する。

まずは秘密鍵・公開鍵のペアが存在するか確認する。

$ ls ~/.ssh/
id_rsa      id_rsa.pub  known_hosts

すでに鍵は作成済みだったのでGithubに登録!

しようとしたがGithubにログインできない...

少し前にパスワードを強化しろと通知が来て、変更していたのを忘れていた。

何度も思い当たるパスワードをチャレンジしてようやく

...

There have been several failed attempts to sign
in from this account or IP address. Please wait
a while and try again later.

はぁ...

一度こうなるとしばらくアクセスできなくなるので気をつけよう!

というわけでパスワードをリセットしてからリトライ。

$ git clone git@github.com:koni/docker-php-nginx-mysql-memcached.git
$ ls
docker-php-nginx-mysql-memcached

やったぜ。

ビルドと起動

以下のコマンドでクローンしたディレクトリに入り、docker-compose.ymlが存在することを確認。

$ cd docker-php-nginx-mysql-memcached/docker
$ ls
docker-compose.yml  nginx           php-fpm56       php-fpm71       php-fpm72       php-fpm73

ありますねぇ!

docker-composeでビルド。

$ docker-compose up

初回起動時はダウンロードやビルドが行われるため結構時間がかかる。

ちなみに、オプションとして-dをつけることでバックグラウンドで起動してくれる。

コンテナの起動状態を確認するには、

$ docker-compose ps
       Name                     Command               State                     Ports                  
-------------------------------------------------------------------------------------------------------
docker_data_1        sh                               Exit 0                                           
docker_memcached_1   docker-entrypoint.sh memcached   Up       0.0.0.0:11212->11211/tcp                
mysql                docker-entrypoint.sh mysqld      Up       0.0.0.0:13306->3306/tcp, 33060/tcp      
nginx                nginx -g daemon off;             Up       0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
web                  docker-php-entrypoint php-fpm    Up       9000/tcp

うん、全て起動している。

起動が確認できたらブラウザでhttp://localhostにアクセスする。

するとpublic/index.phpの内容が表示されるのでPHPを動かせた。

dockerを終了させるには、

$ docker-compose stop

上記コマンドでコンテナを停止させる。


時間はかかったがPHPの開発環境を作成することができた。

docker-compose.ymlが何をしているのかや、これからPHPプロジェクトのディレクトリをどのように配置していけばいいのかはまだわからないが、追い追い調べていこうと思う。

Dockerを触ってみたい(1)

PHPの開発環境を作りたいけど、少し前に上司に教わりながらvagrantLAMP環境を作成したときは全く理解できなかったトラウマがあります。

そろそろちゃんと理解しなければということで、重い腰をあげてとりあえずDockerから始めてみます。

参考にさせていただいたところ

koni.hateblo.jp

↑これをそのまんまやってみます。

Dockerの準備

※homebrewはインストール済みとします。

まずはターミナルに下記を打ち込んでdockerをインストールします。

$ brew cask install docker

brewbrew caskの違いについてですが、brew caskGUIツールをインストールする用らしいです(違うかも...)。

dockerがインストールされたことを確認するためにバージョン情報を表示してみる。

$ docker version
-bash: docker: command not found

は?

なんで?インストールされたんじゃないの...?

ググる

Homebrewを使ってDockerをMacにインストール - Qiita

なるほど、起動しないといけないのかな?

openというコマンドを使用しているみたいなので、そこから調べてみる。

コマンド/open - MacWiki

どうやらopenはダブルクリックでアプリを立ち上げるのと同様の動きをするらしい。

やってみる。

$ open /Applications/Docker.app

...

f:id:kituman:20190108232811p:plain
えぇ...

え?なにこれログインとか必要なの?

...と最初は思いましたが、右上のバツボタンをクリックして先ほどのコマンドを入力。

$ docker version
Client: Docker Engine - Community
 Version:           18.09.0
 API version:       1.39
 Go version:        go1.10.4
 Git commit:        4d60db4
 Built:             Wed Nov  7 00:47:43 2018
 OS/Arch:           darwin/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          18.09.0
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.4
  Git commit:       4d60db4
  Built:            Wed Nov  7 00:55:00 2018
  OS/Arch:          linux/amd64
  Experimental:     false

インストールできた!!

次はPHPが動作する環境を作りたい。

デザインパターンを学ぶ Strategy パターン偏その1

ゲームを作ろう

私はとあるゲームの開発を行っています。 このゲームは多種多様なキャラクターを操作することができます。 そこで全てのキャラクターが継承するChampionスーパークラスを作成しました。

//Champion.java
public class Champion {
  public void attack(){
    System.out.println("attack");
  }
  public void move(){
    System.out.println("move");
  }
  public void display();
}

全てのキャラクターは攻撃と移動が可能で、キャラクター毎に違う見た目を持っています。

//Ez.java
ublic class Ez {
  public void display(){
    System.out.println("Ezの表示");
  }
}

//Toris.java
ublic class Toris {
  public void display(){
    System.out.println("Torisの表示");
  }
}

うん、完璧な実装ですね。





しかし、仕様が変わってしまった

私の上司はこのゲームにブリンクが必要だと判断しました。 ブリンクのスタイリッシュな動きがこのゲームを素晴らしいものにするのです。

私は必要なのはChampionクラスにsay()メソッドを追加することだと判断しました。 そうすれば全てのキャラクターがブリンクをつかえるようになります。 さあ、実装しましょう。

//Champion.java
public class Champion {
  public void attack(){
    System.out.println("attack");
  }
  public void move(){
    System.out.println("move");
  }
  public void display();
  public void blink() {
    System.out.println("blink");
  }
}





だがしかし

私は気づいていませんでした。 全てのキャラクターがブリンクできるべきではないということに。

ブリンクを持ったコグマウがサマナーズリフトを駆け回っています。 Championスーパークラスに新しいふるまいを追加した際に、いくつかのChampionサブクラスには適切でないふるまいを追加してしまったのです。





display()メソッドと同様に、ブリンクできないキャラクターで常にblink()メソッドをオーバーライドすることもできます……

しかし、そうすると他のメソッドを追加した際はどうなるのでしょう?

例えば、MSアップを追加したときには? そのたびにそれが必要のないすべてのサブクラスでオーバーライドするのですか?





続く

続・RMPとIRPの差分ベクトルの積和平均による項目選択ルール

たぶんRMPの計算方法がわかったので書く.
確認のしようがないのであんまりあてにしないでください.
前回の記事よりはマシ程度に受け止めて.

定義

RMPの計算に必要なのは

  • IRP
  • 受験者の回答

の2つ

IRP

q : 問題番号
r : ランク
IRP : \displaystyle{\vec{V}\ni\vec{V_q}\ni\vec{v_{qr}}}
また,このようにも見れる
IRP : \displaystyle{\vec{V}\ni\vec{V_r}\ni\vec{v_{qr}}}

表にすると
f:id:kituman:20170208183826p:plain

受験者の回答

同じく
q : 問題番号
回答 : \displaystyle{\vec{U}\ni u_q}

表にすると
f:id:kituman:20170208184235p:plain

RMP

同じく
r : ランク
RMP : \displaystyle {\vec{p} \ni p_r}

表にすると
f:id:kituman:20170208184459p:plain

RMPを算出する

例えば,RMP要素をとあるランクr1について求めるとき
ランクrの範囲を1~Rとすると
RMPの要素 : \displaystyle{p_{r1} = \frac{p(\vec{U} | \vec{V_{r1}})g(f_{r1})}{\sum_{r=1}^{R} p(\vec{U} | \vec{V_r})g(f_r)}}

\displaystyle{g(f_r)}は前回話したね.事前確立だったはず.


また,問題番号qの範囲を1~Qとすると
\displaystyle{ p(\vec{U} | \vec{V_{r1}}) = exp(\sum_{q=1}^{Q} u_q\log (v_{qr1}) + (1 - u_q)\log (1 - v_{qr1}))}

完成

後は?

どんどん問題を出しつつRMPを更新してください.
{g(f_r)}は前回のRMPを使えばいいと思います.
qの範囲Qは問題を解く毎に増えていくと思います.

しかしまあ,ここら辺のことはよくわかってないので
前回のリンクを参照してください.