mirror of
https://github.com/soheilhy/cmux.git
synced 2025-04-28 11:40:27 +08:00
Compare commits
No commits in common. "master" and "v0.1.1" have entirely different histories.
@ -1,9 +1,8 @@
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
|
- 1.5
|
||||||
- 1.6
|
- 1.6
|
||||||
- 1.7
|
|
||||||
- 1.8
|
|
||||||
- tip
|
- tip
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
@ -14,7 +13,7 @@ gobuild_args: -race
|
|||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- if [[ $TRAVIS_GO_VERSION == 1.6* ]]; then go get -u github.com/kisielk/errcheck; fi
|
- if [[ $TRAVIS_GO_VERSION == 1.6* ]]; then go get -u github.com/kisielk/errcheck; fi
|
||||||
- if [[ $TRAVIS_GO_VERSION == 1.6* ]]; then go get -u golang.org/x/lint/golint; fi
|
- if [[ $TRAVIS_GO_VERSION == 1.6* ]]; then go get -u github.com/golang/lint/golint; fi
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- '! gofmt -s -l . | read'
|
- '! gofmt -s -l . | read'
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
# Auto-generated with:
|
# Auto-generated with:
|
||||||
# git log --oneline --pretty=format:'%an <%aE>' | sort -u
|
# git log --oneline --pretty=format:'%an <%aE>' | sort -u
|
||||||
#
|
#
|
||||||
Andreas Jaekle <andreas@jaekle.net>
|
|
||||||
Dmitri Shuralyov <shurcooL@gmail.com>
|
Dmitri Shuralyov <shurcooL@gmail.com>
|
||||||
Ethan Mosbaugh <emosbaugh@gmail.com>
|
Ethan Mosbaugh <emosbaugh@gmail.com>
|
||||||
Soheil Hassas Yeganeh <soheil.h.y@gmail.com>
|
Soheil Hassas Yeganeh <soheil.h.y@gmail.com>
|
||||||
|
@ -25,14 +25,14 @@ trpcL := m.Match(cmux.Any()) // Any means anything that is not yet matched.
|
|||||||
|
|
||||||
// Create your protocol servers.
|
// Create your protocol servers.
|
||||||
grpcS := grpc.NewServer()
|
grpcS := grpc.NewServer()
|
||||||
grpchello.RegisterGreeterServer(grpcS, &server{})
|
grpchello.RegisterGreeterServer(grpcs, &server{})
|
||||||
|
|
||||||
httpS := &http.Server{
|
httpS := &http.Server{
|
||||||
Handler: &helloHTTP1Handler{},
|
Handler: &helloHTTP1Handler{},
|
||||||
}
|
}
|
||||||
|
|
||||||
trpcS := rpc.NewServer()
|
trpcS := rpc.NewServer()
|
||||||
trpcS.Register(&ExampleRPCRcvr{})
|
s.Register(&ExampleRPCRcvr{})
|
||||||
|
|
||||||
// Use the muxed listeners for your servers.
|
// Use the muxed listeners for your servers.
|
||||||
go grpcS.Serve(grpcL)
|
go grpcS.Serve(grpcL)
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
@ -44,10 +43,6 @@ func (c *mockConn) Read(b []byte) (n int, err error) {
|
|||||||
return c.r.Read(b)
|
return c.r.Read(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *mockConn) SetReadDeadline(time.Time) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func discard(l net.Listener) {
|
func discard(l net.Listener) {
|
||||||
for {
|
for {
|
||||||
if _, err := l.Accept(); err != nil {
|
if _, err := l.Accept(); err != nil {
|
||||||
|
@ -42,10 +42,6 @@ func (s *bufferedReader) Read(p []byte) (int, error) {
|
|||||||
bn := copy(p, s.buffer.Bytes()[s.bufferRead:s.bufferSize])
|
bn := copy(p, s.buffer.Bytes()[s.bufferRead:s.bufferSize])
|
||||||
s.bufferRead += bn
|
s.bufferRead += bn
|
||||||
return bn, s.lastErr
|
return bn, s.lastErr
|
||||||
} else if !s.sniffing && s.buffer.Cap() != 0 {
|
|
||||||
// We don't need the buffer anymore.
|
|
||||||
// Reset it to release the internal slice.
|
|
||||||
s.buffer = bytes.Buffer{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is nothing more to return in the sniffed buffer, read from the
|
// If there is nothing more to return in the sniffed buffer, read from the
|
||||||
|
86
cmux.go
86
cmux.go
@ -15,12 +15,10 @@
|
|||||||
package cmux
|
package cmux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Matcher matches a connection based on its content.
|
// Matcher matches a connection based on its content.
|
||||||
@ -62,20 +60,13 @@ func (e errListenerClosed) Timeout() bool { return false }
|
|||||||
// listener is closed.
|
// listener is closed.
|
||||||
var ErrListenerClosed = errListenerClosed("mux: listener closed")
|
var ErrListenerClosed = errListenerClosed("mux: listener closed")
|
||||||
|
|
||||||
// ErrServerClosed is returned from muxListener.Accept when mux server is closed.
|
|
||||||
var ErrServerClosed = errors.New("mux: server closed")
|
|
||||||
|
|
||||||
// for readability of readTimeout
|
|
||||||
var noTimeout time.Duration
|
|
||||||
|
|
||||||
// New instantiates a new connection multiplexer.
|
// New instantiates a new connection multiplexer.
|
||||||
func New(l net.Listener) CMux {
|
func New(l net.Listener) CMux {
|
||||||
return &cMux{
|
return &cMux{
|
||||||
root: l,
|
root: l,
|
||||||
bufLen: 1024,
|
bufLen: 1024,
|
||||||
errh: func(_ error) bool { return true },
|
errh: func(_ error) bool { return true },
|
||||||
donec: make(chan struct{}),
|
donec: make(chan struct{}),
|
||||||
readTimeout: noTimeout,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,12 +88,8 @@ type CMux interface {
|
|||||||
// Serve starts multiplexing the listener. Serve blocks and perhaps
|
// Serve starts multiplexing the listener. Serve blocks and perhaps
|
||||||
// should be invoked concurrently within a go routine.
|
// should be invoked concurrently within a go routine.
|
||||||
Serve() error
|
Serve() error
|
||||||
// Closes cmux server and stops accepting any connections on listener
|
|
||||||
Close()
|
|
||||||
// HandleError registers an error handler that handles listener errors.
|
// HandleError registers an error handler that handles listener errors.
|
||||||
HandleError(ErrorHandler)
|
HandleError(ErrorHandler)
|
||||||
// sets a timeout for the read of matchers
|
|
||||||
SetReadTimeout(time.Duration)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type matchersListener struct {
|
type matchersListener struct {
|
||||||
@ -111,21 +98,18 @@ type matchersListener struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type cMux struct {
|
type cMux struct {
|
||||||
root net.Listener
|
root net.Listener
|
||||||
bufLen int
|
bufLen int
|
||||||
errh ErrorHandler
|
errh ErrorHandler
|
||||||
sls []matchersListener
|
donec chan struct{}
|
||||||
readTimeout time.Duration
|
sls []matchersListener
|
||||||
donec chan struct{}
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchersToMatchWriters(matchers []Matcher) []MatchWriter {
|
func matchersToMatchWriters(matchers []Matcher) []MatchWriter {
|
||||||
mws := make([]MatchWriter, 0, len(matchers))
|
mws := make([]MatchWriter, 0, len(matchers))
|
||||||
for _, m := range matchers {
|
for _, m := range matchers {
|
||||||
cm := m
|
|
||||||
mws = append(mws, func(w io.Writer, r io.Reader) bool {
|
mws = append(mws, func(w io.Writer, r io.Reader) bool {
|
||||||
return cm(r)
|
return m(r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return mws
|
return mws
|
||||||
@ -140,21 +124,16 @@ func (m *cMux) MatchWithWriters(matchers ...MatchWriter) net.Listener {
|
|||||||
ml := muxListener{
|
ml := muxListener{
|
||||||
Listener: m.root,
|
Listener: m.root,
|
||||||
connc: make(chan net.Conn, m.bufLen),
|
connc: make(chan net.Conn, m.bufLen),
|
||||||
donec: make(chan struct{}),
|
|
||||||
}
|
}
|
||||||
m.sls = append(m.sls, matchersListener{ss: matchers, l: ml})
|
m.sls = append(m.sls, matchersListener{ss: matchers, l: ml})
|
||||||
return ml
|
return ml
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *cMux) SetReadTimeout(t time.Duration) {
|
|
||||||
m.readTimeout = t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *cMux) Serve() error {
|
func (m *cMux) Serve() error {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
m.closeDoneChans()
|
close(m.donec)
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
for _, sl := range m.sls {
|
for _, sl := range m.sls {
|
||||||
@ -184,17 +163,11 @@ func (m *cMux) serve(c net.Conn, donec <-chan struct{}, wg *sync.WaitGroup) {
|
|||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
muc := newMuxConn(c)
|
muc := newMuxConn(c)
|
||||||
if m.readTimeout > noTimeout {
|
|
||||||
_ = c.SetReadDeadline(time.Now().Add(m.readTimeout))
|
|
||||||
}
|
|
||||||
for _, sl := range m.sls {
|
for _, sl := range m.sls {
|
||||||
for _, s := range sl.ss {
|
for _, s := range sl.ss {
|
||||||
matched := s(muc.Conn, muc.startSniffing())
|
matched := s(muc.Conn, muc.startSniffing())
|
||||||
if matched {
|
if matched {
|
||||||
muc.doneSniffing()
|
muc.doneSniffing()
|
||||||
if m.readTimeout > noTimeout {
|
|
||||||
_ = c.SetReadDeadline(time.Time{})
|
|
||||||
}
|
|
||||||
select {
|
select {
|
||||||
case sl.l.connc <- muc:
|
case sl.l.connc <- muc:
|
||||||
case <-donec:
|
case <-donec:
|
||||||
@ -212,30 +185,6 @@ func (m *cMux) serve(c net.Conn, donec <-chan struct{}, wg *sync.WaitGroup) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *cMux) Close() {
|
|
||||||
m.closeDoneChans()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *cMux) closeDoneChans() {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-m.donec:
|
|
||||||
// Already closed. Don't close again
|
|
||||||
default:
|
|
||||||
close(m.donec)
|
|
||||||
}
|
|
||||||
for _, sl := range m.sls {
|
|
||||||
select {
|
|
||||||
case <-sl.l.donec:
|
|
||||||
// Already closed. Don't close again
|
|
||||||
default:
|
|
||||||
close(sl.l.donec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *cMux) HandleError(h ErrorHandler) {
|
func (m *cMux) HandleError(h ErrorHandler) {
|
||||||
m.errh = h
|
m.errh = h
|
||||||
}
|
}
|
||||||
@ -255,19 +204,14 @@ func (m *cMux) handleErr(err error) bool {
|
|||||||
type muxListener struct {
|
type muxListener struct {
|
||||||
net.Listener
|
net.Listener
|
||||||
connc chan net.Conn
|
connc chan net.Conn
|
||||||
donec chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l muxListener) Accept() (net.Conn, error) {
|
func (l muxListener) Accept() (net.Conn, error) {
|
||||||
select {
|
c, ok := <-l.connc
|
||||||
case c, ok := <-l.connc:
|
if !ok {
|
||||||
if !ok {
|
return nil, ErrListenerClosed
|
||||||
return nil, ErrListenerClosed
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
case <-l.donec:
|
|
||||||
return nil, ErrServerClosed
|
|
||||||
}
|
}
|
||||||
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MuxConn wraps a net.Conn and provides transparent sniffing of connection data.
|
// MuxConn wraps a net.Conn and provides transparent sniffing of connection data.
|
||||||
|
323
cmux_test.go
323
cmux_test.go
@ -15,20 +15,13 @@
|
|||||||
package cmux
|
package cmux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/rpc"
|
"net/rpc"
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@ -38,7 +31,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
"golang.org/x/net/http2/hpack"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -47,7 +39,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func safeServe(errCh chan<- error, muxl CMux) {
|
func safeServe(errCh chan<- error, muxl CMux) {
|
||||||
if err := muxl.Serve(); !strings.Contains(err.Error(), "use of closed") {
|
if err := muxl.Serve(); !strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
errCh <- err
|
errCh <- err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,17 +73,14 @@ func (l *chanListener) Accept() (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testListener(t *testing.T) (net.Listener, func()) {
|
func testListener(t *testing.T) (net.Listener, func()) {
|
||||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
l, err := net.Listen("tcp", ":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
var once sync.Once
|
|
||||||
return l, func() {
|
return l, func() {
|
||||||
once.Do(func() {
|
if err := l.Close(); err != nil {
|
||||||
if err := l.Close(); err != nil {
|
t.Fatal(err)
|
||||||
t.Fatal(err)
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,62 +117,13 @@ func runTestHTTPServer(errCh chan<- error, l net.Listener) {
|
|||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := s.Serve(l); err != ErrListenerClosed && err != ErrServerClosed {
|
if err := s.Serve(l); err != ErrListenerClosed {
|
||||||
errCh <- err
|
errCh <- err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateTLSCert(t *testing.T) {
|
|
||||||
err := exec.Command("go", "run", build.Default.GOROOT+"/src/crypto/tls/generate_cert.go", "--host", "*").Run()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanupTLSCert(t *testing.T) {
|
|
||||||
err := os.Remove("cert.pem")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
err = os.Remove("key.pem")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTestTLSServer(errCh chan<- error, l net.Listener) {
|
|
||||||
certificate, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
|
|
||||||
if err != nil {
|
|
||||||
errCh <- err
|
|
||||||
log.Printf("1")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{certificate},
|
|
||||||
Rand: rand.Reader,
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsl := tls.NewListener(l, config)
|
|
||||||
runTestHTTPServer(errCh, tlsl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTestHTTP1Client(t *testing.T, addr net.Addr) {
|
func runTestHTTP1Client(t *testing.T, addr net.Addr) {
|
||||||
runTestHTTPClient(t, "http", addr)
|
r, err := http.Get("http://" + addr.String())
|
||||||
}
|
|
||||||
|
|
||||||
func runTestTLSClient(t *testing.T, addr net.Addr) {
|
|
||||||
runTestHTTPClient(t, "https", addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTestHTTPClient(t *testing.T, proto string, addr net.Addr) {
|
|
||||||
client := http.Client{
|
|
||||||
Timeout: 5 * time.Second,
|
|
||||||
Transport: &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
r, err := client.Get(proto + "://" + addr.String())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -218,7 +158,7 @@ func runTestRPCServer(errCh chan<- error, l net.Listener) {
|
|||||||
for {
|
for {
|
||||||
c, err := l.Accept()
|
c, err := l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != ErrListenerClosed && err != ErrServerClosed {
|
if err != ErrListenerClosed {
|
||||||
errCh <- err
|
errCh <- err
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -241,84 +181,6 @@ func runTestRPCClient(t *testing.T, addr net.Addr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
handleHTTP1Close = 1
|
|
||||||
handleHTTP1Request = 2
|
|
||||||
handleAnyClose = 3
|
|
||||||
handleAnyRequest = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTimeout(t *testing.T) {
|
|
||||||
defer leakCheck(t)()
|
|
||||||
lis, Close := testListener(t)
|
|
||||||
defer Close()
|
|
||||||
result := make(chan int, 5)
|
|
||||||
testDuration := time.Millisecond * 500
|
|
||||||
m := New(lis)
|
|
||||||
m.SetReadTimeout(testDuration)
|
|
||||||
http1 := m.Match(HTTP1Fast())
|
|
||||||
any := m.Match(Any())
|
|
||||||
go func() {
|
|
||||||
_ = m.Serve()
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
con, err := http1.Accept()
|
|
||||||
if err != nil {
|
|
||||||
result <- handleHTTP1Close
|
|
||||||
} else {
|
|
||||||
_, _ = con.Write([]byte("http1"))
|
|
||||||
_ = con.Close()
|
|
||||||
result <- handleHTTP1Request
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
con, err := any.Accept()
|
|
||||||
if err != nil {
|
|
||||||
result <- handleAnyClose
|
|
||||||
} else {
|
|
||||||
_, _ = con.Write([]byte("any"))
|
|
||||||
_ = con.Close()
|
|
||||||
result <- handleAnyRequest
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
time.Sleep(testDuration) // wait to prevent timeouts on slow test-runners
|
|
||||||
client, err := net.Dial("tcp", lis.Addr().String())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("testTimeout client failed: ", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = client.Close()
|
|
||||||
}()
|
|
||||||
time.Sleep(testDuration / 2)
|
|
||||||
if len(result) != 0 {
|
|
||||||
log.Print("tcp ")
|
|
||||||
t.Fatal("testTimeout failed: accepted to fast: ", len(result))
|
|
||||||
}
|
|
||||||
_ = client.SetReadDeadline(time.Now().Add(testDuration * 3))
|
|
||||||
buffer := make([]byte, 10)
|
|
||||||
rl, err := client.Read(buffer)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("testTimeout failed: client error: ", err, rl)
|
|
||||||
}
|
|
||||||
Close()
|
|
||||||
if rl != 3 {
|
|
||||||
log.Print("testTimeout failed: response from wrong sevice ", rl)
|
|
||||||
}
|
|
||||||
if string(buffer[0:3]) != "any" {
|
|
||||||
log.Print("testTimeout failed: response from wrong sevice ")
|
|
||||||
}
|
|
||||||
time.Sleep(testDuration * 2)
|
|
||||||
if len(result) != 2 {
|
|
||||||
t.Fatal("testTimeout failed: accepted to less: ", len(result))
|
|
||||||
}
|
|
||||||
if a := <-result; a != handleAnyRequest {
|
|
||||||
t.Fatal("testTimeout failed: any rule did not match")
|
|
||||||
}
|
|
||||||
if a := <-result; a != handleHTTP1Close {
|
|
||||||
t.Fatal("testTimeout failed: no close an http rule")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRead(t *testing.T) {
|
func TestRead(t *testing.T) {
|
||||||
defer leakCheck(t)()
|
defer leakCheck(t)()
|
||||||
errCh := make(chan error)
|
errCh := make(chan error)
|
||||||
@ -399,33 +261,6 @@ func TestAny(t *testing.T) {
|
|||||||
runTestHTTP1Client(t, l.Addr())
|
runTestHTTP1Client(t, l.Addr())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTLS(t *testing.T) {
|
|
||||||
generateTLSCert(t)
|
|
||||||
defer cleanupTLSCert(t)
|
|
||||||
defer leakCheck(t)()
|
|
||||||
errCh := make(chan error)
|
|
||||||
defer func() {
|
|
||||||
select {
|
|
||||||
case err := <-errCh:
|
|
||||||
t.Fatal(err)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
l, cleanup := testListener(t)
|
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
muxl := New(l)
|
|
||||||
tlsl := muxl.Match(TLS())
|
|
||||||
httpl := muxl.Match(Any())
|
|
||||||
|
|
||||||
go runTestTLSServer(errCh, tlsl)
|
|
||||||
go runTestHTTPServer(errCh, httpl)
|
|
||||||
go safeServe(errCh, muxl)
|
|
||||||
|
|
||||||
runTestHTTP1Client(t, l.Addr())
|
|
||||||
runTestTLSClient(t, l.Addr())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHTTP2(t *testing.T) {
|
func TestHTTP2(t *testing.T) {
|
||||||
defer leakCheck(t)()
|
defer leakCheck(t)()
|
||||||
errCh := make(chan error)
|
errCh := make(chan error)
|
||||||
@ -477,85 +312,6 @@ func TestHTTP2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTTP2MatchHeaderField(t *testing.T) {
|
|
||||||
testHTTP2MatchHeaderField(t, HTTP2HeaderField, "value", "value", "anothervalue")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHTTP2MatchHeaderFieldPrefix(t *testing.T) {
|
|
||||||
testHTTP2MatchHeaderField(t, HTTP2HeaderFieldPrefix, "application/grpc+proto", "application/grpc", "application/json")
|
|
||||||
}
|
|
||||||
|
|
||||||
func testHTTP2MatchHeaderField(
|
|
||||||
t *testing.T,
|
|
||||||
matcherConstructor func(string, string) Matcher,
|
|
||||||
headerValue string,
|
|
||||||
matchValue string,
|
|
||||||
notMatchValue string,
|
|
||||||
) {
|
|
||||||
defer leakCheck(t)()
|
|
||||||
errCh := make(chan error)
|
|
||||||
defer func() {
|
|
||||||
select {
|
|
||||||
case err := <-errCh:
|
|
||||||
t.Fatal(err)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
name := "name"
|
|
||||||
writer, reader := net.Pipe()
|
|
||||||
go func() {
|
|
||||||
if _, err := io.WriteString(writer, http2.ClientPreface); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
enc := hpack.NewEncoder(&buf)
|
|
||||||
if err := enc.WriteField(hpack.HeaderField{Name: name, Value: headerValue}); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
framer := http2.NewFramer(writer, nil)
|
|
||||||
err := framer.WriteHeaders(http2.HeadersFrameParam{
|
|
||||||
StreamID: 1,
|
|
||||||
BlockFragment: buf.Bytes(),
|
|
||||||
EndStream: true,
|
|
||||||
EndHeaders: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := writer.Close(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
l := newChanListener()
|
|
||||||
l.connCh <- reader
|
|
||||||
muxl := New(l)
|
|
||||||
// Register a bogus matcher that only reads one byte.
|
|
||||||
muxl.Match(func(r io.Reader) bool {
|
|
||||||
var b [1]byte
|
|
||||||
_, _ = r.Read(b[:])
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
// Create a matcher that cannot match the response.
|
|
||||||
muxl.Match(matcherConstructor(name, notMatchValue))
|
|
||||||
// Then match with the expected field.
|
|
||||||
h2l := muxl.Match(matcherConstructor(name, matchValue))
|
|
||||||
go safeServe(errCh, muxl)
|
|
||||||
muxedConn, err := h2l.Accept()
|
|
||||||
close(l.connCh)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
var b [len(http2.ClientPreface)]byte
|
|
||||||
// We have the sniffed buffer first...
|
|
||||||
if _, err := muxedConn.Read(b[:]); err == io.EOF {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if string(b[:]) != http2.ClientPreface {
|
|
||||||
t.Errorf("got unexpected read %s, expected %s", b, http2.ClientPreface)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHTTPGoRPC(t *testing.T) {
|
func TestHTTPGoRPC(t *testing.T) {
|
||||||
defer leakCheck(t)()
|
defer leakCheck(t)()
|
||||||
errCh := make(chan error)
|
errCh := make(chan error)
|
||||||
@ -622,36 +378,7 @@ func TestErrorHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultipleMatchers(t *testing.T) {
|
func TestClose(t *testing.T) {
|
||||||
defer leakCheck(t)()
|
|
||||||
errCh := make(chan error)
|
|
||||||
defer func() {
|
|
||||||
select {
|
|
||||||
case err := <-errCh:
|
|
||||||
t.Fatal(err)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
l, cleanup := testListener(t)
|
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
matcher := func(r io.Reader) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
unmatcher := func(r io.Reader) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
muxl := New(l)
|
|
||||||
lis := muxl.Match(unmatcher, matcher, unmatcher)
|
|
||||||
|
|
||||||
go runTestHTTPServer(errCh, lis)
|
|
||||||
go safeServe(errCh, muxl)
|
|
||||||
|
|
||||||
runTestHTTP1Client(t, l.Addr())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestListenerClose(t *testing.T) {
|
|
||||||
defer leakCheck(t)()
|
defer leakCheck(t)()
|
||||||
errCh := make(chan error)
|
errCh := make(chan error)
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -685,42 +412,15 @@ func TestListenerClose(t *testing.T) {
|
|||||||
|
|
||||||
// Second connection either goes through or it is closed.
|
// Second connection either goes through or it is closed.
|
||||||
if _, err := anyl.Accept(); err != nil {
|
if _, err := anyl.Accept(); err != nil {
|
||||||
if err != ErrListenerClosed && err != ErrServerClosed {
|
if err != ErrListenerClosed {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// The error is either io.ErrClosedPipe or net.OpError wrapping
|
if _, err := c2.Read([]byte{}); err != io.ErrClosedPipe {
|
||||||
// a net.pipeError depending on the go version.
|
|
||||||
if _, err := c2.Read([]byte{}); !strings.Contains(err.Error(), "closed") {
|
|
||||||
t.Fatalf("connection is not closed and is leaked: %v", err)
|
t.Fatalf("connection is not closed and is leaked: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClose(t *testing.T) {
|
|
||||||
defer leakCheck(t)()
|
|
||||||
errCh := make(chan error)
|
|
||||||
defer func() {
|
|
||||||
select {
|
|
||||||
case err := <-errCh:
|
|
||||||
t.Fatal(err)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
l, cleanup := testListener(t)
|
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
muxl := New(l)
|
|
||||||
anyl := muxl.Match(Any())
|
|
||||||
|
|
||||||
go safeServe(errCh, muxl)
|
|
||||||
|
|
||||||
muxl.Close()
|
|
||||||
|
|
||||||
if _, err := anyl.Accept(); err != ErrServerClosed {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cribbed from google.golang.org/grpc/test/end2end_test.go.
|
// Cribbed from google.golang.org/grpc/test/end2end_test.go.
|
||||||
|
|
||||||
// interestingGoroutines returns all goroutines we care about for the purpose
|
// interestingGoroutines returns all goroutines we care about for the purpose
|
||||||
@ -739,7 +439,6 @@ func interestingGoroutines() (gs []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if stack == "" ||
|
if stack == "" ||
|
||||||
strings.Contains(stack, "main.main()") ||
|
|
||||||
strings.Contains(stack, "testing.Main(") ||
|
strings.Contains(stack, "testing.Main(") ||
|
||||||
strings.Contains(stack, "runtime.goexit") ||
|
strings.Contains(stack, "runtime.goexit") ||
|
||||||
strings.Contains(stack, "created by runtime.gc") ||
|
strings.Contains(stack, "created by runtime.gc") ||
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
module github.com/soheilhy/cmux/example
|
|
||||||
|
|
||||||
go 1.11
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/golang/protobuf v1.4.3 // indirect
|
|
||||||
github.com/soheilhy/cmux v0.0.0-00010101000000-000000000000
|
|
||||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb
|
|
||||||
google.golang.org/genproto v0.0.0-20201207150747-9ee31aac76e7 // indirect
|
|
||||||
google.golang.org/grpc v1.27.0
|
|
||||||
)
|
|
||||||
|
|
||||||
replace github.com/soheilhy/cmux => ../
|
|
@ -1,80 +0,0 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
|
||||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
|
|
||||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
|
||||||
google.golang.org/genproto v0.0.0-20201207150747-9ee31aac76e7 h1:MrlntRhz7JNWmR2J5pRYZFgfR0IuuhELDhxo2aBZVsg=
|
|
||||||
google.golang.org/genproto v0.0.0-20201207150747-9ee31aac76e7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
|
|
||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
@ -29,7 +29,6 @@ import (
|
|||||||
"golang.org/x/net/websocket"
|
"golang.org/x/net/websocket"
|
||||||
|
|
||||||
"github.com/soheilhy/cmux"
|
"github.com/soheilhy/cmux"
|
||||||
"google.golang.org/grpc/examples/helloworld/helloworld"
|
|
||||||
grpchello "google.golang.org/grpc/examples/helloworld/helloworld"
|
grpchello "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -87,9 +86,7 @@ func serveRPC(l net.Listener) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type grpcServer struct {
|
type grpcServer struct{}
|
||||||
helloworld.UnimplementedGreeterServer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *grpcServer) SayHello(ctx context.Context, in *grpchello.HelloRequest) (
|
func (s *grpcServer) SayHello(ctx context.Context, in *grpchello.HelloRequest) (
|
||||||
*grpchello.HelloReply, error) {
|
*grpchello.HelloReply, error) {
|
||||||
@ -115,7 +112,7 @@ func Example() {
|
|||||||
|
|
||||||
// We first match the connection against HTTP2 fields. If matched, the
|
// We first match the connection against HTTP2 fields. If matched, the
|
||||||
// connection will be sent through the "grpcl" listener.
|
// connection will be sent through the "grpcl" listener.
|
||||||
grpcl := m.Match(cmux.HTTP2HeaderFieldPrefix("content-type", "application/grpc"))
|
grpcl := m.Match(cmux.HTTP2HeaderField("content-type", "application/grpc"))
|
||||||
//Otherwise, we match it againts a websocket upgrade request.
|
//Otherwise, we match it againts a websocket upgrade request.
|
||||||
wsl := m.Match(cmux.HTTP1HeaderField("Upgrade", "websocket"))
|
wsl := m.Match(cmux.HTTP1HeaderField("Upgrade", "websocket"))
|
||||||
|
|
5
go.mod
5
go.mod
@ -1,5 +0,0 @@
|
|||||||
module github.com/soheilhy/cmux
|
|
||||||
|
|
||||||
go 1.11
|
|
||||||
|
|
||||||
require golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb
|
|
12
go.sum
12
go.sum
@ -1,12 +0,0 @@
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
|
|
||||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
128
matchers.go
128
matchers.go
@ -16,7 +16,6 @@ package cmux
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"crypto/tls"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -38,11 +37,6 @@ func PrefixMatcher(strs ...string) Matcher {
|
|||||||
return pt.matchPrefix
|
return pt.matchPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
func prefixByteMatcher(list ...[]byte) Matcher {
|
|
||||||
pt := newPatriciaTree(list...)
|
|
||||||
return pt.matchPrefix
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultHTTPMethods = []string{
|
var defaultHTTPMethods = []string{
|
||||||
"OPTIONS",
|
"OPTIONS",
|
||||||
"GET",
|
"GET",
|
||||||
@ -63,27 +57,6 @@ func HTTP1Fast(extMethods ...string) Matcher {
|
|||||||
return PrefixMatcher(append(defaultHTTPMethods, extMethods...)...)
|
return PrefixMatcher(append(defaultHTTPMethods, extMethods...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLS matches HTTPS requests.
|
|
||||||
//
|
|
||||||
// By default, any TLS handshake packet is matched. An optional whitelist
|
|
||||||
// of versions can be passed in to restrict the matcher, for example:
|
|
||||||
// TLS(tls.VersionTLS11, tls.VersionTLS12)
|
|
||||||
func TLS(versions ...int) Matcher {
|
|
||||||
if len(versions) == 0 {
|
|
||||||
versions = []int{
|
|
||||||
tls.VersionSSL30,
|
|
||||||
tls.VersionTLS10,
|
|
||||||
tls.VersionTLS11,
|
|
||||||
tls.VersionTLS12,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prefixes := [][]byte{}
|
|
||||||
for _, v := range versions {
|
|
||||||
prefixes = append(prefixes, []byte{22, byte(v >> 8 & 0xff), byte(v & 0xff)})
|
|
||||||
}
|
|
||||||
return prefixByteMatcher(prefixes...)
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxHTTPRead = 4096
|
const maxHTTPRead = 4096
|
||||||
|
|
||||||
// HTTP1 parses the first line or upto 4096 bytes of the request to see if
|
// HTTP1 parses the first line or upto 4096 bytes of the request to see if
|
||||||
@ -127,41 +100,15 @@ func HTTP2() Matcher {
|
|||||||
// request of an HTTP 1 connection.
|
// request of an HTTP 1 connection.
|
||||||
func HTTP1HeaderField(name, value string) Matcher {
|
func HTTP1HeaderField(name, value string) Matcher {
|
||||||
return func(r io.Reader) bool {
|
return func(r io.Reader) bool {
|
||||||
return matchHTTP1Field(r, name, func(gotValue string) bool {
|
return matchHTTP1Field(r, name, value)
|
||||||
return gotValue == value
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP1HeaderFieldPrefix returns a matcher matching the header fields of the
|
// HTTP2HeaderField resturns a matcher matching the header fields of the first
|
||||||
// first request of an HTTP 1 connection. If the header with key name has a
|
|
||||||
// value prefixed with valuePrefix, this will match.
|
|
||||||
func HTTP1HeaderFieldPrefix(name, valuePrefix string) Matcher {
|
|
||||||
return func(r io.Reader) bool {
|
|
||||||
return matchHTTP1Field(r, name, func(gotValue string) bool {
|
|
||||||
return strings.HasPrefix(gotValue, valuePrefix)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP2HeaderField returns a matcher matching the header fields of the first
|
|
||||||
// headers frame.
|
// headers frame.
|
||||||
func HTTP2HeaderField(name, value string) Matcher {
|
func HTTP2HeaderField(name, value string) Matcher {
|
||||||
return func(r io.Reader) bool {
|
return func(r io.Reader) bool {
|
||||||
return matchHTTP2Field(ioutil.Discard, r, name, func(gotValue string) bool {
|
return matchHTTP2Field(ioutil.Discard, r, name, value)
|
||||||
return gotValue == value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP2HeaderFieldPrefix returns a matcher matching the header fields of the
|
|
||||||
// first headers frame. If the header with key name has a value prefixed with
|
|
||||||
// valuePrefix, this will match.
|
|
||||||
func HTTP2HeaderFieldPrefix(name, valuePrefix string) Matcher {
|
|
||||||
return func(r io.Reader) bool {
|
|
||||||
return matchHTTP2Field(ioutil.Discard, r, name, func(gotValue string) bool {
|
|
||||||
return strings.HasPrefix(gotValue, valuePrefix)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,66 +117,37 @@ func HTTP2HeaderFieldPrefix(name, valuePrefix string) Matcher {
|
|||||||
// does not block on receiving a SETTING frame.
|
// does not block on receiving a SETTING frame.
|
||||||
func HTTP2MatchHeaderFieldSendSettings(name, value string) MatchWriter {
|
func HTTP2MatchHeaderFieldSendSettings(name, value string) MatchWriter {
|
||||||
return func(w io.Writer, r io.Reader) bool {
|
return func(w io.Writer, r io.Reader) bool {
|
||||||
return matchHTTP2Field(w, r, name, func(gotValue string) bool {
|
return matchHTTP2Field(w, r, name, value)
|
||||||
return gotValue == value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP2MatchHeaderFieldPrefixSendSettings matches the header field prefix
|
|
||||||
// and writes the settings to the server. Prefer HTTP2HeaderFieldPrefix over
|
|
||||||
// this one, if the client does not block on receiving a SETTING frame.
|
|
||||||
func HTTP2MatchHeaderFieldPrefixSendSettings(name, valuePrefix string) MatchWriter {
|
|
||||||
return func(w io.Writer, r io.Reader) bool {
|
|
||||||
return matchHTTP2Field(w, r, name, func(gotValue string) bool {
|
|
||||||
return strings.HasPrefix(gotValue, valuePrefix)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasHTTP2Preface(r io.Reader) bool {
|
func hasHTTP2Preface(r io.Reader) bool {
|
||||||
var b [len(http2.ClientPreface)]byte
|
var b [len(http2.ClientPreface)]byte
|
||||||
last := 0
|
if _, err := io.ReadFull(r, b[:]); err != nil {
|
||||||
|
return false
|
||||||
for {
|
|
||||||
n, err := r.Read(b[last:])
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
last += n
|
|
||||||
eq := string(b[:last]) == http2.ClientPreface[:last]
|
|
||||||
if last == len(http2.ClientPreface) {
|
|
||||||
return eq
|
|
||||||
}
|
|
||||||
if !eq {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return string(b[:]) == http2.ClientPreface
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchHTTP1Field(r io.Reader, name string, matches func(string) bool) (matched bool) {
|
func matchHTTP1Field(r io.Reader, name, value string) (matched bool) {
|
||||||
req, err := http.ReadRequest(bufio.NewReader(r))
|
req, err := http.ReadRequest(bufio.NewReader(r))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return matches(req.Header.Get(name))
|
return req.Header.Get(name) == value
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchHTTP2Field(w io.Writer, r io.Reader, name string, matches func(string) bool) (matched bool) {
|
func matchHTTP2Field(w io.Writer, r io.Reader, name, value string) (matched bool) {
|
||||||
if !hasHTTP2Preface(r) {
|
if !hasHTTP2Preface(r) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
done := false
|
|
||||||
framer := http2.NewFramer(w, r)
|
framer := http2.NewFramer(w, r)
|
||||||
hdec := hpack.NewDecoder(uint32(4<<10), func(hf hpack.HeaderField) {
|
hdec := hpack.NewDecoder(uint32(4<<10), func(hf hpack.HeaderField) {
|
||||||
if hf.Name == name {
|
if hf.Name == name && hf.Value == value {
|
||||||
done = true
|
matched = true
|
||||||
if matches(hf.Value) {
|
|
||||||
matched = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
for {
|
for {
|
||||||
@ -240,28 +158,20 @@ func matchHTTP2Field(w io.Writer, r io.Reader, name string, matches func(string)
|
|||||||
|
|
||||||
switch f := f.(type) {
|
switch f := f.(type) {
|
||||||
case *http2.SettingsFrame:
|
case *http2.SettingsFrame:
|
||||||
// Sender acknoweldged the SETTINGS frame. No need to write
|
|
||||||
// SETTINGS again.
|
|
||||||
if f.IsAck() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err := framer.WriteSettings(); err != nil {
|
if err := framer.WriteSettings(); err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case *http2.ContinuationFrame:
|
|
||||||
if _, err := hdec.Write(f.HeaderBlockFragment()); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
done = done || f.FrameHeader.Flags&http2.FlagHeadersEndHeaders != 0
|
|
||||||
case *http2.HeadersFrame:
|
case *http2.HeadersFrame:
|
||||||
if _, err := hdec.Write(f.HeaderBlockFragment()); err != nil {
|
if _, err := hdec.Write(f.HeaderBlockFragment()); err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
done = done || f.FrameHeader.Flags&http2.FlagHeadersEndHeaders != 0
|
if matched {
|
||||||
}
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if done {
|
if f.FrameHeader.Flags&http2.FlagHeadersEndHeaders != 0 {
|
||||||
return matched
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user