diff --git a/gin.go b/gin.go
index b91fc2fa57..29b3e051f3 100644
--- a/gin.go
+++ b/gin.go
@@ -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 (
@@ -103,6 +107,7 @@ type Engine struct {
noMethod HandlersChain
pool sync.Pool
trees methodTrees
+ h2Server *http2.Server
}
var _ IRouter = &Engine{}
@@ -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)
@@ -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
@@ -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.
diff --git a/gin_test.go b/gin_test.go
index 3ac60577ea..9bb6be2e34 100644
--- a/gin_test.go
+++ b/gin_test.go
@@ -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 {
@@ -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, "
Hello world
", string(resp[:]))
+
+ td()
+}
+
func TestLoadHTMLGlob2(t *testing.T) {
td := setupHTMLGlob(t, TestMode, false)
res, err := http.Get("http://127.0.0.1:8888/test")
diff --git a/h2c.go b/h2c.go
new file mode 100644
index 0000000000..f9fe1dd8df
--- /dev/null
+++ b/h2c.go
@@ -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
+}
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 5ace49df56..01888ca28d 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -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",
@@ -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",