Skip to content

Commit

Permalink
support h2c with prior knowledge
Browse files Browse the repository at this point in the history
  • Loading branch information
taoso committed Jun 20, 2018
1 parent caf3e35 commit feac8a6
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 0 deletions.
50 changes: 50 additions & 0 deletions gin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
package gin

import (
"fmt"
"html/template"
"io"
"net"
"net/http"
"os"
"strings"
"sync"

"github.com/gin-gonic/gin/render"
"golang.org/x/net/http2"
)

const (
Expand Down Expand Up @@ -103,6 +107,7 @@ type Engine struct {
noMethod HandlersChain
pool sync.Pool
trees methodTrees
h2Server *http2.Server
}

var _ IRouter = &Engine{}
Expand Down Expand Up @@ -282,6 +287,9 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()

// TODO support h2Server configuration
engine.h2Server = &http2.Server{}

address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine)
Expand Down Expand Up @@ -318,6 +326,10 @@ func (engine *Engine) RunUnix(file string) (err error) {

// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if engine.tryHandleH2C(w, req) {
return
}

c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
Expand All @@ -328,6 +340,44 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
engine.pool.Put(c)
}

func (engine *Engine) tryHandleH2C(w http.ResponseWriter, req *http.Request) bool {
if req.Method != "PRI" || req.URL.Path != "*" || req.ProtoMajor < 2 {
return false
}

hijacker, ok := w.(http.Hijacker)
if !ok {
return false
}

conn, rw, err := hijacker.Hijack()
if err != nil {
return false
}

const expectedBody = "SM\r\n\r\n"
buf := make([]byte, len(expectedBody))
n, err := io.ReadFull(rw, buf)

if err != nil {
panic(fmt.Sprintf("Error h2c with prior knowledge: %v", err))
}

if string(buf[:n]) != expectedBody {
return false
}

conn2 := &rwConn{
Conn: conn,
Reader: io.MultiReader(strings.NewReader(http2.ClientPreface), rw),
BufWriter: rw.Writer,
}

engine.h2Server.ServeConn(conn2, &http2.ServeConnOpts{Handler: engine})

return true
}

// HandleContext re-enter a context that has been rewritten.
// This can be done by setting c.Request.URL.Path to your new target.
// Disclaimer: You can loop yourself to death with this, use wisely.
Expand Down
25 changes: 25 additions & 0 deletions gin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import (
"fmt"
"html/template"
"io/ioutil"
"net"
"net/http"
"reflect"
"testing"
"time"

"github.com/stretchr/testify/assert"
"golang.org/x/net/http2"
)

func formatAsDate(t time.Time) string {
Expand Down Expand Up @@ -93,6 +95,29 @@ func TestLoadHTMLGlob(t *testing.T) {
td()
}

func TestLoadHTMLGlobOverH2c(t *testing.T) {
td := setupHTMLGlob(t, DebugMode, false)

http := http.Client{
Transport: &http2.Transport{
AllowHTTP: true,
DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) {
return net.Dial(netw, addr)
},
},
}

res, err := http.Get("http://127.0.0.1:8888/test")
if err != nil {
fmt.Println(err)
}

resp, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))

td()
}

func TestLoadHTMLGlob2(t *testing.T) {
td := setupHTMLGlob(t, TestMode, false)
res, err := http.Get("http://127.0.0.1:8888/test")
Expand Down
37 changes: 37 additions & 0 deletions h2c.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.

package gin

import (
"io"
"net"
)

type bufWriter interface {
io.Writer
Flush() error
}

// rwConn implements net.Conn but overrides Read and Write so that reads and
// writes are forwarded to the provided io.Reader and bufWriter.
type rwConn struct {
net.Conn
io.Reader
BufWriter bufWriter
}

// Read forwards reads to the underlying Reader.
func (c *rwConn) Read(p []byte) (int, error) {
return c.Reader.Read(p)
}

// Write forwards writes to the underlying bufWriter and immediately flushes.
func (c *rwConn) Write(p []byte) (int, error) {
n, err := c.BufWriter.Write(p)
if err := c.BufWriter.Flush(); err != nil {
return 0, err
}
return n, err
}
48 changes: 48 additions & 0 deletions vendor/vendor.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,30 @@
"revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a",
"revisionTime": "2016-10-18T08:54:36Z"
},
{
"checksumSHA1": "pCY4YtdNKVBYRbNvODjx8hj0hIs=",
"path": "golang.org/x/net/http/httpguts",
"revision": "db08ff08e8622530d9ed3a0e8ac279f6d4c02196",
"revisionTime": "2018-06-11T16:35:41Z"
},
{
"checksumSHA1": "NNo0AcF8P5+b/140t0Wox/HFkkw=",
"path": "golang.org/x/net/http2",
"revision": "db08ff08e8622530d9ed3a0e8ac279f6d4c02196",
"revisionTime": "2018-06-11T16:35:41Z"
},
{
"checksumSHA1": "leSW9aM30mATlWs/eeqhQQh/3eo=",
"path": "golang.org/x/net/http2/hpack",
"revision": "db08ff08e8622530d9ed3a0e8ac279f6d4c02196",
"revisionTime": "2018-06-11T16:35:41Z"
},
{
"checksumSHA1": "RcrB7tgYS/GMW4QrwVdMOTNqIU8=",
"path": "golang.org/x/net/idna",
"revision": "db08ff08e8622530d9ed3a0e8ac279f6d4c02196",
"revisionTime": "2018-06-11T16:35:41Z"
},
{
"checksumSHA1": "S0DP7Pn7sZUmXc55IzZnNvERu6s=",
"path": "golang.org/x/sync/errgroup",
Expand All @@ -114,6 +138,30 @@
"revision": "99f16d856c9836c42d24e7ab64ea72916925fa97",
"revisionTime": "2017-03-08T15:04:45Z"
},
{
"checksumSHA1": "CbpjEkkOeh0fdM/V8xKDdI0AA88=",
"path": "golang.org/x/text/secure/bidirule",
"revision": "5cec4b58c438bd98288aeb248bab2c1840713d21",
"revisionTime": "2018-05-20T16:02:21Z"
},
{
"checksumSHA1": "ziMb9+ANGRJSSIuxYdRbA+cDRBQ=",
"path": "golang.org/x/text/transform",
"revision": "5cec4b58c438bd98288aeb248bab2c1840713d21",
"revisionTime": "2018-05-20T16:02:21Z"
},
{
"checksumSHA1": "1oQpUH9BjCWlqFPDahRH+UMlYy4=",
"path": "golang.org/x/text/unicode/bidi",
"revision": "5cec4b58c438bd98288aeb248bab2c1840713d21",
"revisionTime": "2018-05-20T16:02:21Z"
},
{
"checksumSHA1": "lN2xlA6Utu7tXy2iUoMF2+y9EUE=",
"path": "golang.org/x/text/unicode/norm",
"revision": "5cec4b58c438bd98288aeb248bab2c1840713d21",
"revisionTime": "2018-05-20T16:02:21Z"
},
{
"checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=",
"comment": "v8.18.1",
Expand Down

0 comments on commit feac8a6

Please sign in to comment.