知らないうちにfavicon.icoにアクセスされる
Goで自作ルータをテストしていたときのこと...
とりあえずルート("/"
)だけ処理できるようにだけして動かしてみようと思い、ルーティング用のプログラムを作成しブラウザからlocalhostにアクセスして正しいルーティングが行えるかをテストしていた。
アクセスしてみるとエラーが発生。panicで落ちていた。
スライスの範囲外を参照しているらしいが、"/"へのアクセスに対しては正しく動作するように書いたはずだが。
おかしいと思い、リクエストを調べてみると...
どうやらfavicon.ico
というパスにアクセスされたことが原因らしい。
favicon.icoとは?
favicon(ファビコン)は、ウェブサイトのシンボルマーク・イメージとして、ウェブサイト運営者がウェブサイトやウェブページに配置するアイコンの俗称である。favorite icon(フェイバリット・アイコン:お気に入りアイコン)という英語の語句を縮約したものである。
ルートディレクトリに favicon.ico という名称のファイルを設置しておくと、HTML中で指定が無くともfaviconとして認識される。
なるほど。
何に使うの?
ブラウザのタブの左側にアイコンが表示されていると思うが、これの表示に使用している。
ブラウザはサイトにアクセスすると自動的にfavicon.icoを要求する。
知らないうちにアクセスされていて困惑したが、なるほど。
今後ブラウザからアクセスする際には気をつけよう。
【Go言語】http周りを調べてみた ~ ListenAndServeは何をしているのか
golangのhttpに入門してみて、自作のハンドラー作成 -> 登録 -> サーバ起動の流れはだいたいわかった。
しかし自作のルータ(mux)を作成しようと考えると全くどこから手をつけたらいいのかわからない。
そこで、デフォルトのServeMuxを調べた。
http.ListenAndServe(":8080", nil) //http.ListenAndServe(":8080", myMux)
よくみるのが上記の形だ。ListenAndServe
の第二引数にmuxを指定できる。
ここに渡せるmuxとは何なのかを知るために、ListenAndServe
の定義を見てみると、
func ListenAndServe(addr string, handler Handler) error
このようにHandler
型であることがわかる。
Handler
はインターフェースでServeHTTP
を持つ。
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
つまり、よくみる自作ハンドラーもmuxもどちらもHandler
型なのだ。
実験するために以下のコードを作成した。
package main import ( "net/http" ) func main() { router := NewRouter() http.ListenAndServe(":8080", router) } // ---- 自作mux ---- type Router struct {} func NewRouter() *Router { return new(Router) } func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Print("Router::ServeHTTP\n") fmt.Print(r.URL.Path + "\n") }
上記にコードを実行し、アクセスしてみる。
[http:localhost:8080/aaa]にアクセス
Router::ServeHTTP /aaa
[http:localhost:8080/hoge/huga/huni]にアクセス
Router::ServeHTTP /hoge/huga/huni
つまり、ListenAndServeはアクセスが発生した際に、第二引数で指定したHandlerのServeHTTPを実行する。
そのため自作のmuxでルーティングを行う場合は、ServeHTTPを実装(Handlerインターフェース)してその中でアクセスされたパスに対しての検証を行えば良いと思われる。
olang勉強するンGo(4) ~ ページの返却
前回まででリクエストに対するハンドラーを登録できた。
今回はハンドラーでHTMLを返却できるようにしたい。
home、about、contactページを作成しようと思う。
完成品
先に完成後のイメージを貼っておく。
レイアウト
まずはlayoutを作成する。
layout.html.tpl
というファイル名でhtmlのテンプレートを作成した。
ちなみに拡張子はなんでも良いのだが、.tpl
形式が一般的らしい。
<!DOCTYPE html> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>golang-blog</title> <meta charset="utf-8"> <meta name="description" content="golang-blog"> <meta name="author" content="kituman"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous"> </head> <body> <nav class="navbar navbar-dark bg-dark navbar-expand-lg"> <div class="collapse navbar-collapse" id="navbarNavAltMarkup"> <div class="navbar-nav"> <a class="nav-item nav-link" href="http://localhost:8080/">Home</a> <a class="nav-item nav-link" href="http://localhost:8080/about">About</a> <a class="nav-item nav-link" href="http://localhost:8080/contact">Contact</a> </div> </div> </nav> {{.}} <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script> </body> </html>
見た目が壊滅的だったので、Bootstrapを使用して体裁を整えた。
Go言語のhtml/templateでは{{.}}
となっている箇所にデータを流し込める。
リクエストを処理する
前回同様にリクエストに対するハンドラーを作成していく。
main.go
の内容を以下のように編集する。
package main import ( "html/template" "log" "net/http" ) func main() { mux := http.NewServeMux() // handler登録 aboutHandler := new(AboutHandler) contactHandler := new(ContactHandler) articleHandler := new(ArticleHandler) mux.Handle("/about", aboutHandler) mux.Handle("/contact", contactHandler) mux.Handle("/", articleHandler) // サーバ起動 http.ListenAndServe(":8080", mux) } // AboutHandler : aboutを表示 type AboutHandler struct{} func (handler *AboutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { t := template.Must(template.ParseFiles("layout.html.tpl")) str := "これはGo言語を勉強するためのブログです。" err := t.ExecuteTemplate(w, "layout.html.tpl", str) if err != nil { log.Fatal(err) } } // ContactHandler : aboutを表示 type ContactHandler struct{} func (handler *ContactHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { t := template.Must(template.ParseFiles("layout.html.tpl")) str := "ご連絡ください。" err := t.ExecuteTemplate(w, "layout.html.tpl", str) if err != nil { log.Fatal(err) } } // ArticleHandler : 記事を表示 type ArticleHandler struct{} func (handler *ArticleHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { t := template.Must(template.ParseFiles("layout.html.tpl")) str := "今後、記事を表示できるようにします。" err := t.ExecuteTemplate(w, "layout.html.tpl", str) if err != nil { log.Fatal(err) } }
今回使用したhtml/templateについて順番に説明していく。
t := template.Must(template.ParseFiles("layout.html.tpl"))
について、
ここではtemplate.ParseFiles
により先ほど作成したlayout.html.tpl
をパースしている。
通常ではtemplate.ParseFiles
だけで十分だが、template.Must
を通すことでエラー処理を自動で行っている。
err := t.ExecuteTemplate(w, "layout.html.tpl", str)
について、
ここではhttp.ResponseWriter
に対して先ほどパースしたファイルを出力している。
パースしたテンプレートの名前は、ファイル名と同一になる。
また、第三引数でテンプレートに流し込むデータを指定できる。
これによりstr
の内容がテンプレート内の{{.}}
に展開される。
とりあえずhtmlを表示させることが可能になった。
ただ、入れ後のテンプレートなどの表示の仕方がわからないので今後調べていきたい。
次回はDBアクセス周りか、その準備を行いたい。
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/Aとhttp://localhost:8080/Bにアクセスしてみると、それぞれA
・B
と表示される。
解説
関数main
の最後でhttp.ListenAndServeを呼び出している。
この関数により第一引数に指定した8080ポートでサーバが起動する。
第二引数に渡したmux
(type ServeMux
)はHTTP request multiplexer
とかいうもの。
リクエストに応じたハンドラーを呼び出すらしいが、よくわからん。
とにかくこのServeMux
にURLと呼び出したいハンドラーを登録すればリクエストを処理できるみたいだ。
で、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 } }
ごちゃごちゃと書いてあるが、色々とチェックをした後にpattern
とhandler
をマップに対応づけている。
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.lock
・Gopkg.toml
・vendor
が作成され、プロジェクトの初期化が行われた。
忘れずに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 CodeをVSCodeでインストール。
依存しているパッケージがあるため順番にインストールする。
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にbin
・pkg
・src
ディレクトリが作成されたことが確認できる。
動作テスト
適当なプログラムファイルを作成して動作を確認する。
$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プロジェクトのディレクトリをどのように配置していけばいいのかはまだわからないが、追い追い調べていこうと思う。