Created
December 17, 2014 09:28
-
-
Save yuroyoro/117d3043bc9128584a55 to your computer and use it in GitHub Desktop.
GoでHTTP/2 最速実装 v3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
GoでHTTP/2 最速実装 v3 | |
参考: | |
[HTTP/2 最速実装 v3 // Speaker Deck](https://speakerdeck.com/syucream/2-zui-su-shi-zhuang-v3) | |
[syucream/MinimumHTTP2](https://github.com/syucream/MinimumHTTP2) | |
[Hypertext Transfer Protocol version 2 (draft-ietf-httpbis-http2-14) 日本語訳](http://summerwind.jp/docs/draft-ietf-httpbis-http2-14/#section4-3) | |
[Jxck/http2](https://github.com/Jxck/http2) | |
*/ | |
package main | |
import ( | |
"errors" | |
"fmt" | |
"os" | |
"strings" | |
"time" | |
"io" | |
"crypto/tls" | |
"net" | |
"net/url" | |
"encoding/binary" | |
) | |
const Preface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" | |
var PrefaceData = []byte(Preface) | |
func main() { | |
if len(os.Args) < 1 { | |
fmt.Fprintf(os.Stderr, "usage: %s [url]\n", os.Args[0]) | |
os.Exit(1) | |
} | |
url, err := url.Parse(os.Args[1]) | |
if err != nil { | |
fmt.Fprintf(os.Stderr, "%s\n", err) | |
os.Exit(1) | |
} | |
err = request(url) | |
if err != nil { | |
fmt.Fprintf(os.Stderr, "%s\n", err) | |
os.Exit(1) | |
} | |
} | |
func request(u *url.URL) error { | |
// Connect to Server | |
conn, err := connect(u) | |
if err != nil { | |
return err | |
} | |
// 1. SEND magic octets as Connection Preface | |
fmt.Println("-- SEND Connection Preface") | |
sendPreface(conn) | |
// 2. SEND empty SETTINGS frame | |
fmt.Println("-- SEND empty SETTINGS frame") | |
sendSettingFrame(conn) | |
// 3 RECV SETTINGS frame and ACK | |
for { | |
fmt.Println("-- RECV SETTINGS frame and ACK") | |
recvfrm := recvSettingFrame(conn) | |
if recvfrm.Type != 0x4 { | |
return errors.New("recieved settings frame is invalid") | |
} | |
if recvfrm.Flags&0x1 == 0x1 { | |
// Detect Ack | |
break | |
} | |
// 4 Send ACK corresponding to SETTINGS from server | |
fmt.Println("-- Send ACK corresponding to SETTINGS from server") | |
sendAckSettingFrame(conn) | |
} | |
// 5. SEND HEADERS frame as request headers | |
fmt.Println("-- SEND HEADERS frame as request headers") | |
sendHeadersFrame(conn, | |
Pair{":method", "GET"}, | |
Pair{":scheme", u.Scheme}, | |
Pair{":authority", u.Host}, | |
Pair{":path", u.Path}, | |
) | |
// 6. RECV HEADERS frames as response headers | |
fmt.Println("-- RECV HEADERS frames as response headers") | |
recvhfrm := recvHeadersFrame(conn) | |
// 7. RECV DATA frame as response body | |
if recvhfrm.Flags&0x1 != 0x1 { // chceck the RECV HEADER frams's END_STREAM flag | |
fmt.Println("-- RECV DATA frame as response body") | |
for { | |
datafrm := recvDataFrame(conn) | |
if datafrm.Flags&0x1 == 0x1 { // END_STREAM flag | |
break | |
} | |
} | |
} | |
// 8. SEND GOAWAY frame | |
fmt.Println("-- SEND GOAWAY frame") | |
sendGoawayFrame(conn) | |
return nil | |
} | |
func makeAddr(u *url.URL) string { | |
if xs := strings.Split(u.Host, ":"); len(xs) < 2 { | |
port := 80 | |
if u.Scheme == "https" { | |
port = 443 | |
} | |
return fmt.Sprintf("%s:%d", u.Host, port) | |
} | |
return u.Host | |
} | |
func connect(u *url.URL) (io.ReadWriter, error) { | |
addr := makeAddr(u) | |
fmt.Printf("-- Connect to Server : addr -> %s\n", addr) | |
if u.Scheme == "https" { | |
config := tls.Config{ | |
InsecureSkipVerify: true, | |
NextProtos: []string{"h2-14"}, | |
} | |
conn, err := tls.Dial("tcp", addr, &config) | |
state := conn.ConnectionState() | |
fmt.Printf("%v %v\n", "handshake", state.HandshakeComplete) | |
fmt.Printf("%v %v\n", "protocol", state.NegotiatedProtocol) | |
return conn, err | |
} | |
return net.DialTimeout("tcp", addr, 10*time.Second) | |
} | |
func read(r io.Reader, data interface{}) { | |
err := binary.Read(r, binary.BigEndian, data) | |
if err != nil { | |
panic(err) | |
} | |
} | |
func write(w io.Writer, data interface{}) { | |
err := binary.Write(w, binary.BigEndian, data) | |
if err != nil { | |
panic(err) | |
} | |
} | |
func sendPreface(w io.Writer) { | |
write(w, PrefaceData) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"fmt" | |
"io" | |
"encoding/hex" | |
) | |
/* | |
0 1 2 3 | |
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| Length (24) | | |
+---------------+---------------+---------------+ | |
| Type (8) | Flags (8) | | |
+-+-+-----------+---------------+-------------------------------+ | |
|R| Stream Identifier (31) | | |
+=+=============================================================+ | |
| Frame Payload (0...) ... | |
+---------------------------------------------------------------+ | |
*/ | |
type HTTP2FrameHeader struct { | |
Length uint32 | |
Type uint8 | |
Flags uint8 | |
StreamID uint32 | |
} | |
func newHTTP2FrameHeader(length uint32, typ, flgs uint8, sid uint32) *HTTP2FrameHeader { | |
return &HTTP2FrameHeader{ | |
Length: length, | |
Type: typ, | |
Flags: flgs, | |
StreamID: sid, | |
} | |
} | |
func (h *HTTP2FrameHeader) bytes() []byte { | |
var b bytes.Buffer | |
// write length + type as 32bit | |
var first = h.Length<<8 + uint32(h.Type) | |
write(&b, &first) | |
// write flags | |
write(&b, &h.Flags) | |
// write stream id | |
write(&b, &h.StreamID) | |
return b.Bytes() | |
} | |
func (h *HTTP2FrameHeader) readFrom(r io.Reader) { | |
read(r, &h.Length) | |
h.Type = uint8(h.Length & 0xFF) | |
h.Length = h.Length >> 8 | |
read(r, &h.Flags) | |
read(r, &h.StreamID) | |
} | |
func (h *HTTP2FrameHeader) writeTo(w io.Writer) { | |
// slower, allocation happens. | |
// but we want to print bytes to stdout | |
write(w, h.bytes()) | |
} | |
func sendSettingFrame(w io.Writer) *HTTP2FrameHeader { | |
header := newHTTP2FrameHeader(0, 0x4, 0, 0) | |
header.writeTo(w) | |
fmt.Println("<FrameHeader>") | |
fmt.Print(hex.Dump(header.bytes())) | |
return header | |
} | |
func recvSettingFrame(r io.Reader) *HTTP2FrameHeader { | |
var header HTTP2FrameHeader | |
header.readFrom(r) | |
fmt.Println("<FrameHeader>") | |
fmt.Print(hex.Dump(header.bytes())) | |
if header.Length > 0 { | |
data := make([]byte, header.Length) | |
read(r, data) | |
fmt.Println("<Payload>") | |
fmt.Print(hex.Dump(data)) | |
} | |
return &header | |
} | |
func sendAckSettingFrame(w io.Writer) *HTTP2FrameHeader { | |
header := newHTTP2FrameHeader(0, 0x4, 0x1, 0) | |
header.writeTo(w) | |
fmt.Println("<FrameHeader>") | |
fmt.Print(hex.Dump(header.bytes())) | |
return header | |
} | |
func sendHeadersFrame(w io.Writer, headers ...Pair) *HTTP2FrameHeader { | |
payload := encodedHeaders(headers...) | |
header := newHTTP2FrameHeader(uint32(len(payload)), 0x1, 0x5, 0x1) | |
header.writeTo(w) | |
fmt.Println("<FrameHeader>") | |
fmt.Print(hex.Dump(header.bytes())) | |
write(w, payload) | |
fmt.Println("<Payload>") | |
fmt.Print(hex.Dump(payload)) | |
return header | |
} | |
func recvHeadersFrame(r io.Reader) *HTTP2FrameHeader { | |
var header HTTP2FrameHeader | |
header.readFrom(r) | |
fmt.Println("<FrameHeader>") | |
fmt.Print(hex.Dump(header.bytes())) | |
if header.Length > 0 { | |
data := make([]byte, header.Length) | |
read(r, data) | |
fmt.Println("<Payload>") | |
fmt.Print(hex.Dump(data)) | |
} | |
return &header | |
} | |
func recvDataFrame(r io.Reader) *HTTP2FrameHeader { | |
var header HTTP2FrameHeader | |
header.readFrom(r) | |
fmt.Println("<FrameHeader>") | |
fmt.Print(hex.Dump(header.bytes())) | |
if header.Length > 0 { | |
data := make([]byte, header.Length) | |
read(r, data) | |
fmt.Println("<Payload>") | |
fmt.Print(hex.Dump(data)) | |
} | |
return &header | |
} | |
func sendGoawayFrame(w io.Writer) *HTTP2FrameHeader { | |
header := newHTTP2FrameHeader(0x8, 0x7, 0x1, 0) | |
header.writeTo(w) | |
fmt.Println("<FrameHeader>") | |
fmt.Print(hex.Dump(header.bytes())) | |
var b bytes.Buffer | |
write(&b, uint32(1)) // Last-Stream-ID = 1 | |
write(&b, uint32(0)) // Error Code = 0 | |
data := b.Bytes() | |
write(w, data) | |
fmt.Println("<Payload>") | |
fmt.Print(hex.Dump(data)) | |
return header | |
} | |
type Pair struct { | |
Name string | |
Value string | |
} | |
func encodedHeaders(headers ...Pair) []byte { | |
var b bytes.Buffer | |
for _, p := range headers { | |
write(&b, uint8(0)) | |
length := uint8(len(p.Name)) & 0x7f | |
write(&b, &length) | |
write(&b, []byte(p.Name)) | |
length = uint8(len(p.Value)) & 0x7f | |
write(&b, &length) | |
write(&b, []byte(p.Value)) | |
} | |
return b.Bytes() | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ozaki@mbp-2 ( ꒪⌓꒪) $ ./go-mini-http2 http://localhost:8888/ | |
-- Connect to Server : addr -> localhost:8888 | |
-- SEND Connection Preface | |
-- SEND empty SETTINGS frame | |
<FrameHeader> | |
00000000 00 00 00 04 00 00 00 00 00 |.........| | |
-- RECV SETTINGS frame and ACK | |
<FrameHeader> | |
00000000 00 00 06 04 00 00 00 00 00 |.........| | |
<Payload> | |
00000000 00 03 00 00 00 64 |.....d| | |
-- Send ACK corresponding to SETTINGS from server | |
<FrameHeader> | |
00000000 00 00 00 04 01 00 00 00 00 |.........| | |
-- RECV SETTINGS frame and ACK | |
<FrameHeader> | |
00000000 00 00 00 04 01 00 00 00 00 |.........| | |
-- SEND HEADERS frame as request headers | |
<FrameHeader> | |
00000000 00 00 3f 01 05 00 00 00 01 |..?......| | |
<Payload> | |
00000000 00 07 3a 6d 65 74 68 6f 64 03 47 45 54 00 07 3a |..:method.GET..:| | |
00000010 73 63 68 65 6d 65 04 68 74 74 70 00 0a 3a 61 75 |scheme.http..:au| | |
00000020 74 68 6f 72 69 74 79 0e 6c 6f 63 61 6c 68 6f 73 |thority.localhos| | |
00000030 74 3a 38 38 38 38 00 05 3a 70 61 74 68 01 2f |t:8888..:path./| | |
-- RECV HEADERS frames as response headers | |
<FrameHeader> | |
00000000 00 00 3e 01 04 00 00 00 01 |..>......| | |
<Payload> | |
00000000 8d 76 8f aa 69 d2 9a e4 52 a9 a7 4a 6b 13 00 5d |.v..i...R..Jk..]| | |
00000010 c5 dc 61 96 e4 59 3e 94 0b aa 5f 29 14 10 02 d2 |..a..Y>..._)....| | |
00000020 80 7e e0 83 70 00 53 16 8d ff 5f 92 49 7c a5 89 |.~..p.S..._.I|..| | |
00000030 d3 4d 1f 6a 12 71 d8 82 a6 0e 1b f0 ac f7 |.M.j.q........| | |
-- RECV DATA frame as response body | |
<FrameHeader> | |
00000000 00 00 7e 00 00 00 00 00 01 |..~......| | |
<Payload> | |
00000000 3c 68 74 6d 6c 3e 3c 68 65 61 64 3e 3c 74 69 74 |<html><head><tit| | |
00000010 6c 65 3e 34 30 34 3c 2f 74 69 74 6c 65 3e 3c 2f |le>404</title></| | |
00000020 68 65 61 64 3e 3c 62 6f 64 79 3e 3c 68 31 3e 34 |head><body><h1>4| | |
00000030 30 34 3c 2f 68 31 3e 3c 68 72 3e 3c 61 64 64 72 |04</h1><hr><addr| | |
00000040 65 73 73 3e 6e 67 68 74 74 70 64 20 6e 67 68 74 |ess>nghttpd nght| | |
00000050 74 70 32 2f 30 2e 36 2e 36 20 61 74 20 70 6f 72 |tp2/0.6.6 at por| | |
00000060 74 20 38 38 38 38 3c 2f 61 64 64 72 65 73 73 3e |t 8888</address>| | |
00000070 3c 2f 62 6f 64 79 3e 3c 2f 68 74 6d 6c 3e |</body></html>| | |
<FrameHeader> | |
00000000 00 00 00 00 01 00 00 00 01 |.........| | |
-- SEND GOAWAY frame | |
<FrameHeader> | |
00000000 00 00 08 07 01 00 00 00 00 |.........| | |
<Payload> | |
00000000 00 00 00 01 00 00 00 00 |........| |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment