|
|
|
@ -7,6 +7,7 @@ package nghttp2
|
|
|
|
|
import "C"
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"context"
|
|
|
|
|
"crypto/tls"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
@ -14,6 +15,7 @@ import (
|
|
|
|
|
"net"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/url"
|
|
|
|
|
"runtime"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
@ -26,56 +28,104 @@ type Conn struct {
|
|
|
|
|
session *C.nghttp2_session
|
|
|
|
|
streams map[int]*stream
|
|
|
|
|
streamCount int
|
|
|
|
|
closed bool
|
|
|
|
|
isServer bool
|
|
|
|
|
running bool
|
|
|
|
|
handler http.Handler
|
|
|
|
|
lock *sync.Mutex
|
|
|
|
|
err error
|
|
|
|
|
errch chan error
|
|
|
|
|
exitch chan struct{}
|
|
|
|
|
ctx context.Context
|
|
|
|
|
cancel context.CancelFunc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Server create server side http2 connection
|
|
|
|
|
// Dial connect to addr and create a http2 client Conn
|
|
|
|
|
//
|
|
|
|
|
// the Conn.Run have already called, should not call it again
|
|
|
|
|
func Dial(network, addr string, cfg *tls.Config) (*Conn, error) {
|
|
|
|
|
nextProto := []string{"h2"}
|
|
|
|
|
if cfg == nil {
|
|
|
|
|
_addr := addr
|
|
|
|
|
h, _, err := net.SplitHostPort(addr)
|
|
|
|
|
if err == nil {
|
|
|
|
|
_addr = h
|
|
|
|
|
}
|
|
|
|
|
cfg = &tls.Config{ServerName: _addr}
|
|
|
|
|
}
|
|
|
|
|
cfg.NextProtos = nextProto
|
|
|
|
|
conn, err := tls.Dial(network, addr, cfg)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if err := conn.Handshake(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
state := conn.ConnectionState()
|
|
|
|
|
if state.NegotiatedProtocol != "h2" {
|
|
|
|
|
return nil, errors.New("server not support http2")
|
|
|
|
|
}
|
|
|
|
|
return Client(conn)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Server create server side http2 connection on c
|
|
|
|
|
//
|
|
|
|
|
// c must be TLS connection and negotiated for h2
|
|
|
|
|
//
|
|
|
|
|
// the Conn.Run not called, you must run it
|
|
|
|
|
func Server(c net.Conn, handler http.Handler) (*Conn, error) {
|
|
|
|
|
conn := &Conn{
|
|
|
|
|
conn: c,
|
|
|
|
|
handler: handler,
|
|
|
|
|
errch: make(chan error),
|
|
|
|
|
exitch: make(chan struct{}),
|
|
|
|
|
lock: new(sync.Mutex),
|
|
|
|
|
isServer: true,
|
|
|
|
|
streams: make(map[int]*stream),
|
|
|
|
|
}
|
|
|
|
|
conn.session = C.init_nghttp2_server_session(C.size_t(uintptr(unsafe.Pointer(conn))))
|
|
|
|
|
|
|
|
|
|
conn.ctx, conn.cancel = context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
|
|
//log.Printf("new conn %x", uintptr(unsafe.Pointer(conn)))
|
|
|
|
|
runtime.SetFinalizer(conn, (*Conn).free)
|
|
|
|
|
conn.session = C.init_nghttp2_server_session(
|
|
|
|
|
C.size_t(uintptr(unsafe.Pointer(conn))))
|
|
|
|
|
if conn.session == nil {
|
|
|
|
|
return nil, errors.New("init server session failed")
|
|
|
|
|
}
|
|
|
|
|
ret := C.send_connection_header(conn.session)
|
|
|
|
|
if int(ret) < 0 {
|
|
|
|
|
conn.Close()
|
|
|
|
|
return nil, fmt.Errorf("send settings error: %s", C.GoString(C.nghttp2_strerror(ret)))
|
|
|
|
|
return nil, fmt.Errorf("send settings error: %s",
|
|
|
|
|
C.GoString(C.nghttp2_strerror(ret)))
|
|
|
|
|
}
|
|
|
|
|
return conn, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Client create client side http2 connection
|
|
|
|
|
// Client create client side http2 connection on c
|
|
|
|
|
//
|
|
|
|
|
// c must be TLS connection and negotiated for h2
|
|
|
|
|
//
|
|
|
|
|
// the Conn.Run have alread called, you should not call it again
|
|
|
|
|
func Client(c net.Conn) (*Conn, error) {
|
|
|
|
|
conn := &Conn{
|
|
|
|
|
conn: c,
|
|
|
|
|
errch: make(chan error),
|
|
|
|
|
exitch: make(chan struct{}),
|
|
|
|
|
lock: new(sync.Mutex),
|
|
|
|
|
streams: make(map[int]*stream),
|
|
|
|
|
}
|
|
|
|
|
conn.session = C.init_nghttp2_client_session(C.size_t(uintptr(unsafe.Pointer(conn))))
|
|
|
|
|
|
|
|
|
|
conn.ctx, conn.cancel = context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
|
|
//log.Printf("new conn %x", uintptr(unsafe.Pointer(conn)))
|
|
|
|
|
runtime.SetFinalizer(conn, (*Conn).free)
|
|
|
|
|
conn.session = C.init_nghttp2_client_session(
|
|
|
|
|
C.size_t(uintptr(unsafe.Pointer(conn))))
|
|
|
|
|
if conn.session == nil {
|
|
|
|
|
return nil, errors.New("init server session failed")
|
|
|
|
|
}
|
|
|
|
|
ret := C.send_connection_header(conn.session)
|
|
|
|
|
if int(ret) < 0 {
|
|
|
|
|
conn.Close()
|
|
|
|
|
return nil, fmt.Errorf("send settings error: %s", C.GoString(C.nghttp2_strerror(ret)))
|
|
|
|
|
return nil, fmt.Errorf("send settings error: %s",
|
|
|
|
|
C.GoString(C.nghttp2_strerror(ret)))
|
|
|
|
|
}
|
|
|
|
|
go conn.Run()
|
|
|
|
|
return conn, nil
|
|
|
|
@ -102,6 +152,13 @@ func HTTP2Handler(srv *http.Server, conn *tls.Conn, handler http.Handler) {
|
|
|
|
|
h2conn.Run()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Conn) free() {
|
|
|
|
|
//log.Printf("free conn %x", uintptr(unsafe.Pointer(c)))
|
|
|
|
|
if !c.isClosed() {
|
|
|
|
|
c.Close()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Error return conn error
|
|
|
|
|
func (c *Conn) Error() error {
|
|
|
|
|
c.lock.Lock()
|
|
|
|
@ -136,7 +193,8 @@ func (c *Conn) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
|
|
|
nv = append(nv, newNV(":path", p))
|
|
|
|
|
for k, v := range req.Header {
|
|
|
|
|
_k := strings.ToLower(k)
|
|
|
|
|
if _k == "connection" || _k == "proxy-connection" || _k == "transfer-encoding" {
|
|
|
|
|
if _k == "connection" || _k == "proxy-connection" ||
|
|
|
|
|
_k == "transfer-encoding" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
nv = append(nv, newNV(k, v[0]))
|
|
|
|
@ -165,6 +223,9 @@ func (c *Conn) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
|
|
|
dp.Close()
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.request = req
|
|
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
case res := <-s.resch:
|
|
|
|
|
/*
|
|
|
|
@ -172,23 +233,25 @@ func (c *Conn) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
|
|
|
return nil, fmt.Errorf("http error code %d", res.StatusCode)
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
s.request = req
|
|
|
|
|
res.Request = s.request
|
|
|
|
|
return res, nil
|
|
|
|
|
case <-c.exitch:
|
|
|
|
|
case <-c.ctx.Done():
|
|
|
|
|
return nil, errors.New("connection closed")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Conn) submitRequest(nv []C.nghttp2_nv, cdp *C.nghttp2_data_provider) (*stream, error) {
|
|
|
|
|
func (c *Conn) submitRequest(nv []C.nghttp2_nv,
|
|
|
|
|
cdp *C.nghttp2_data_provider) (*stream, error) {
|
|
|
|
|
|
|
|
|
|
c.lock.Lock()
|
|
|
|
|
ret := C._nghttp2_submit_request(c.session, nil,
|
|
|
|
|
C.size_t(uintptr(unsafe.Pointer(&nv[0]))), C.size_t(len(nv)), cdp, nil)
|
|
|
|
|
C.size_t(uintptr(unsafe.Pointer(&nv[0]))),
|
|
|
|
|
C.size_t(len(nv)), cdp, nil)
|
|
|
|
|
c.lock.Unlock()
|
|
|
|
|
|
|
|
|
|
if int(ret) < 0 {
|
|
|
|
|
return nil, fmt.Errorf("submit request error: %s", C.GoString(C.nghttp2_strerror(ret)))
|
|
|
|
|
return nil, fmt.Errorf("submit request error: %s",
|
|
|
|
|
C.GoString(C.nghttp2_strerror(ret)))
|
|
|
|
|
}
|
|
|
|
|
streamID := int(ret)
|
|
|
|
|
s := &stream{
|
|
|
|
@ -200,14 +263,22 @@ func (c *Conn) submitRequest(nv []C.nghttp2_nv, cdp *C.nghttp2_data_provider) (*
|
|
|
|
|
},
|
|
|
|
|
resch: make(chan *http.Response),
|
|
|
|
|
}
|
|
|
|
|
s.ctx, s.cancel = context.WithCancel(context.Background())
|
|
|
|
|
if cdp != nil {
|
|
|
|
|
s.cdp = *cdp
|
|
|
|
|
}
|
|
|
|
|
runtime.SetFinalizer(s, (*stream).free)
|
|
|
|
|
return s, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Connect submit connect request
|
|
|
|
|
func (c *Conn) Connect(addr string) (net.Conn, int, error) {
|
|
|
|
|
//
|
|
|
|
|
// like "CONNECT host:port" on http/1.1
|
|
|
|
|
//
|
|
|
|
|
// statusCode is the http status code the server returned
|
|
|
|
|
//
|
|
|
|
|
// c bounds to the remote host of addr
|
|
|
|
|
func (c *Conn) Connect(addr string) (conn net.Conn, statusCode int, err error) {
|
|
|
|
|
nv := []C.nghttp2_nv{}
|
|
|
|
|
|
|
|
|
|
nv = append(nv, newNV(":method", "CONNECT"))
|
|
|
|
@ -232,7 +303,8 @@ func (c *Conn) Connect(addr string) (net.Conn, int, error) {
|
|
|
|
|
select {
|
|
|
|
|
case res := <-s.resch:
|
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
|
return nil, res.StatusCode, fmt.Errorf("http error code %d", res.StatusCode)
|
|
|
|
|
return nil, res.StatusCode, fmt.Errorf(
|
|
|
|
|
"http error code %d", res.StatusCode)
|
|
|
|
|
}
|
|
|
|
|
s.request = &http.Request{
|
|
|
|
|
Method: "CONNECT",
|
|
|
|
@ -242,8 +314,9 @@ func (c *Conn) Connect(addr string) (net.Conn, int, error) {
|
|
|
|
|
}
|
|
|
|
|
res.Request = s.request
|
|
|
|
|
return s, res.StatusCode, nil
|
|
|
|
|
case <-c.exitch:
|
|
|
|
|
return nil, http.StatusServiceUnavailable, errors.New("connection closed")
|
|
|
|
|
case <-c.ctx.Done():
|
|
|
|
|
return nil, http.StatusServiceUnavailable,
|
|
|
|
|
errors.New("connection closed")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
@ -264,7 +337,7 @@ func (c *Conn) Run() {
|
|
|
|
|
case err := <-c.errch:
|
|
|
|
|
c.err = err
|
|
|
|
|
return
|
|
|
|
|
case <-c.exitch:
|
|
|
|
|
case <-c.ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -275,26 +348,40 @@ func (c *Conn) serve(s *stream) {
|
|
|
|
|
if handler == nil {
|
|
|
|
|
handler = http.DefaultServeMux
|
|
|
|
|
}
|
|
|
|
|
s.request.RemoteAddr = c.conn.RemoteAddr().String()
|
|
|
|
|
if s.request.URL == nil {
|
|
|
|
|
s.request.URL = &url.URL{}
|
|
|
|
|
}
|
|
|
|
|
handler.ServeHTTP(s, s.request)
|
|
|
|
|
s.Close()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close close the connection
|
|
|
|
|
func (c *Conn) Close() error {
|
|
|
|
|
if c.closed {
|
|
|
|
|
c.lock.Lock()
|
|
|
|
|
if c.isClosed() {
|
|
|
|
|
c.lock.Unlock()
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
c.closed = true
|
|
|
|
|
c.cancel()
|
|
|
|
|
c.lock.Unlock()
|
|
|
|
|
|
|
|
|
|
// stream.Close may require the conn.Lock
|
|
|
|
|
// so must not hold the lock here
|
|
|
|
|
for _, s := range c.streams {
|
|
|
|
|
s.Close()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.lock.Lock()
|
|
|
|
|
|
|
|
|
|
for n := range c.streams {
|
|
|
|
|
delete(c.streams, n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
C.nghttp2_session_terminate_session(c.session, 0)
|
|
|
|
|
C.nghttp2_session_del(c.session)
|
|
|
|
|
c.lock.Unlock()
|
|
|
|
|
|
|
|
|
|
close(c.exitch)
|
|
|
|
|
c.conn.Close()
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
@ -307,67 +394,51 @@ func (c *Conn) errorNotify(err error) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Conn) readloop() {
|
|
|
|
|
type data struct {
|
|
|
|
|
buf []byte
|
|
|
|
|
err error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ret C.ssize_t
|
|
|
|
|
var err error
|
|
|
|
|
var d data
|
|
|
|
|
|
|
|
|
|
datach := make(chan data)
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
d1 := data{}
|
|
|
|
|
var n int
|
|
|
|
|
var err1 error
|
|
|
|
|
for {
|
|
|
|
|
buf := make([]byte, 16*1024)
|
|
|
|
|
n, err1 = c.conn.Read(buf)
|
|
|
|
|
d1.buf = buf[:n]
|
|
|
|
|
d1.err = err1
|
|
|
|
|
datach <- d1
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-c.exitch:
|
|
|
|
|
if c.isClosed() {
|
|
|
|
|
return
|
|
|
|
|
case d = <-datach:
|
|
|
|
|
if d.err != nil {
|
|
|
|
|
c.errorNotify(d.err)
|
|
|
|
|
}
|
|
|
|
|
n, err := c.conn.Read(buf)
|
|
|
|
|
if err != nil {
|
|
|
|
|
c.errorNotify(err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.lock.Lock()
|
|
|
|
|
ret = C.nghttp2_session_mem_recv(c.session,
|
|
|
|
|
(*C.uchar)(unsafe.Pointer(&d.buf[0])), C.size_t(len(d.buf)))
|
|
|
|
|
// check again
|
|
|
|
|
if c.isClosed() {
|
|
|
|
|
c.lock.Unlock()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
ret := C.nghttp2_session_mem_recv(c.session,
|
|
|
|
|
(*C.uchar)(unsafe.Pointer(&buf[0])), C.size_t(n))
|
|
|
|
|
c.lock.Unlock()
|
|
|
|
|
if int(ret) < 0 {
|
|
|
|
|
err = fmt.Errorf("http2 recv error: %s", C.GoString(C.nghttp2_strerror(C.int(ret))))
|
|
|
|
|
err = fmt.Errorf("http2 recv error: %s",
|
|
|
|
|
C.GoString(C.nghttp2_strerror(C.int(ret))))
|
|
|
|
|
c.errorNotify(err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Conn) writeloop() {
|
|
|
|
|
var ret C.int
|
|
|
|
|
var err error
|
|
|
|
|
var delay = 50 * time.Millisecond
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-c.exitch:
|
|
|
|
|
c.lock.Lock()
|
|
|
|
|
if c.isClosed() {
|
|
|
|
|
c.lock.Unlock()
|
|
|
|
|
return
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
c.lock.Lock()
|
|
|
|
|
ret = C.nghttp2_session_send(c.session)
|
|
|
|
|
c.lock.Unlock()
|
|
|
|
|
if int(ret) < 0 {
|
|
|
|
|
err = fmt.Errorf("http2 send error: %s", C.GoString(C.nghttp2_strerror(C.int(ret))))
|
|
|
|
|
err = fmt.Errorf("http2 send error: %s",
|
|
|
|
|
C.GoString(C.nghttp2_strerror(C.int(ret))))
|
|
|
|
|
c.errorNotify(err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
@ -380,3 +451,12 @@ func (c *Conn) writeloop() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Conn) isClosed() bool {
|
|
|
|
|
select {
|
|
|
|
|
case <-c.ctx.Done():
|
|
|
|
|
return true
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|