🍇
HTTP GET 少し深掘り
少し気になったのでなるべく詳しめに調べてみた。
一番単純そうなGETメソッドのみに絞る。
HTTP GETの主なフロー
- tcp接続する
- httpリクエストする。
- tcp切断する。
パケットの調査
以下のような単純なサーバとクライアントを書きキャプチャして内容を確認した。
- server
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Yo!")
})
http.ListenAndServe("localhost:8000", nil)
}
- client
package main
import (
"fmt"
"net/http"
)
func main() {
resp, _ := http.Get("http://localhost:8000")
defer resp.Body.Close()
fmt.Println(resp.Body)
}
調査結果
以下の図のような通信を行っている。
- 今回初めて知った点
httpを送信した後も確認用のACKを送っている。
httpプロトコル自体はテキストを送っているだけのように見える。
クライアントの実装を変えて送ってみる
決まったフォーマットのテキストをtcp上で送っているだけということがわかったので少し実装を変えて試してみる。
クライアントの方が簡単そうなのでクライアントのみ。
- httpをそのままかく
httpのテキストを以下の様に直に書いて、tcpで送る
package main
import (
"fmt"
"net"
)
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
defer conn.Close()
if err != nil {
fmt.Println("error: ", err)
}
httpGetStr := "GET / HTTP/1.1\r\nHost: localhost:8000\r\nUser-Agent: curl/7.81.0\r\nAccept: */*\r\n\r\n"
data := []byte(httpGetStr)
_, err = conn.Write(data)
if err != nil {
fmt.Println("error: ", err)
}
// ここから読み取り
readdata := make([]byte, 1024)
count, _ := conn.Read(readdata)
fmt.Println(string(readdata[:count]))
}
- ソケットに書いてみる。
package main
import (
"fmt"
"syscall"
)
func main() {
// Create a TCP socket
sock, _ := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, 0)
defer syscall.Close(sock)
// Create Connection
syscall.Connect(sock, &syscall.SockaddrInet4{
Port: 8000,
Addr: [4]byte{127, 0, 0, 1},
})
httpGet := "GET / HTTP/1.1\r\nHost: localhost:8000\r\nUser-Agent: curl/7.81.0\r\nAccept: */*\r\n\r\n"
syscall.Write(sock, []byte(httpGet))
response := make([]byte, 1024)
n, _ := syscall.Read(sock, response)
fmt.Println(string(response[:n]))
}
上記の実装でもHTTP GETをいちよ送れた。
syscall.Socket
初めて使ったが、syscall.SOCK_STREAM
を指定するとtcp用のソケットを作ってくれるらしい。
参考
たぶんtcpの3ウェイハンドシェイクはSocket関数で抽象化されているらしい。
今回使ったコード
Discussion