こんにちは。KOUKIです。
とあるWeb系企業でエンジニアをやってます。
以前、Interfaceに関する記事を出しました。
この記事は、Interfaceの使い方に関して初級者から中級者になることを想定した記事です。
Interfaceはめちゃくちゃ便利なのですが、使い方が難しく、また実戦で活用しにくい機能の一つかと思います。
そこで、Goの標準パッケージである「net/http」を題材に、Interfaceをより理解することを目的にこの記事を書きました。
<目次>
net/httpパッケージとは
net/httpパッケージは、HTTPのClientとServerの機能を提供します。
Exampleから簡単な例を取り上げましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package main import ( "fmt" "io" "log" "net/http" ) func main() { res, err := http.Get("http://www.google.com/robots.txt") if err != nil { log.Fatal(err) } body, err := io.ReadAll(res.Body) res.Body.Close() if res.StatusCode > 299 { log.Fatalf("Response failed with status code: %d and\nbody: %s\n", res.StatusCode, body) } if err != nil { log.Fatal(err) } fmt.Printf("%s", body) } |
上記のコードは、http://www.google.com/robots.txtに対してGetリクエストを送り、その結果をコンソール上に表示します。
簡単にHTTP Getリクエストを行えるプログラムが作成できるわけですね。
もちろん、サーバーも作れます。
1 2 3 4 5 6 7 8 9 10 11 12 |
package main import ( "log" "net/http" ) func main() { // Simple static webserver: log.Fatal(http.ListenAndServe( ":8080", http.FileServer(http.Dir("/usr/share/doc")))) } |
上記のコードは、8080番ポートでリッスンするFileサーバーです。
Handlerが大切な件
net/httpパッケージを理解する上で、最も大切なものの一つがHandler Interfaceでしょう。
1 2 3 |
type Handler interface { ServeHTTP(ResponseWriter, *Request) } |
このHandler Interfaceはnet/httpパッケージ内の至る所で使われています。
ちなみに、ServeHTTPは、リクエストの応答を呼び出し元に返す機能を提供します。
例えばですが、Serverを立ち上げるとき、以下の処理を呼び出します。
1 |
func ListenAndServe(addr string, handler Handler) error |
1 |
func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error |
どちらの処理も引数にHandler Interfaceを渡すことになっています。
そして、このHandller Interfaceには「ServeHTTP」メソッドが実装されることになっており、これを使って呼び出し元にリクエストを返す流れになります。
ResponseWriterとRequest
Handler InterfaceのServeHTTP関数は、「ResponseWriter(レスポンス)」と「Request(リクエスト)」を持っています。
Requestは呼び出しもとからの情報が格納されるStructであるとして、注目したいのがResponseWriter Interfaceです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
type ResponseWriter interface { Header() Header Write([]byte) (int, error) WriteHeader(statusCode int) } type Request struct { Method string URL *url.URL Proto string // "HTTP/1.0" ProtoMajor int // 1 ProtoMinor int // 0 Header Header Body io.ReadCloser GetBody func() (io.ReadCloser, error) ContentLength int64 TransferEncoding []string Close bool Host string Form url.Values PostForm url.Values MultipartForm *multipart.Form Trailer Header RemoteAddr string RequestURI string TLS *tls.ConnectionState Cancel <-chan struct{} Response *Response } |
ResponseWriter Interfaceは、「Header」、「Write」、「WriteHeader」関数を持っており、名前からして処理結果を呼び出しもとに返すためのものと想像できます。
これらの機能を実装したものであれば、自作したメソッドもServeHTTP関数に渡せそうですね。
Handler Interfaceを紐解くと、こんな感じになっています。
1 2 3 4 5 6 7 8 9 10 11 |
type Handler interface { <-----ハンドラー ServeHTTP( <------ 呼び出しもとにレスポンスを返す ResponseWriter, <----- 処理結果をレスポンスに書き込む *Request <---- リクエスト情報保持(コネクションとか?) ) } // Handlerを渡して、リッスン // func ListenAndServe(addr string, handler Handler) error // FileServer Handler http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc"))) |
Handlerを作成しよう
Handlerを実装してみましょう。
まずは、Handler Interfaceのシグネチャを思い出してください。
1 2 3 4 5 |
// Handler Interfaceのシグネチャ type Handler interface { ServeHTTP(ResponseWriter, *Request) } |
このInterfaceは、「ServeHTTP(ResponseWriter, *Request)」(シグネチャ)を持っています。つまり、これを満たすメソッドを作成するとHandler Interfaceを実装したことになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package main import ( "fmt" "net/http" ) type bookman int func (b bookman) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Bookman API Serve!!!") } var port = ":8080" func main() { var b bookman fmt.Println("Start Bookman Server......") // 自作したハンドラーを渡す http.ListenAndServe(port, b) } |
bookmanを宣言し、「ServeHTTP」メソッドを実装しました。これでHandler Interfaceの要件を満たすので、ListenAndServeメソッドの第二引数に渡すことができます。
下記のコマンドで、サーバーを立ち上げましょう。
1 2 |
$ go run main.go Start Bookman Server...... |
サーバーは、「8080番ポート」をリッスンします。
ブラウザから「http://localhost:8080」にアクセスしてみましょう。

OKですね。問題なくサーバーが起動してます。
Client-Serverを構築してみよう
net/httpパッケージの至る所で使われる「Response」についてもう少し掘り下げます。
公式では、「Response represents the response from an HTTP request.(ResponseはHTTP Requestからのレスポンスを表す)」とあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
type Response struct { Status string // e.g. "200 OK" StatusCode int // e.g. 200 Proto string // e.g. "HTTP/1.0" ProtoMajor int // e.g. 1 ProtoMinor int // e.g. 0 Header Header Body io.ReadCloser ContentLength int64 TransferEncoding []string Close bool Uncompressed bool Trailer Header Request *Request TLS *tls.ConnectionState } |
サーバー側でリクエストを受け取った後、任意の処理を実行し、結果を呼び出しもとに返す。その際に必要なものがResponseです。
そして、これが「Client – Server」のアーキテクチャとなります。

処理結果を返却する際は、HTTPプロトコルやコミュニケーションルールなども合わせて返されるのでブラウザとの通信が可能になっています。
Go言語だけで、このアーキテクチャを実装できるので、実装してみましょう。
Web画面を作る
以下のファイルを作成してください。
1 2 |
touch main.go touch form.gohtml |
「form.gohtml」には、Goのテンプレートファイルを書き込みます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>Input Type Submit</title> </head> <body> {{if .}} <main> <p><strong>Bookman</strong> produced by <em> Selfnote </em>:</p> {{range $key, $value := .}} <p><strong>{{$key}}</strong></p> <ul>{{range $value}}<li><em>{{.}}</em></li>{{end}}</ul> {{end}} </main> {{end}} <form action="/" method="POST"> <input type="text" name="name" placeholder="name" id="name" autocomplete="off"> <input type="submit" name="submit-btn" value="User submit"> </form> <form action="/?author=j.k. rowling" method="POST"> <input type="text" name="book name" placeholder="book name" id="book-name" autocomplete="off"> <input type="submit" name="submit-btn" value="Book Name Submit"> </form> </body> </html> |
上記のHTMLの中には、「{{if .}}」など見慣れない構文があると思います。
これは、Go言語の標準パッケージである「text/template」の構文で、Go言語で処理した結果をHTMLのテンプレート内に埋め込むことができます。
VS Codeのおすすめなプラグインを下記の記事で紹介してます。
アプリケーションサーバーを作る
form.gohtmlはGoコードから読み込んで使います。先ほど実装したbookmanサーバーを拡張しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
package main import ( "fmt" "html/template" "log" "net/http" ) const ( formName = "form.gohtml" port = ":8080" ) var tpl *template.Template func init() { // テンプレートを解析する tpl = template.Must(template.ParseFiles(formName)) } type bookman int func (b bookman) ServeHTTP(w http.ResponseWriter, req *http.Request) { err := req.ParseForm() if err != nil { log.Fatalln(err) } // req.Formは、「type Values map[string][]string」の型を持ち、 // Formの入力値を保持する log.Println(req.Form) tpl.ExecuteTemplate(w, formName, req.Form) } func main() { var b bookman fmt.Println("Start Bookman Server......") http.ListenAndServe(port, b) } |
Goのコードからテンプレートを読み込むために、init関数でform.gohtmlをパースし、tpl変数に格納してます。
それをServeHTTPメソッド内で、リクエストと共に呼び出しもとに返す処理を追加実装しました。
req.Formには、Formから渡された値が入ります。
1 2 3 |
// 例 2022/02/14 05:37:10 map[name:[Selfnote] submit-btn:[User submit]] 2022/02/14 05:37:22 map[author:[j.k. rowling] book name:[Harry Potter] submit-btn:[Book Name Submit]] |
動作確認
下記のコマンドで、bookmanサーバーを起動します。
1 |
$ go run main.go |
そして、ブラウザから「http://localhost:8080」にアクセスします。

nameテキストボックスに「j.k. rowling」と打ち込んで、「User submit」を押下してみましょう。

続いて、book nameテキストボックスに「Harry Potter」を打ち込んで、「Book Name Submit」ボタンを押下してみます。

OKですね。Interfaceを実装した自分のHandlerで問題なく処理が行えていることがわかりました。
補足:詳細なデータ回収
reqパラメータにはたくさんのリクエスト情報が格納されているので、下記のような感じでデータ収集可能です。
※ テンプレートを直さないといけませんが…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func (b bookman) ServeHTTP(w http.ResponseWriter, req *http.Request) { err := req.ParseForm() if err != nil { log.Fatalln(err) } // データ収集 data := struct { Method string Submissions url.Values }{ req.Method, req.Form, } tpl.ExecuteTemplate(w, formName, data) } |
まとめ
簡単でしたが、以上です。
Interfaceを活用すれば、柔軟なプログラミングが行えることをお分かりいただけた、、、と思います^^
プログラムの密結合も防げるし、慣れればGoのコードもより読みやすくなります。
ただし、ソースコードを追うのがちょっと難しくなりますが…笑
ガンガン使ってより上級者になりましょう。
それでは、また!
コメントを残す
コメントを投稿するにはログインしてください。