今回は初心者向けにGo言語において超簡単なWebサーバを実装します。最終的なコードは最後に載せているので、それだけ見たい方は下まで飛ばしてください!
ListenAndServe関数
http.ListenAndServe
関数を用いることでサーバを起動することができます。
http.ListenAndServe(":8080", nil)
http.ListenAndServe
の中身を見てみましょう。VSCodeであればfn+F12
で関数の定義に飛ぶことができます。もちろん、Githubでも確認できます。
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
これを見ると、addrとhandlerを使ってhttp.Server
構造体を作り、それに対してListenAndServeしているのがわかります。
Addr
サーバーアドレス、インターフェースHandler
は一つだけメソッドを定義します。
Handler
2つめの引数Handler
インターフェースは、1つだけメソッドを定義します。
type Handler interface {
ServeHTTP(ResponseWriter, *http.Request)
}
ResponseWriter
最初の引数ResponseWriter
インターフェースは以下のように3つのメソッドを持ちます。
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(statusCode int)
}
これらのメソッドは以下のように呼び出すことができます。
-
Header
-
http.Server
のインスタンスを取得し、ヘッダを設定する
-
-
WriteHeader
- statusCodeを設定(200の場合は省略可能)
-
Write
- レスポンスボディを設定
*http.Request
2番目の引数*http.Request
は以下のような構造体です。詳細は省きますが、サーバーに送られたリクエストを格納していて、これ使ったらいろいろ取得できるよ、といった認識で大丈夫です。(たぶん...)
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
ctx context.Context
}
今回実装するサーバーでは、URL
を呼び出して、リクエストに投げられたURLを取得しています。
もしhttp.ListenAndServe
の第二引数がnil
だった場合は、デフォルトで用意されているDefaultServeMux
が使用されます。
ということで、おさらいをすると
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
で定義されたListenAndServeにサーバーアドレスとHandler
を引数にとることでサーバを起動できます。
ここで作られるServer
構造体は
type Server struct {
Addr string
Handler Handler // handler to invoke, http.DefaultServeMux if nil
// (以下略)
}
のように、Addr
やHandler
を持ちます。このServer
構造体のインスタンスListenAndServe()
を用いて起動します。
// ListenAndServe always returns a non-nil error. After Shutdown or Close,
// the returned error is ErrServerClosed.
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
サーバの作成
さて、実際にListenAndServe関数を使って起動してみます。
err := http.ListenAndServe(
":8080",
http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}),
)
ここで、http.HandlerFunc
を使っています。以下で定義され、ServeHTTP
メソッドも用意されています。
type HandlerFunc func(ResponseWriter, *Request)
func (http.HandlerFunc).ServeHTTP(w http.ResponseWriter, r *http.Request)
http.HandlerFunc
はGoのhttp
パッケージにおいて、任意の関数をhttp.Handler
インターフェースに適合させるための型です。この型は、特定のシグネチャを持つ関数をServeHTTP
メソッドを実装したハンドラーとして扱うことを可能にします。これにより、簡単な関数を使用してHTTPリクエストを処理するカスタムハンドラーを素早く作成することができます。
また、
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
において、fmt.Fprintf()
は内部的にWrite()
メソッドを呼び出してレスポンスボディにデータを書き込んでいます。
最終的なコード
package main
import (
"fmt"
"net/http"
"os"
)
func main() {
err := http.ListenAndServe(
":8080",
http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}),
)
if err != nil {
fmt.Printf("failed to terminate server: %v", err)
os.Exit(1)
}
}
これをターミナル上でgo run server.go
として立ち上げ、別ターミナルで
curl localhost:8080/world
とリクエストを送ってみましょう。
レスポンスとしてHello, world!%
が返ってきたら成功です。