雑草SEの備忘録

東大を卒業し、SEとして働くことになった。備忘録的に作業を綴る。

Goならわかるシステムプログラミング 6.6.1のKeep-Alive対応のHTTPサーバーのコードがわかりにくい問題

少し前にはてな界隈でも話題になった、Goならわかるシステムプログラミングという本を写経しています。



Goならわかるシステムプログラミング

  • 作者: 渋川よしき
  • 出版社/メーカー: Lambda Note
  • 発売日:2017/10/19
この本は、Go言語の勉強のために購入したのですが、Go言語というより、HTTPプロトコルなど、比較的程レイヤーの解説および実装となっていて、Go言語の勉強というよりも、TCPとは何か、UDPとは何か、OSにおけるファイルシステムとはどういうものなのかということを学べる書籍となっています。そういう意味で、今まで何気なく使っているものより少し下のレイヤーの技術を勉強するにはもってこいの本だと思います。

その中で、「6.6.1 速度改善(1)」にあるp107ページのコードがあるのですが、「6.5.1 TCPソケットを使ったHTTPサーバー」の差分になっており、差分といってもどこをどう編集すればいいか少し戸惑ったので、diffで表現して見ました。
また、差分表記ではないものは、こちらにあります。

 package main

 import (
     "bufio"
     "fmt"
+    "io"
     "io/ioutil"
     "net"
     "net/http"
     "net/http/httputil"
     "strings"
+    "time"
 )

 func main() {
     listener, err := net.Listen("tcp", "localhost:8888")
     if err != nil {
         panic(err)
     }
     fmt.Println("Server is running at localhost:8888")
     for {
         conn, err := listener.Accept()
         if err != nil {
            panic(err)
         }
-        fmt.Printf("Accept %v\n", conn.RemoteAddr())
-        request, err := http.ReadRequest(
-            bufio.NewReader(conn))
-        if err != nil {
-              panic(err)
-        }
-        dump, err := httputil.DumpRequest(request, true)
-        if err != nil {
-              panic(err)
-        }
-        fmt.Println(string(dump))
-        response := http.Response{
-              StatusCode: 200,
-              ProtoMajor: 1,
-              ProtoMinor: 0,
-              Body:    ioutil.NopCloser(strings.NewReader("Hellow World\n")),
-        }
-        response.Write(conn)
-        conn.Close()
+
+        go func() {
+            defer conn.Close()
+            fmt.Printf("Accept %v\n", conn.RemoteAddr())
+
+            for {
+                conn.SetReadDeadline(time.Now().Add(5 * time.Second))
+                request, err := http.ReadRequest(bufio.NewReader(conn))
+                if err != nil {
+                     neterr, ok := err.(net.Error)
+                     if ok && neterr.Timeout() {
+                          fmt.Println("Timeout")
+                          break
+                     } else if err == io.EOF {
+                          break
+                     }
+                     panic(err)
+                }
+                dump, err := httputil.DumpRequest(request, true)
+                if err != nil {
+                     panic(err)
+                }
+                fmt.Println(string(dump))
+                content := "Hello World\n"
+
+                response := http.Response{
+                     StatusCode:    200,
+                     ProtoMajor:    1,
+                     ProtoMinor:    1,
+                     ContentLength: int64(len(content)),
+                     Body: ioutil.NopCloser(
+                          strings.NewReader(content)),
+                }
+                response.Write(conn)
+            }
+        }()
     }
 }