知らないうちに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ページを作成しようと思う。

完成品

先に完成後のイメージを貼っておく。

f:id:kituman:20190120233455p:plain

レイアウト

まずは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/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プロジェクトのディレクトリをどのように配置していけばいいのかはまだわからないが、追い追い調べていこうと思う。