Skip to content

Instantly share code, notes, and snippets.

@yuroyoro
Created December 17, 2014 09:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yuroyoro/117d3043bc9128584a55 to your computer and use it in GitHub Desktop.
Save yuroyoro/117d3043bc9128584a55 to your computer and use it in GitHub Desktop.
GoでHTTP/2 最速実装 v3
/*
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)
}
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()
}
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