2
0
mirror of https://github.com/soheilhy/cmux.git synced 2026-09-01 00:04:30 +08:00

16 Commits

Author SHA1 Message Date
Soheil Hassas Yeganeh
7b740c1fa6 Modernize for current Go toolchains
The library already compiled and passed tests, but tooling and
dependencies were stale. This brings everything up to current standards
without changing public behavior.

Library (go 1.23.0):
- cmux_test.go: fix 8 `go vet` failures by sending writer-goroutine
  errors to the existing errCh (now buffered) instead of t.Fatal;
  ioutil.ReadAll -> io.ReadAll
- matchers.go: io/ioutil -> io (io.Discard); gofmt -s doc comment
- go.mod: go 1.11 -> 1.23.0; golang.org/x/net -> v0.42.0 (newest
  release that still permits a 1.23 directive)

Example submodule:
- golang.org/x/net/context -> stdlib context
- grpc v1.27.0 -> v1.80, modern protobuf; helloworld now comes from
  the separate google.golang.org/grpc/examples module (requires go 1.25,
  isolated to this demo-only module)

CI:
- remove ancient .travis.yml (Go 1.6-1.8)
- add .github/workflows/ci.yml: gofmt/vet/build/test -race for the
  library (Go 1.23 + 1.24 matrix) and the example

Temporary() methods and TLS version defaults are intentionally
unchanged: net.Error still requires Temporary(), and the TLS defaults
are public-API behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 23:38:42 -04:00
Ryan Fitzpatrick
5ec6847320 Adopt go modules 2021-02-05 14:11:34 -05:00
Abhilash Gnan
cdd3331e3e Add TestClose
Signed-off-by: Abhilash Gnan <abhilashgnan@gmail.com>
2021-01-14 18:06:57 -05:00
Abhilash Gnan
ce11cfdf3a Add mutex around channel close
Signed-off-by: Abhilash Gnan <abhilashgnan@gmail.com>
2021-01-14 18:06:57 -05:00
Abhilash Gnan
ae889c5259 fix blocking select on donec
Signed-off-by: Abhilash Gnan <abhilashgnan@gmail.com>
2021-01-14 18:06:57 -05:00
Abhilash Gnan
3c0ee784ab use param for recieve only chan
Signed-off-by: Abhilash Gnan <abhilashgnan@gmail.com>
2021-01-14 18:06:57 -05:00
Abhilash Gnan
a192073df5 Remove use of mutex around done chan 2021-01-14 18:06:57 -05:00
Abhilash Gnan
e13d1cbf02 add cmux.Close() function 2021-01-14 18:06:57 -05:00
golint fixer
8a8ea3c539 Fix golint import path 2018-10-25 10:41:06 -04:00
Alan D. Cabrera
f7603f4e1c Fix README.md example code 2018-05-19 12:37:04 -04:00
Soheil Hassas Yeganeh
e09e9389d8 Do not write SETTINGS in response to ACKs.
Reported-by talgendler in Issue #42
2018-01-29 10:50:01 -05:00
Yuki Ito
444ce56efe Add test for multiple matchers 2018-01-23 12:52:48 -05:00
Yuki Ito
cfc68f9888 Fix ugly variable name 2018-01-23 12:52:48 -05:00
Yuki Ito
e96bd75f84 Fix bug matchers are ignored except last one 2018-01-23 12:52:48 -05:00
Soheil Hassas Yeganeh
be5b383fd5 Use IPv4 for the listener to avoid v6 failures on Travis. 2017-12-04 12:53:26 -05:00
Soheil Hassas Yeganeh
b9e684ba4e Fix TestClose for Go10.
Depending on the Go version used, reading from a closed pipe can return
net.OpError or io.ErrClosedPipe. Simply check the string content of the
error.
2017-12-04 12:53:26 -05:00
13 changed files with 260 additions and 65 deletions

50
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: CI
on:
push:
branches: [master]
pull_request:
permissions:
contents: read
jobs:
library:
name: library (go ${{ matrix.go }})
runs-on: ubuntu-latest
strategy:
matrix:
go: ["1.23", "1.24"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: gofmt
run: test -z "$(gofmt -s -l .)" || (gofmt -s -l . && exit 1)
- name: vet
run: go vet ./...
- name: build
run: go build ./...
- name: test
run: go test -race ./...
example:
name: example
runs-on: ubuntu-latest
defaults:
run:
working-directory: example
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
- name: gofmt
run: test -z "$(gofmt -s -l .)" || (gofmt -s -l . && exit 1)
- name: vet
run: go vet ./...
- name: build
run: go build ./...
- name: test
run: go test -race ./...

View File

@@ -1,29 +0,0 @@
language: go
go:
- 1.6
- 1.7
- 1.8
- tip
matrix:
allow_failures:
- go: tip
gobuild_args: -race
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/golang/lint/golint; fi
before_script:
- '! gofmt -s -l . | read'
- echo $TRAVIS_GO_VERSION
- if [[ $TRAVIS_GO_VERSION == 1.6* ]]; then golint ./...; fi
- if [[ $TRAVIS_GO_VERSION == 1.6* ]]; then errcheck ./...; fi
- if [[ $TRAVIS_GO_VERSION == 1.6* ]]; then go tool vet .; fi
- if [[ $TRAVIS_GO_VERSION == 1.6* ]]; then go tool vet --shadow .; fi
script:
- go test -bench . -v ./...
- go test -race -bench . -v ./...

View File

@@ -25,7 +25,7 @@ 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{},

52
cmux.go
View File

@@ -15,6 +15,7 @@
package cmux package cmux
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"net" "net"
@@ -61,6 +62,9 @@ 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 // for readability of readTimeout
var noTimeout time.Duration var noTimeout time.Duration
@@ -93,6 +97,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 // sets a timeout for the read of matchers
@@ -108,16 +114,18 @@ type cMux struct {
root net.Listener root net.Listener
bufLen int bufLen int
errh ErrorHandler errh ErrorHandler
donec chan struct{}
sls []matchersListener sls []matchersListener
readTimeout time.Duration readTimeout time.Duration
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 m(r) return cm(r)
}) })
} }
return mws return mws
@@ -132,6 +140,7 @@ 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
@@ -145,7 +154,7 @@ func (m *cMux) Serve() error {
var wg sync.WaitGroup var wg sync.WaitGroup
defer func() { defer func() {
close(m.donec) m.closeDoneChans()
wg.Wait() wg.Wait()
for _, sl := range m.sls { for _, sl := range m.sls {
@@ -203,6 +212,30 @@ 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
} }
@@ -222,14 +255,19 @@ 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) {
c, ok := <-l.connc select {
if !ok { case c, ok := <-l.connc:
return nil, ErrListenerClosed if !ok {
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.

View File

@@ -22,7 +22,6 @@ import (
"fmt" "fmt"
"go/build" "go/build"
"io" "io"
"io/ioutil"
"log" "log"
"net" "net"
"net/http" "net/http"
@@ -128,7 +127,7 @@ func runTestHTTPServer(errCh chan<- error, l net.Listener) {
mu.Unlock() mu.Unlock()
}, },
} }
if err := s.Serve(l); err != ErrListenerClosed { if err := s.Serve(l); err != ErrListenerClosed && err != ErrServerClosed {
errCh <- err errCh <- err
} }
} }
@@ -194,7 +193,7 @@ func runTestHTTPClient(t *testing.T, proto string, addr net.Addr) {
} }
}() }()
b, err := ioutil.ReadAll(r.Body) b, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -218,7 +217,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 { if err != ErrListenerClosed && err != ErrServerClosed {
errCh <- err errCh <- err
} }
return return
@@ -321,7 +320,7 @@ func TestTimeout(t *testing.T) {
func TestRead(t *testing.T) { func TestRead(t *testing.T) {
defer leakCheck(t)() defer leakCheck(t)()
errCh := make(chan error) errCh := make(chan error, 1)
defer func() { defer func() {
select { select {
case err := <-errCh: case err := <-errCh:
@@ -335,10 +334,11 @@ func TestRead(t *testing.T) {
writer, reader := net.Pipe() writer, reader := net.Pipe()
go func() { go func() {
if _, err := io.WriteString(writer, strings.Repeat(payload, mult)); err != nil { if _, err := io.WriteString(writer, strings.Repeat(payload, mult)); err != nil {
t.Fatal(err) errCh <- err
return
} }
if err := writer.Close(); err != nil { if err := writer.Close(); err != nil {
t.Fatal(err) errCh <- err
} }
}() }()
@@ -379,7 +379,7 @@ func TestRead(t *testing.T) {
func TestAny(t *testing.T) { func TestAny(t *testing.T) {
defer leakCheck(t)() defer leakCheck(t)()
errCh := make(chan error) errCh := make(chan error, 1)
defer func() { defer func() {
select { select {
case err := <-errCh: case err := <-errCh:
@@ -403,7 +403,7 @@ func TestTLS(t *testing.T) {
generateTLSCert(t) generateTLSCert(t)
defer cleanupTLSCert(t) defer cleanupTLSCert(t)
defer leakCheck(t)() defer leakCheck(t)()
errCh := make(chan error) errCh := make(chan error, 1)
defer func() { defer func() {
select { select {
case err := <-errCh: case err := <-errCh:
@@ -428,7 +428,7 @@ func TestTLS(t *testing.T) {
func TestHTTP2(t *testing.T) { func TestHTTP2(t *testing.T) {
defer leakCheck(t)() defer leakCheck(t)()
errCh := make(chan error) errCh := make(chan error, 1)
defer func() { defer func() {
select { select {
case err := <-errCh: case err := <-errCh:
@@ -439,10 +439,11 @@ func TestHTTP2(t *testing.T) {
writer, reader := net.Pipe() writer, reader := net.Pipe()
go func() { go func() {
if _, err := io.WriteString(writer, http2.ClientPreface); err != nil { if _, err := io.WriteString(writer, http2.ClientPreface); err != nil {
t.Fatal(err) errCh <- err
return
} }
if err := writer.Close(); err != nil { if err := writer.Close(); err != nil {
t.Fatal(err) errCh <- err
} }
}() }()
@@ -493,7 +494,7 @@ func testHTTP2MatchHeaderField(
notMatchValue string, notMatchValue string,
) { ) {
defer leakCheck(t)() defer leakCheck(t)()
errCh := make(chan error) errCh := make(chan error, 1)
defer func() { defer func() {
select { select {
case err := <-errCh: case err := <-errCh:
@@ -505,12 +506,14 @@ func testHTTP2MatchHeaderField(
writer, reader := net.Pipe() writer, reader := net.Pipe()
go func() { go func() {
if _, err := io.WriteString(writer, http2.ClientPreface); err != nil { if _, err := io.WriteString(writer, http2.ClientPreface); err != nil {
t.Fatal(err) errCh <- err
return
} }
var buf bytes.Buffer var buf bytes.Buffer
enc := hpack.NewEncoder(&buf) enc := hpack.NewEncoder(&buf)
if err := enc.WriteField(hpack.HeaderField{Name: name, Value: headerValue}); err != nil { if err := enc.WriteField(hpack.HeaderField{Name: name, Value: headerValue}); err != nil {
t.Fatal(err) errCh <- err
return
} }
framer := http2.NewFramer(writer, nil) framer := http2.NewFramer(writer, nil)
err := framer.WriteHeaders(http2.HeadersFrameParam{ err := framer.WriteHeaders(http2.HeadersFrameParam{
@@ -520,10 +523,11 @@ func testHTTP2MatchHeaderField(
EndHeaders: true, EndHeaders: true,
}) })
if err != nil { if err != nil {
t.Fatal(err) errCh <- err
return
} }
if err := writer.Close(); err != nil { if err := writer.Close(); err != nil {
t.Fatal(err) errCh <- err
} }
}() }()
@@ -558,7 +562,7 @@ func testHTTP2MatchHeaderField(
func TestHTTPGoRPC(t *testing.T) { func TestHTTPGoRPC(t *testing.T) {
defer leakCheck(t)() defer leakCheck(t)()
errCh := make(chan error) errCh := make(chan error, 1)
defer func() { defer func() {
select { select {
case err := <-errCh: case err := <-errCh:
@@ -583,7 +587,7 @@ func TestHTTPGoRPC(t *testing.T) {
func TestErrorHandler(t *testing.T) { func TestErrorHandler(t *testing.T) {
defer leakCheck(t)() defer leakCheck(t)()
errCh := make(chan error) errCh := make(chan error, 1)
defer func() { defer func() {
select { select {
case err := <-errCh: case err := <-errCh:
@@ -622,9 +626,38 @@ func TestErrorHandler(t *testing.T) {
} }
} }
func TestClose(t *testing.T) { func TestMultipleMatchers(t *testing.T) {
defer leakCheck(t)() defer leakCheck(t)()
errCh := make(chan error) errCh := make(chan error, 1)
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)()
errCh := make(chan error, 1)
defer func() { defer func() {
select { select {
case err := <-errCh: case err := <-errCh:
@@ -656,7 +689,7 @@ func TestClose(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 { if err != ErrListenerClosed && err != ErrServerClosed {
t.Fatal(err) t.Fatal(err)
} }
// The error is either io.ErrClosedPipe or net.OpError wrapping // The error is either io.ErrClosedPipe or net.OpError wrapping
@@ -667,6 +700,31 @@ func TestClose(t *testing.T) {
} }
} }
func TestClose(t *testing.T) {
defer leakCheck(t)()
errCh := make(chan error, 1)
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

View File

@@ -15,6 +15,7 @@
package cmux_test package cmux_test
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"log" "log"
@@ -25,10 +26,10 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
"golang.org/x/net/context"
"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"
) )
@@ -86,7 +87,9 @@ 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) {

19
example/go.mod Normal file
View File

@@ -0,0 +1,19 @@
module github.com/soheilhy/cmux/example
go 1.25.0
require (
github.com/soheilhy/cmux v0.0.0-00010101000000-000000000000
golang.org/x/net v0.53.0
google.golang.org/grpc v1.80.0
google.golang.org/grpc/examples v0.0.0-20260605180800-0f3086db7a75
)
require (
golang.org/x/sys v0.43.0 // indirect
golang.org/x/text v0.36.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)
replace github.com/soheilhy/cmux => ../

40
example/go.sum Normal file
View File

@@ -0,0 +1,40 @@
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/grpc/examples v0.0.0-20260605180800-0f3086db7a75 h1:AEIy9rWQCaqGLD5thytE2ox41wjT4vOe43/yh2enp+A=
google.golang.org/grpc/examples v0.0.0-20260605180800-0f3086db7a75/go.mod h1:Cj6Qdy58KTzw400M0aa1PA3rNLrhQgPFdqXxShtOXmM=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=

7
go.mod Normal file
View File

@@ -0,0 +1,7 @@
module github.com/soheilhy/cmux
go 1.23.0
require golang.org/x/net v0.42.0
require golang.org/x/text v0.27.0 // indirect

4
go.sum Normal file
View File

@@ -0,0 +1,4 @@
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=

View File

@@ -18,7 +18,6 @@ import (
"bufio" "bufio"
"crypto/tls" "crypto/tls"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"strings" "strings"
@@ -67,7 +66,8 @@ func HTTP1Fast(extMethods ...string) Matcher {
// //
// By default, any TLS handshake packet is matched. An optional whitelist // By default, any TLS handshake packet is matched. An optional whitelist
// of versions can be passed in to restrict the matcher, for example: // of versions can be passed in to restrict the matcher, for example:
// TLS(tls.VersionTLS11, tls.VersionTLS12) //
// TLS(tls.VersionTLS11, tls.VersionTLS12)
func TLS(versions ...int) Matcher { func TLS(versions ...int) Matcher {
if len(versions) == 0 { if len(versions) == 0 {
versions = []int{ versions = []int{
@@ -148,7 +148,7 @@ func HTTP1HeaderFieldPrefix(name, valuePrefix string) Matcher {
// 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(io.Discard, r, name, func(gotValue string) bool {
return gotValue == value return gotValue == value
}) })
} }
@@ -159,7 +159,7 @@ func HTTP2HeaderField(name, value string) Matcher {
// valuePrefix, this will match. // valuePrefix, this will match.
func HTTP2HeaderFieldPrefix(name, valuePrefix string) Matcher { func HTTP2HeaderFieldPrefix(name, valuePrefix 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(io.Discard, r, name, func(gotValue string) bool {
return strings.HasPrefix(gotValue, valuePrefix) return strings.HasPrefix(gotValue, valuePrefix)
}) })
} }
@@ -240,6 +240,11 @@ 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
} }