Compare commits
No commits in common. 'master' and 'merge_conn' have entirely different histories.
master
...
merge_conn
@ -1,338 +1,319 @@
|
|||||||
package nghttp2
|
package nghttp2
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#include "_nghttp2.h"
|
#include "_nghttp2.h"
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"crypto/tls"
|
||||||
"crypto/tls"
|
"errors"
|
||||||
"errors"
|
"io"
|
||||||
"io"
|
"net/http"
|
||||||
"net/http"
|
"net/url"
|
||||||
"net/url"
|
"strconv"
|
||||||
"runtime"
|
"strings"
|
||||||
"strconv"
|
"sync"
|
||||||
"strings"
|
"unsafe"
|
||||||
"sync"
|
)
|
||||||
"unsafe"
|
|
||||||
)
|
var (
|
||||||
|
errAgain = errors.New("again")
|
||||||
var (
|
)
|
||||||
errAgain = errors.New("again")
|
|
||||||
)
|
const (
|
||||||
|
NGHTTP2_NO_ERROR = 0
|
||||||
const (
|
NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE = -521
|
||||||
NGHTTP2_NO_ERROR = 0
|
NGHTTP2_ERR_CALLBACK_FAILURE = -902
|
||||||
NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE = -521
|
NGHTTP2_ERR_DEFERRED = -508
|
||||||
NGHTTP2_ERR_CALLBACK_FAILURE = -902
|
)
|
||||||
NGHTTP2_ERR_DEFERRED = -508
|
|
||||||
)
|
// onDataSourceReadCallback callback function for libnghttp2 library
|
||||||
|
// want read data from data provider source,
|
||||||
/*
|
// return NGHTTP2_ERR_DEFERRED will cause data frame defered,
|
||||||
var bufPool = &sync.Pool{
|
// application later call nghttp2_session_resume_data will re-quene the data frame
|
||||||
New: func() interface{} {
|
//
|
||||||
return make([]byte, 16*1024)
|
//export onDataSourceReadCallback
|
||||||
},
|
func onDataSourceReadCallback(ptr unsafe.Pointer, streamID C.int,
|
||||||
}
|
buf unsafe.Pointer, length C.size_t) C.ssize_t {
|
||||||
*/
|
//log.Println("onDataSourceReadCallback begin")
|
||||||
|
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
||||||
// onDataSourceReadCallback callback function for libnghttp2 library
|
s, ok := conn.streams[int(streamID)]
|
||||||
// want read data from data provider source,
|
if !ok {
|
||||||
// return NGHTTP2_ERR_DEFERRED will cause data frame defered,
|
//log.Println("client dp callback, stream not exists")
|
||||||
// application later call nghttp2_session_resume_data will re-quene the data frame
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
||||||
//
|
}
|
||||||
//export onDataSourceReadCallback
|
gobuf := make([]byte, int(length))
|
||||||
func onDataSourceReadCallback(ptr unsafe.Pointer, streamID C.int,
|
n, err := s.dp.Read(gobuf)
|
||||||
buf unsafe.Pointer, length C.size_t) C.ssize_t {
|
if err != nil {
|
||||||
//log.Println("onDataSourceReadCallback begin")
|
if err == io.EOF {
|
||||||
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
//log.Println("onDataSourceReadCallback end")
|
||||||
s, ok := conn.streams[int(streamID)]
|
return 0
|
||||||
if !ok {
|
}
|
||||||
//log.Println("client dp callback, stream not exists")
|
if err == errAgain {
|
||||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
//log.Println("onDataSourceReadCallback end")
|
||||||
}
|
//s.dp.deferred = true
|
||||||
gobuf := make([]byte, int(length))
|
return NGHTTP2_ERR_DEFERRED
|
||||||
/*
|
}
|
||||||
_length := int(length)
|
//log.Println("onDataSourceReadCallback end")
|
||||||
gobuf := bufPool.Get().([]byte)
|
return NGHTTP2_ERR_CALLBACK_FAILURE
|
||||||
if len(gobuf) < _length {
|
}
|
||||||
gobuf = make([]byte, _length)
|
//cbuf := C.CBytes(gobuf)
|
||||||
}
|
//defer C.free(cbuf)
|
||||||
defer bufPool.Put(gobuf)
|
//C.memcpy(buf, cbuf, C.size_t(n))
|
||||||
*/
|
C.memcpy(buf, unsafe.Pointer(&gobuf[0]), C.size_t(n))
|
||||||
|
//log.Println("onDataSourceReadCallback end")
|
||||||
n, err := s.dp.Read(gobuf[0:])
|
return C.ssize_t(n)
|
||||||
if err != nil {
|
}
|
||||||
if err == io.EOF {
|
|
||||||
//log.Println("onDataSourceReadCallback end")
|
// onDataChunkRecv callback function for libnghttp2 library data chunk received.
|
||||||
return 0
|
//
|
||||||
}
|
//export onDataChunkRecv
|
||||||
if err == errAgain {
|
func onDataChunkRecv(ptr unsafe.Pointer, streamID C.int,
|
||||||
//log.Println("onDataSourceReadCallback end")
|
buf unsafe.Pointer, length C.size_t) C.int {
|
||||||
//s.dp.deferred = true
|
//log.Println("onDataChunkRecv begin")
|
||||||
return NGHTTP2_ERR_DEFERRED
|
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
||||||
}
|
gobuf := C.GoBytes(buf, C.int(length))
|
||||||
//log.Println("onDataSourceReadCallback end")
|
|
||||||
return NGHTTP2_ERR_CALLBACK_FAILURE
|
s, ok := conn.streams[int(streamID)]
|
||||||
}
|
if !ok {
|
||||||
//cbuf := C.CBytes(gobuf)
|
//log.Println("onDataChunkRecv end")
|
||||||
//defer C.free(cbuf)
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
||||||
//C.memcpy(buf, cbuf, C.size_t(n))
|
}
|
||||||
C.memcpy(buf, unsafe.Pointer(&gobuf[0]), C.size_t(n))
|
if s.bp == nil {
|
||||||
//log.Println("onDataSourceReadCallback end")
|
//log.Println("empty body")
|
||||||
return C.ssize_t(n)
|
//log.Println("onDataChunkRecv end")
|
||||||
}
|
return C.int(length)
|
||||||
|
}
|
||||||
// onDataChunkRecv callback function for libnghttp2 library data chunk received.
|
//log.Println("bp write")
|
||||||
//
|
n, err := s.bp.Write(gobuf)
|
||||||
//export onDataChunkRecv
|
if err != nil {
|
||||||
func onDataChunkRecv(ptr unsafe.Pointer, streamID C.int,
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
||||||
buf unsafe.Pointer, length C.size_t) C.int {
|
}
|
||||||
//log.Println("onDataChunkRecv begin")
|
//log.Println("onDataChunkRecv end")
|
||||||
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
return C.int(n)
|
||||||
gobuf := C.GoBytes(buf, C.int(length))
|
}
|
||||||
|
|
||||||
s, ok := conn.streams[int(streamID)]
|
// onDataSendCallback callback function for libnghttp2 library want send data to network.
|
||||||
if !ok {
|
//
|
||||||
//log.Println("onDataChunkRecv end")
|
//export onDataSendCallback
|
||||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
func onDataSendCallback(ptr unsafe.Pointer, data unsafe.Pointer, size C.size_t) C.ssize_t {
|
||||||
}
|
//log.Println("onDataSendCallback begin")
|
||||||
if s.bp == nil {
|
//log.Println("data write req ", int(size))
|
||||||
//log.Println("empty body")
|
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
||||||
//log.Println("onDataChunkRecv end")
|
buf := C.GoBytes(data, C.int(size))
|
||||||
return C.int(length)
|
//log.Println(conn.conn.RemoteAddr())
|
||||||
}
|
n, err := conn.conn.Write(buf)
|
||||||
//log.Println("bp write")
|
if err != nil {
|
||||||
n, err := s.bp.Write(gobuf)
|
//log.Println("onDataSendCallback end")
|
||||||
if err != nil {
|
return NGHTTP2_ERR_CALLBACK_FAILURE
|
||||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
}
|
||||||
}
|
//log.Printf("write %d bytes to network ", n)
|
||||||
//log.Println("onDataChunkRecv end")
|
//log.Println("onDataSendCallback end")
|
||||||
return C.int(n)
|
return C.ssize_t(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// onDataSendCallback callback function for libnghttp2 library want send data to network.
|
// onBeginHeaderCallback callback function for begin header receive.
|
||||||
//
|
//
|
||||||
//export onDataSendCallback
|
//export onBeginHeaderCallback
|
||||||
func onDataSendCallback(ptr unsafe.Pointer, data unsafe.Pointer,
|
func onBeginHeaderCallback(ptr unsafe.Pointer, streamID C.int) C.int {
|
||||||
size C.size_t) C.ssize_t {
|
//log.Println("onBeginHeaderCallback begin")
|
||||||
//log.Println("onDataSendCallback begin")
|
//log.Printf("stream %d begin headers", int(streamID))
|
||||||
//log.Println("data write req ", int(size))
|
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
||||||
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
|
||||||
buf := C.GoBytes(data, C.int(size))
|
var TLS tls.ConnectionState
|
||||||
//log.Println(conn.conn.RemoteAddr())
|
if tlsconn, ok := conn.conn.(*tls.Conn); ok {
|
||||||
n, err := conn.conn.Write(buf)
|
TLS = tlsconn.ConnectionState()
|
||||||
if err != nil {
|
}
|
||||||
//log.Println("onDataSendCallback end")
|
// client
|
||||||
return NGHTTP2_ERR_CALLBACK_FAILURE
|
if !conn.isServer {
|
||||||
}
|
s, ok := conn.streams[int(streamID)]
|
||||||
//log.Printf("write %d bytes to network ", n)
|
if !ok {
|
||||||
//log.Println("onDataSendCallback end")
|
//log.Println("onBeginHeaderCallback end")
|
||||||
return C.ssize_t(n)
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
||||||
}
|
}
|
||||||
|
s.response = &http.Response{
|
||||||
// onBeginHeaderCallback callback function for begin header receive.
|
Proto: "HTTP/2",
|
||||||
//
|
ProtoMajor: 2,
|
||||||
//export onBeginHeaderCallback
|
ProtoMinor: 0,
|
||||||
func onBeginHeaderCallback(ptr unsafe.Pointer, streamID C.int) C.int {
|
Header: make(http.Header),
|
||||||
//log.Println("onBeginHeaderCallback begin")
|
Body: s.bp,
|
||||||
//log.Printf("stream %d begin headers", int(streamID))
|
TLS: &TLS,
|
||||||
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
}
|
||||||
|
return NGHTTP2_NO_ERROR
|
||||||
var TLS tls.ConnectionState
|
}
|
||||||
if tlsconn, ok := conn.conn.(*tls.Conn); ok {
|
|
||||||
TLS = tlsconn.ConnectionState()
|
// server
|
||||||
}
|
s := &stream{
|
||||||
// client
|
streamID: int(streamID),
|
||||||
if !conn.isServer {
|
conn: conn,
|
||||||
s, ok := conn.streams[int(streamID)]
|
bp: &bodyProvider{
|
||||||
if !ok {
|
buf: new(bytes.Buffer),
|
||||||
//log.Println("onBeginHeaderCallback end")
|
lock: new(sync.Mutex),
|
||||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
},
|
||||||
}
|
request: &http.Request{
|
||||||
s.response = &http.Response{
|
Header: make(http.Header),
|
||||||
Proto: "HTTP/2",
|
Proto: "HTTP/2",
|
||||||
ProtoMajor: 2,
|
ProtoMajor: 2,
|
||||||
ProtoMinor: 0,
|
ProtoMinor: 0,
|
||||||
Header: make(http.Header),
|
TLS: &TLS,
|
||||||
Body: s.bp,
|
},
|
||||||
TLS: &TLS,
|
}
|
||||||
}
|
s.request.Body = s.bp
|
||||||
return NGHTTP2_NO_ERROR
|
|
||||||
}
|
conn.streams[int(streamID)] = s
|
||||||
|
|
||||||
// server
|
//log.Println("onBeginHeaderCallback end")
|
||||||
s := &stream{
|
return NGHTTP2_NO_ERROR
|
||||||
streamID: int(streamID),
|
}
|
||||||
conn: conn,
|
|
||||||
bp: &bodyProvider{
|
// onHeaderCallback callback function for each header received.
|
||||||
buf: new(bytes.Buffer),
|
//
|
||||||
lock: new(sync.Mutex),
|
//export onHeaderCallback
|
||||||
},
|
func onHeaderCallback(ptr unsafe.Pointer, streamID C.int,
|
||||||
request: &http.Request{
|
name unsafe.Pointer, namelen C.int,
|
||||||
Header: make(http.Header),
|
value unsafe.Pointer, valuelen C.int) C.int {
|
||||||
Proto: "HTTP/2",
|
//log.Println("onHeaderCallback begin")
|
||||||
ProtoMajor: 2,
|
//log.Printf("header %d", int(streamID))
|
||||||
ProtoMinor: 0,
|
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
||||||
TLS: &TLS,
|
goname := string(C.GoBytes(name, namelen))
|
||||||
},
|
govalue := string(C.GoBytes(value, valuelen))
|
||||||
}
|
|
||||||
s.ctx, s.cancel = context.WithCancel(context.Background())
|
s, ok := conn.streams[int(streamID)]
|
||||||
s.request.Body = s.bp
|
if !ok {
|
||||||
//log.Printf("new stream %d", int(streamID))
|
//log.Println("onHeaderCallback end")
|
||||||
conn.streams[int(streamID)] = s
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
||||||
|
}
|
||||||
runtime.SetFinalizer(s, (*stream).free)
|
var header http.Header
|
||||||
|
if conn.isServer {
|
||||||
//log.Println("onBeginHeaderCallback end")
|
header = s.request.Header
|
||||||
return NGHTTP2_NO_ERROR
|
} else {
|
||||||
}
|
header = s.response.Header
|
||||||
|
}
|
||||||
// onHeaderCallback callback function for each header received.
|
goname = strings.ToLower(goname)
|
||||||
//
|
switch goname {
|
||||||
//export onHeaderCallback
|
case ":method":
|
||||||
func onHeaderCallback(ptr unsafe.Pointer, streamID C.int,
|
s.request.Method = govalue
|
||||||
name unsafe.Pointer, namelen C.int,
|
case ":scheme":
|
||||||
value unsafe.Pointer, valuelen C.int) C.int {
|
case ":authority":
|
||||||
//log.Println("onHeaderCallback begin")
|
s.request.Host = govalue
|
||||||
//log.Printf("header %d", int(streamID))
|
case ":path":
|
||||||
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
s.request.RequestURI = govalue
|
||||||
goname := string(C.GoBytes(name, namelen))
|
u, err := url.Parse(govalue)
|
||||||
govalue := string(C.GoBytes(value, valuelen))
|
if err != nil {
|
||||||
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
||||||
s, ok := conn.streams[int(streamID)]
|
}
|
||||||
if !ok {
|
s.request.URL = u
|
||||||
//log.Println("onHeaderCallback end")
|
case ":status":
|
||||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
if s.response == nil {
|
||||||
}
|
//log.Println("empty response")
|
||||||
var header http.Header
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
||||||
if conn.isServer {
|
}
|
||||||
header = s.request.Header
|
statusCode, _ := strconv.Atoi(govalue)
|
||||||
} else {
|
s.response.StatusCode = statusCode
|
||||||
header = s.response.Header
|
s.response.Status = http.StatusText(statusCode)
|
||||||
}
|
case "content-length":
|
||||||
goname = strings.ToLower(goname)
|
header.Add(goname, govalue)
|
||||||
switch goname {
|
n, err := strconv.ParseInt(govalue, 10, 64)
|
||||||
case ":method":
|
if err == nil {
|
||||||
s.request.Method = govalue
|
if conn.isServer {
|
||||||
case ":scheme":
|
s.request.ContentLength = n
|
||||||
case ":authority":
|
} else {
|
||||||
s.request.Host = govalue
|
s.response.ContentLength = n
|
||||||
case ":path":
|
}
|
||||||
s.request.RequestURI = govalue
|
}
|
||||||
u, err := url.Parse(govalue)
|
case "transfer-encoding":
|
||||||
if err != nil {
|
header.Add(goname, govalue)
|
||||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
if conn.isServer {
|
||||||
}
|
s.request.TransferEncoding = append(s.response.TransferEncoding, govalue)
|
||||||
s.request.URL = u
|
} else {
|
||||||
case ":status":
|
s.response.TransferEncoding = append(s.response.TransferEncoding, govalue)
|
||||||
if s.response == nil {
|
}
|
||||||
//log.Println("empty response")
|
default:
|
||||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
header.Add(goname, govalue)
|
||||||
}
|
}
|
||||||
statusCode, _ := strconv.Atoi(govalue)
|
//log.Println("onHeaderCallback end")
|
||||||
s.response.StatusCode = statusCode
|
return NGHTTP2_NO_ERROR
|
||||||
s.response.Status = http.StatusText(statusCode)
|
}
|
||||||
case "content-length":
|
|
||||||
header.Add(goname, govalue)
|
// onHeadersDoneCallback callback function for the stream when all headers received.
|
||||||
n, err := strconv.ParseInt(govalue, 10, 64)
|
//
|
||||||
if err == nil {
|
//export onHeadersDoneCallback
|
||||||
if conn.isServer {
|
func onHeadersDoneCallback(ptr unsafe.Pointer, streamID C.int) C.int {
|
||||||
s.request.ContentLength = n
|
//log.Println("onHeadersDoneCallback begin")
|
||||||
} else {
|
//log.Printf("stream %d headers done", int(streamID))
|
||||||
s.response.ContentLength = n
|
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
||||||
}
|
s, ok := conn.streams[int(streamID)]
|
||||||
}
|
if !ok {
|
||||||
case "transfer-encoding":
|
//log.Println("onHeadersDoneCallback end")
|
||||||
header.Add(goname, govalue)
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
||||||
if conn.isServer {
|
}
|
||||||
s.request.TransferEncoding = append(
|
s.headersEnd = true
|
||||||
s.response.TransferEncoding, govalue)
|
if conn.isServer {
|
||||||
} else {
|
if s.request.Method == "CONNECT" {
|
||||||
s.response.TransferEncoding = append(
|
go conn.serve(s)
|
||||||
s.response.TransferEncoding, govalue)
|
}
|
||||||
}
|
return NGHTTP2_NO_ERROR
|
||||||
default:
|
}
|
||||||
header.Add(goname, govalue)
|
select {
|
||||||
}
|
case s.resch <- s.response:
|
||||||
//log.Println("onHeaderCallback end")
|
default:
|
||||||
return NGHTTP2_NO_ERROR
|
}
|
||||||
}
|
//log.Println("onHeadersDoneCallback end")
|
||||||
|
return NGHTTP2_NO_ERROR
|
||||||
// onHeadersDoneCallback callback function for the stream when all headers received.
|
}
|
||||||
//
|
|
||||||
//export onHeadersDoneCallback
|
// onStreamClose callback function for the stream when closed.
|
||||||
func onHeadersDoneCallback(ptr unsafe.Pointer, streamID C.int) C.int {
|
//
|
||||||
//log.Println("onHeadersDoneCallback begin")
|
//export onStreamClose
|
||||||
//log.Printf("stream %d headers done", int(streamID))
|
func onStreamClose(ptr unsafe.Pointer, streamID C.int) C.int {
|
||||||
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
//log.Println("onStreamClose begin")
|
||||||
s, ok := conn.streams[int(streamID)]
|
//log.Printf("stream %d closed", int(streamID))
|
||||||
if !ok {
|
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
||||||
//log.Println("onHeadersDoneCallback end")
|
|
||||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
stream, ok := conn.streams[int(streamID)]
|
||||||
}
|
if ok {
|
||||||
s.headersEnd = true
|
go stream.Close()
|
||||||
if conn.isServer {
|
//conn.lock.Lock()
|
||||||
if s.request.Method == "CONNECT" {
|
delete(conn.streams, int(streamID))
|
||||||
go conn.serve(s)
|
//go stream.Close()
|
||||||
}
|
//conn.lock.Unlock()
|
||||||
return NGHTTP2_NO_ERROR
|
//log.Println("onStreamClose end")
|
||||||
}
|
return NGHTTP2_NO_ERROR
|
||||||
select {
|
}
|
||||||
case s.resch <- s.response:
|
//log.Println("onStreamClose end")
|
||||||
default:
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
||||||
}
|
}
|
||||||
//log.Println("onHeadersDoneCallback end")
|
|
||||||
return NGHTTP2_NO_ERROR
|
//export onConnectionCloseCallback
|
||||||
}
|
func onConnectionCloseCallback(ptr unsafe.Pointer) {
|
||||||
|
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
||||||
// onStreamClose callback function for the stream when closed.
|
conn.err = io.EOF
|
||||||
//
|
|
||||||
//export onStreamClose
|
// signal all goroutings exit
|
||||||
func onStreamClose(ptr unsafe.Pointer, streamID C.int) C.int {
|
for i := 0; i < 4; i++ {
|
||||||
//log.Println("onStreamClose begin")
|
select {
|
||||||
//log.Printf("stream %d closed", int(streamID))
|
case conn.exitch <- struct{}{}:
|
||||||
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
default:
|
||||||
|
}
|
||||||
stream, ok := conn.streams[int(streamID)]
|
}
|
||||||
if ok {
|
}
|
||||||
go stream.Close()
|
|
||||||
//log.Printf("remove stream %d", int(streamID))
|
//export onStreamEndCallback
|
||||||
//conn.lock.Lock()
|
func onStreamEndCallback(ptr unsafe.Pointer, streamID C.int) {
|
||||||
delete(conn.streams, int(streamID))
|
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
||||||
//go stream.Close()
|
stream, ok := conn.streams[int(streamID)]
|
||||||
//conn.lock.Unlock()
|
if !ok {
|
||||||
//log.Println("onStreamClose end")
|
return
|
||||||
return NGHTTP2_NO_ERROR
|
}
|
||||||
}
|
stream.streamEnd = true
|
||||||
//log.Println("onStreamClose end")
|
|
||||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
stream.bp.Close()
|
||||||
}
|
|
||||||
|
if stream.conn.isServer {
|
||||||
//export onConnectionCloseCallback
|
if stream.request.Method != "CONNECT" {
|
||||||
func onConnectionCloseCallback(ptr unsafe.Pointer) {
|
go conn.serve(stream)
|
||||||
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
}
|
||||||
conn.err = io.EOF
|
return
|
||||||
conn.Close()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onStreamEndCallback
|
|
||||||
func onStreamEndCallback(ptr unsafe.Pointer, streamID C.int) {
|
|
||||||
conn := (*Conn)(unsafe.Pointer(uintptr(ptr)))
|
|
||||||
stream, ok := conn.streams[int(streamID)]
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
stream.streamEnd = true
|
|
||||||
|
|
||||||
stream.bp.Close()
|
|
||||||
|
|
||||||
if stream.conn.isServer {
|
|
||||||
if stream.request.Method != "CONNECT" {
|
|
||||||
go conn.serve(stream)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
package nghttp2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Transport the nghttp2 RoundTripper implement
|
|
||||||
type Transport struct {
|
|
||||||
TLSConfig *tls.Config
|
|
||||||
DialTLS func(network, addr string, cfg *tls.Config) (*tls.Conn, error)
|
|
||||||
cacheConn map[string]*Conn
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoundTrip send req and get res
|
|
||||||
func (tr *Transport) RoundTrip(req *http.Request) (res *http.Response, err error) {
|
|
||||||
h2conn, err := tr.getConn(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return h2conn.RoundTrip(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *Transport) getConn(req *http.Request) (*Conn, error) {
|
|
||||||
tr.mu.Lock()
|
|
||||||
defer tr.mu.Unlock()
|
|
||||||
|
|
||||||
if tr.cacheConn == nil {
|
|
||||||
tr.cacheConn = map[string]*Conn{}
|
|
||||||
}
|
|
||||||
k := req.URL.Host
|
|
||||||
if c, ok := tr.cacheConn[k]; ok {
|
|
||||||
if c.CanTakeNewRequest() {
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
delete(tr.cacheConn, k)
|
|
||||||
c.Close()
|
|
||||||
}
|
|
||||||
c, err := tr.createConn(k)
|
|
||||||
if err == nil {
|
|
||||||
tr.cacheConn[k] = c
|
|
||||||
}
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *Transport) createConn(host string) (*Conn, error) {
|
|
||||||
dial := tls.Dial
|
|
||||||
if tr.DialTLS != nil {
|
|
||||||
dial = tr.DialTLS
|
|
||||||
}
|
|
||||||
cfg := tr.TLSConfig
|
|
||||||
if cfg == nil {
|
|
||||||
h, _, err := net.SplitHostPort(host)
|
|
||||||
if err != nil {
|
|
||||||
h = host
|
|
||||||
}
|
|
||||||
cfg = &tls.Config{
|
|
||||||
ServerName: h,
|
|
||||||
NextProtos: []string{"h2"},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !strings.Contains(host, ":") {
|
|
||||||
host = fmt.Sprintf("%s:443", host)
|
|
||||||
}
|
|
||||||
conn, err := dial("tcp", host, cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err = conn.Handshake(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
state := conn.ConnectionState()
|
|
||||||
if state.NegotiatedProtocol != "h2" {
|
|
||||||
conn.Close()
|
|
||||||
return nil, errors.New("http2 is not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
return Client(conn)
|
|
||||||
}
|
|
@ -1,189 +1,164 @@
|
|||||||
package nghttp2
|
package nghttp2
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#include "_nghttp2.h"
|
#include "_nghttp2.h"
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"log"
|
||||||
"log"
|
"sync"
|
||||||
"sync"
|
"time"
|
||||||
"time"
|
"unsafe"
|
||||||
"unsafe"
|
)
|
||||||
)
|
|
||||||
|
// dataProvider provider data for libnghttp2 library
|
||||||
// dataProvider provider data for libnghttp2 library
|
// libnghttp2 callback will Read to read the data,
|
||||||
// libnghttp2 callback will Read to read the data,
|
// application call Write to provider data,
|
||||||
// application call Write to provider data,
|
// application call Close will cause Read return io.EOF
|
||||||
// application call Close will cause Read return io.EOF
|
type dataProvider struct {
|
||||||
type dataProvider struct {
|
buf *bytes.Buffer
|
||||||
buf *bytes.Buffer
|
closed bool
|
||||||
closed bool
|
lock *sync.Mutex
|
||||||
lock *sync.Mutex
|
sessLock *sync.Mutex
|
||||||
sessLock *sync.Mutex
|
session *C.nghttp2_session
|
||||||
session *C.nghttp2_session
|
streamID int
|
||||||
streamID int
|
deferred bool
|
||||||
deferred bool
|
}
|
||||||
}
|
|
||||||
|
// Read read from data provider
|
||||||
// Read read from data provider
|
func (dp *dataProvider) Read(buf []byte) (n int, err error) {
|
||||||
func (dp *dataProvider) Read(buf []byte) (n int, err error) {
|
if dp.buf == nil || dp.lock == nil || dp.sessLock == nil || dp.session == nil {
|
||||||
if dp.buf == nil || dp.lock == nil ||
|
log.Println("dp read invalid state")
|
||||||
dp.sessLock == nil || dp.session == nil {
|
return 0, errors.New("invalid state")
|
||||||
log.Println("dp read invalid state")
|
}
|
||||||
return 0, errors.New("invalid state")
|
dp.lock.Lock()
|
||||||
}
|
defer dp.lock.Unlock()
|
||||||
dp.lock.Lock()
|
|
||||||
defer dp.lock.Unlock()
|
n, err = dp.buf.Read(buf)
|
||||||
|
if err != nil && !dp.closed {
|
||||||
n, err = dp.buf.Read(buf)
|
//log.Println("deferred")
|
||||||
if err != nil && !dp.closed {
|
dp.deferred = true
|
||||||
//log.Println("deferred")
|
return 0, errAgain
|
||||||
dp.deferred = true
|
}
|
||||||
return 0, errAgain
|
return
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
// Write provider data for data provider
|
||||||
|
func (dp *dataProvider) Write(buf []byte) (n int, err error) {
|
||||||
// Write provider data for data provider
|
if dp.buf == nil || dp.lock == nil || dp.sessLock == nil || dp.session == nil {
|
||||||
func (dp *dataProvider) Write(buf []byte) (n int, err error) {
|
log.Println("dp write invalid state")
|
||||||
if dp.buf == nil || dp.lock == nil ||
|
return 0, errors.New("invalid state")
|
||||||
dp.sessLock == nil || dp.session == nil {
|
}
|
||||||
log.Println("dp write invalid state")
|
dp.lock.Lock()
|
||||||
return 0, errors.New("invalid state")
|
defer dp.lock.Unlock()
|
||||||
}
|
|
||||||
|
//if dp.closed {
|
||||||
// make sure the buffer not too large
|
// return 0, io.EOF
|
||||||
delay := 10 * time.Millisecond
|
//}
|
||||||
maxBufSize := 4 * 1024
|
|
||||||
for {
|
n, err = dp.buf.Write(buf)
|
||||||
dp.lock.Lock()
|
if dp.deferred {
|
||||||
_len := dp.buf.Len()
|
dp.sessLock.Lock()
|
||||||
closed := dp.closed
|
C.nghttp2_session_resume_data(dp.session, C.int(dp.streamID))
|
||||||
dp.lock.Unlock()
|
dp.sessLock.Unlock()
|
||||||
if closed {
|
|
||||||
return 0, io.EOF
|
//log.Println("resume")
|
||||||
}
|
dp.deferred = false
|
||||||
if _len < maxBufSize {
|
}
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
time.Sleep(delay)
|
|
||||||
}
|
// Close end to provide data
|
||||||
|
func (dp *dataProvider) Close() error {
|
||||||
dp.lock.Lock()
|
if dp.buf == nil || dp.lock == nil || dp.sessLock == nil || dp.session == nil {
|
||||||
defer dp.lock.Unlock()
|
log.Println("dp close, invalid state")
|
||||||
|
return errors.New("invalid state")
|
||||||
//if dp.closed {
|
}
|
||||||
// return 0, io.EOF
|
dp.lock.Lock()
|
||||||
//}
|
defer dp.lock.Unlock()
|
||||||
|
|
||||||
n, err = dp.buf.Write(buf)
|
if dp.closed {
|
||||||
if dp.deferred {
|
return nil
|
||||||
dp.sessLock.Lock()
|
}
|
||||||
C.nghttp2_session_resume_data(
|
dp.closed = true
|
||||||
dp.session, C.int(dp.streamID))
|
//log.Printf("dp close stream %d", dp.streamID)
|
||||||
dp.sessLock.Unlock()
|
if dp.deferred {
|
||||||
|
dp.sessLock.Lock()
|
||||||
//log.Println("resume")
|
C.nghttp2_session_resume_data(dp.session, C.int(dp.streamID))
|
||||||
dp.deferred = false
|
dp.sessLock.Unlock()
|
||||||
}
|
|
||||||
return
|
dp.deferred = false
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
// Close end to provide data
|
}
|
||||||
func (dp *dataProvider) Close() error {
|
|
||||||
if dp.buf == nil || dp.lock == nil ||
|
func newDataProvider(cdp unsafe.Pointer, sessionLock *sync.Mutex, t int) *dataProvider {
|
||||||
dp.sessLock == nil || dp.session == nil {
|
dp := &dataProvider{
|
||||||
log.Println("dp close, invalid state")
|
buf: new(bytes.Buffer),
|
||||||
return errors.New("invalid state")
|
lock: new(sync.Mutex),
|
||||||
}
|
sessLock: sessionLock,
|
||||||
dp.lock.Lock()
|
}
|
||||||
defer dp.lock.Unlock()
|
C.data_provider_set_callback(C.size_t(uintptr(cdp)),
|
||||||
|
C.size_t(uintptr(unsafe.Pointer(dp))), C.int(t))
|
||||||
if dp.closed {
|
return dp
|
||||||
return nil
|
}
|
||||||
}
|
|
||||||
dp.closed = true
|
// bodyProvider provide data for http body
|
||||||
//log.Printf("dp close stream %d", dp.streamID)
|
// Read will block when data not yet avaliable
|
||||||
if dp.deferred {
|
type bodyProvider struct {
|
||||||
dp.sessLock.Lock()
|
buf *bytes.Buffer
|
||||||
C.nghttp2_session_resume_data(
|
closed bool
|
||||||
dp.session, C.int(dp.streamID))
|
lock *sync.Mutex
|
||||||
dp.sessLock.Unlock()
|
}
|
||||||
|
|
||||||
dp.deferred = false
|
// Read read data from provider
|
||||||
}
|
// will block when data not yet avaliable
|
||||||
return nil
|
func (bp *bodyProvider) Read(buf []byte) (int, error) {
|
||||||
}
|
var delay = 100 * time.Millisecond
|
||||||
|
|
||||||
func newDataProvider(cdp unsafe.Pointer,
|
for {
|
||||||
sessionLock *sync.Mutex, t int) *dataProvider {
|
bp.lock.Lock()
|
||||||
dp := &dataProvider{
|
n, err := bp.buf.Read(buf)
|
||||||
buf: new(bytes.Buffer),
|
bp.lock.Unlock()
|
||||||
lock: new(sync.Mutex),
|
if err != nil && !bp.closed {
|
||||||
sessLock: sessionLock,
|
time.Sleep(delay)
|
||||||
}
|
continue
|
||||||
C.data_provider_set_callback(C.size_t(uintptr(cdp)),
|
}
|
||||||
C.size_t(uintptr(unsafe.Pointer(dp))), C.int(t))
|
return n, err
|
||||||
return dp
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// bodyProvider provide data for http body
|
// Write provide data for dataProvider
|
||||||
// Read will block when data not yet avaliable
|
// libnghttp2 data chunk recv callback will call this
|
||||||
type bodyProvider struct {
|
func (bp *bodyProvider) Write(buf []byte) (int, error) {
|
||||||
buf *bytes.Buffer
|
bp.lock.Lock()
|
||||||
closed bool
|
defer bp.lock.Unlock()
|
||||||
lock *sync.Mutex
|
|
||||||
}
|
return bp.buf.Write(buf)
|
||||||
|
}
|
||||||
// Read read data from provider
|
|
||||||
// will block when data not yet avaliable
|
// Close end to provide data
|
||||||
func (bp *bodyProvider) Read(buf []byte) (int, error) {
|
func (bp *bodyProvider) Close() error {
|
||||||
var delay = 100 * time.Millisecond
|
bp.lock.Lock()
|
||||||
|
defer bp.lock.Unlock()
|
||||||
for {
|
|
||||||
bp.lock.Lock()
|
bp.closed = true
|
||||||
n, err := bp.buf.Read(buf)
|
return nil
|
||||||
bp.lock.Unlock()
|
}
|
||||||
if err != nil && !bp.closed {
|
|
||||||
time.Sleep(delay)
|
func newNV(name, value string) C.nghttp2_nv {
|
||||||
continue
|
nv := C.nghttp2_nv{}
|
||||||
}
|
nameArr := make([]byte, len(name)+1)
|
||||||
return n, err
|
valueArr := make([]byte, len(value)+1)
|
||||||
}
|
copy(nameArr, []byte(name))
|
||||||
}
|
copy(valueArr, []byte(value))
|
||||||
|
|
||||||
// Write provide data for dataProvider
|
nv.name = (*C.uchar)(unsafe.Pointer(&nameArr[0]))
|
||||||
// libnghttp2 data chunk recv callback will call this
|
nv.value = (*C.uchar)(unsafe.Pointer(&valueArr[0]))
|
||||||
func (bp *bodyProvider) Write(buf []byte) (int, error) {
|
nv.namelen = C.size_t(len(name))
|
||||||
bp.lock.Lock()
|
nv.valuelen = C.size_t(len(value))
|
||||||
defer bp.lock.Unlock()
|
nv.flags = 0
|
||||||
|
return nv
|
||||||
return bp.buf.Write(buf)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Close end to provide data
|
|
||||||
func (bp *bodyProvider) Close() error {
|
|
||||||
bp.lock.Lock()
|
|
||||||
defer bp.lock.Unlock()
|
|
||||||
|
|
||||||
bp.closed = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newNV(name, value string) C.nghttp2_nv {
|
|
||||||
nv := C.nghttp2_nv{}
|
|
||||||
nameArr := make([]byte, len(name)+1)
|
|
||||||
valueArr := make([]byte, len(value)+1)
|
|
||||||
copy(nameArr, []byte(name))
|
|
||||||
copy(valueArr, []byte(value))
|
|
||||||
|
|
||||||
nv.name = (*C.uchar)(unsafe.Pointer(&nameArr[0]))
|
|
||||||
nv.value = (*C.uchar)(unsafe.Pointer(&valueArr[0]))
|
|
||||||
nv.namelen = C.size_t(len(name))
|
|
||||||
nv.valuelen = C.size_t(len(value))
|
|
||||||
nv.flags = 0
|
|
||||||
return nv
|
|
||||||
}
|
|
||||||
|
@ -1,113 +1,113 @@
|
|||||||
/*Package nghttp2 is libnghttp2 binding for golang.
|
/*Package nghttp2 is libnghttp2 binding for golang.
|
||||||
|
|
||||||
server example
|
server example
|
||||||
|
|
||||||
cert, err := tls.LoadX509KeyPair("testdata/server.crt", "testdata/server.key")
|
cert, err := tls.LoadX509KeyPair("testdata/server.crt", "testdata/server.key")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
l, err := tls.Listen("tcp", "127.0.0.1:1100", &tls.Config{
|
l, err := tls.Listen("tcp", "127.0.0.1:1100", &tls.Config{
|
||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
NextProtos: []string{"h2"},
|
NextProtos: []string{"h2"},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
addr := l.Addr().String()
|
addr := l.Addr().String()
|
||||||
|
|
||||||
http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Printf("%+v", r)
|
log.Printf("%+v", r)
|
||||||
hdr := w.Header()
|
hdr := w.Header()
|
||||||
hdr.Set("content-type", "text/plain")
|
hdr.Set("content-type", "text/plain")
|
||||||
hdr.Set("aa", "bb")
|
hdr.Set("aa", "bb")
|
||||||
d, err := ioutil.ReadAll(r.Body)
|
d, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Write(d)
|
w.Write(d)
|
||||||
})
|
})
|
||||||
|
|
||||||
for {
|
for {
|
||||||
c, err := l.Accept()
|
c, err := l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
h2conn, err := Server(c, nil)
|
h2conn, err := Server(c, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Printf("%+v", h2conn)
|
log.Printf("%+v", h2conn)
|
||||||
go h2conn.Run()
|
go h2conn.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
client example
|
client example
|
||||||
|
|
||||||
conn, err := tls.Dial("tcp", "nghttp2.org:443", &tls.Config{
|
conn, err := tls.Dial("tcp", "nghttp2.org:443", &tls.Config{
|
||||||
NextProtos: []string{"h2"},
|
NextProtos: []string{"h2"},
|
||||||
ServerName: "nghttp2.org",
|
ServerName: "nghttp2.org",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
if err := conn.Handshake(); err != nil{
|
if err := conn.Handshake(); err != nil{
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
cstate := conn.ConnectionState()
|
cstate := conn.ConnectionState()
|
||||||
if cstate.NegotiatedProtocol != "h2" {
|
if cstate.NegotiatedProtocol != "h2" {
|
||||||
log.Fatal("no http2 on server")
|
t.Fatal("no http2 on server")
|
||||||
}
|
}
|
||||||
|
|
||||||
h2conn, err := Client(conn)
|
h2conn, err := Client(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
param := url.Values{}
|
param := url.Values{}
|
||||||
param.Add("e", "b")
|
param.Add("e", "b")
|
||||||
param.Add("f", "d")
|
param.Add("f", "d")
|
||||||
data := bytes.NewReader([]byte(param.Encode()))
|
data := bytes.NewReader([]byte(param.Encode()))
|
||||||
req, _ := http.NewRequest("POST",
|
req, _ := http.NewRequest("POST",
|
||||||
"https://nghttp2.org/httpbin/post?a=b&c=d",
|
"https://nghttp2.org/httpbin/post?a=b&c=d",
|
||||||
data)
|
data)
|
||||||
|
|
||||||
log.Printf("%+v", req)
|
log.Printf("%+v", req)
|
||||||
|
|
||||||
req.Header.Set("user-agent", "go-nghttp2/1.0")
|
req.Header.Set("user-agent", "go-nghttp2/1.0")
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
res, err := h2conn.RoundTrip(req)
|
res, err := h2conn.RoundTrip(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
log.Printf("expect %d, got %d", http.StatusOK, res.StatusCode)
|
t.Errorf("expect %d, got %d", http.StatusOK, res.StatusCode)
|
||||||
}
|
}
|
||||||
res.Write(os.Stderr)
|
res.Write(os.Stderr)
|
||||||
|
|
||||||
|
|
||||||
co-work with net/http example
|
co-work with net/http example
|
||||||
|
|
||||||
l, err := net.Listen("tcp", "127.0.0.1:1222")
|
l, err := net.Listen("tcp", "127.0.0.1:1222")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
TLSConfig: &tls.Config{
|
TLSConfig: &tls.Config{
|
||||||
NextProtos: []string{"h2", "http/1.1"},
|
NextProtos: []string{"h2", "http/1.1"},
|
||||||
},
|
},
|
||||||
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){
|
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){
|
||||||
"h2": nghttp2.HTTP2Handler,
|
"h2": nghttp2.HTTP2Handler,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
srv.ServeTLS(l, "testdata/server.crt", "testdata/server.key")
|
srv.ServeTLS(l, "testdata/server.crt", "testdata/server.key")
|
||||||
|
|
||||||
see http2_test.go for more details
|
see http2_test.go for more details
|
||||||
*/
|
*/
|
||||||
package nghttp2
|
package nghttp2
|
||||||
|
Loading…
Reference in New Issue