HTTP
TreasureDay 24

Status Codeを下ネタで返すサーバーを書きました。

これはTreasure Advent Calendar 2018の24日目の記事です。レポートがやばくて投稿が遅れました、すみません。。

さて、今日はクリスマスですね!クリスマスには下ネタを言ってもいいと法律で決まっているようなのでバンバン綴っていきたいと思います!w

Status Codeとは

HTTPのレスポンス状態を表す番号とメッセージのことです。(メッセージ自体は番号の意味なので厳密には含まないかも)

"404 Not Found"とかのやつですね。誰がクリスマスの「お相手は見つかりませんでした」や!!"201 Created"になれるように頑張ります。おっと、守主語によってはよろしくなさそうですが、、w

で、このStatus Codeって大量にあるんですよね。よく見るやつからほぼお目にかかれないやつまで。それを万国共通語の下ネタで覚えちゃおうって話です。

だいたいエラーを返すサーバーを書きました

だらだら言葉だけで説明していても面白くないので、実際にサーバーを実装してみました。普通に実装していたらほぼ200になるので、ランダムで適当にエラーを返すサーバーをつ作りました。ほんとはプロキシとか挿れてちゃんとエラーハンドリングしている物をつくりたかったんですが、時間の関係で簡易的な実装にしました。

main.go
package main

import (
    "fmt"
    "math/rand"
    "net/http"
    "strconv"
    "time"

    "github.com/Dragon-taro/learn-status-code/db"
    "github.com/Dragon-taro/learn-status-code/status"
)

func getStatusCode() int {
    rand.Seed(time.Now().UnixNano())
    codeArray := []int{200, 200, 200, 200, 200, 200, 201, 205, 301, 400, 401, 402, 403, 404, 405, 413, 414, 429, 502, 503}

    return codeArray[rand.Intn(len(codeArray))]
}

func createBody(code int) string {
    return strconv.Itoa(code) + ": " + status.StatusText(code)
}

func handleStaus() (int, string) {
    code := getStatusCode()
    body := createBody(code)
    return code, body
}

func handleRequest(w http.ResponseWriter, req *http.Request) {
    if req.Method == "POST" {
        code := http.StatusMethodNotAllowed
        body := createBody(code)
        w.WriteHeader(code)
        fmt.Fprint(w, body)
        return
    }

    status, body := handleStaus()
    w.WriteHeader(status)
    fmt.Fprint(w, body)
    return
}



func main() {
    http.HandleFunc("/", handleRequest)
    http.ListenAndServe(":8080", nil)
}

ランダムに配列に格納されたint型のStatus Codeをとってきって、それに対応するメッセージと共に返しています。簡易的にstringで返してますが、本来はjsonとかで返した方がよさそうです。

Status Codeの呼び出しもとに関しては、net/httpパッケージを参考に独自のファイルを作成してそれを呼び出しています。(つまりメッセージを下ネタに変換しています。w)"github.com/Dragon-taro/learn-status-code/status"で呼び出しています。ここに載せるのは気がひけるので、各自僕のリポジトリをみてくださいw

では、実際に叩いてみましょう。まずはGET。

$ curl --dump-header - http://localhost:8080/
HTTP/1.1 200 OK
Date: Mon, 24 Dec 2018 05:28:05 GMT
Content-Length: 23
Content-Type: text/plain; charset=utf-8

200: あ、いいよ♡%

$ curl --dump-header - http://localhost:8080/
HTTP/1.1 429 Too Many Requests
Date: Mon, 24 Dec 2018 05:28:02 GMT
Content-Length: 18
Content-Type: text/plain; charset=utf-8

429: 激しすぎw%

まあこんな感じですね。ちなみに、--dump-header -を付与することでレスポンスのheaderを見ることができます。

次にPOST。

$ curl -X POST --dump-header - http://localhost:8080/
HTTP/1.1 405 Method Not Allowed
Date: Mon, 24 Dec 2018 05:32:35 GMT
Content-Length: 29
Content-Type: text/plain; charset=utf-8

405: 本番は禁止です。%

このサーバーは本番禁止のデリヘルみたいなもんなので、POSTすると怒られます。

適当すぎるUserエンドポイント

さてこれだけでは芸がないので、/userでも作りましょう。

main.go
func handleRequestUser(w http.ResponseWriter, req *http.Request) {
    if req.Method == "POST" {
        _, err := db.Connect()
        if err != nil {
            code := http.StatusInternalServerError
            body := createBody(code)
            w.WriteHeader(code)
            fmt.Fprint(w, body)
            return
        }
    }

    code := http.StatusPaymentRequired
    body := createBody(code)
    w.WriteHeader(code)
    fmt.Fprint(w, body)
    return
}

// ...

func main() {
    http.HandleFunc("/", handleRequest)
    http.HandleFunc("/user", handleRequestUser)
    http.ListenAndServe(":8080", nil)
}
db/handler.go
package db

import (
    "fmt"

    _ "github.com/go-sql-driver/mysql"
    "github.com/jmoiron/sqlx"
)

func Connect() (*sqlx.DB, error) {
    return sqlx.Connect(
        "mysql",
        fmt.Sprintf(
            "%s:%s@tcp(%s:%s)/%s",
            "root",
            "password",
            "localhost",
            "3306",
            "Xmas",
        ),
    )
}

このエンドポイントはPOSTがくると先ほどとは違ってDBにちゃんとアクセスしようとします。そこまではいいのですが、作ってもいないDBに適当にアクセスしようとするので、当たり前のようにエラーが出ます。

どうせエラーなので、成功したときの処理は書いていません。

では、POSTしてみましょう。

curl -X POST --dump-header - http://localhost:8080/user
HTTP/1.1 500 Internal Server Error
Date: Mon, 24 Dec 2018 05:45:50 GMT
Content-Length: 16
Content-Type: text/plain; charset=utf-8

500: EDおつ。%

内部的なエラーが発生します。いやいや、悪いのお前やんっていう。

そのくせにGETしたら金を請求されます。適当すぎる。

curl --dump-header - http://localhost:8080/user
HTTP/1.1 402 Payment Required
Date: Mon, 24 Dec 2018 05:47:39 GMT
Content-Length: 33
Content-Type: text/plain; charset=utf-8

402: 1時間2万円で〜す。%

値段設定がやっぱりデリヘル。

Chiro

最後に/chiroを作りましょう。

main.go
func handleRequestChiro(w http.ResponseWriter, req *http.Request) {
    code := http.StatusGatewayTimeout
    body := createBody(code)
    w.WriteHeader(code)
    fmt.Fprint(w, body)
    time.Sleep(3 * time.Second)
    return
}

func main() {
    http.HandleFunc("/", handleRequest)
    http.HandleFunc("/user", handleRequestUser)
    http.HandleFunc("/chiro", handleRequestChiro)
    http.ListenAndServe(":8080", nil)
}

満を持してタイムアウトしてきます。

リクエストをしてみましょう。

$ curl --dump-header - http://localhost:8080/chiro
HTTP/1.1 504 Gateway Timeout
Date: Mon, 24 Dec 2018 05:54:10 GMT
Content-Length: 26
Content-Type: text/plain; charset=utf-8

504: 遅漏すぎん?笑%

3秒後にやっとレスポンスが返ってきたかと思ったらめちゃくちゃ煽ってきます。いやいや、遅漏なのはあなたでしょ、と。

詰まったところ

最初は、"github.com/Dragon-taro/learn-status-code/status"をmainパッケージに書いていたのですが、buildは通ってもrunでは実行できませんでした。そこで、時間がなかったのでstatusパッケージを作ったのですが、ここにgoのコンパイルの落とし穴があったようです。

go build <file名>のときは同一パッケージのファイルも同時にコンパイルされるようなのですが、go run <file名>のときは指定されたファイルしかコンパイルがされないようです。

そのため、go run **.goのように複数ファイルを指定する必要があるらしいです。@rky_1011教えてくれてありがとう!

以上、唯一有益?な内容でした。w

終わりに

人生の大切な数分を僕のくだらない記事に捧げてくださりありがとうございます。(しかも貴重な平成最後のクリスマスの数分!)クリスマスに予定がある人もそうでない人も、この記事を読んで少しでも笑っていただけたら幸いです。

では、よき平成最後の性y..おっと聖夜を!