Files
upstream-centrifugo/internal/wt/transport.go
upstream-mirror b352a9a30d
Some checks failed
test / Lint (push) Failing after 3s
test / Test with Go 1.26.8 (push) Failing after 2s
snapshot of https://github.com/centrifugal/centrifugo.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:17:21 +05:00

168 lines
4.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package wt
import (
"sync"
"time"
"github.com/centrifugal/centrifugo/v6/internal/tools"
"github.com/centrifugal/centrifuge"
"github.com/centrifugal/protocol"
"github.com/quic-go/webtransport-go"
)
const transportName = "webtransport"
// defaultWebTransportMessageSizeLimit is used when message_size_limit is 0
// (which the stream decoder treats as unbounded). Matches the config default.
const defaultWebTransportMessageSizeLimit = 65536
type webtransportTransport struct {
mu sync.RWMutex
closeCh chan struct{}
protoType centrifuge.ProtocolType
session *webtransport.Session
stream *webtransport.Stream
pingPongConfig centrifuge.PingPongConfig
closed bool
protoMajor int
}
func newWebtransportTransport(protoType centrifuge.ProtocolType, session *webtransport.Session, stream *webtransport.Stream, pingPongConfig centrifuge.PingPongConfig, protoMajor int) *webtransportTransport {
return &webtransportTransport{
protoType: protoType,
closeCh: make(chan struct{}),
session: session,
stream: stream,
pingPongConfig: pingPongConfig,
protoMajor: protoMajor,
}
}
// Name implementation.
func (t *webtransportTransport) Name() string {
return transportName
}
// Protocol implementation.
func (t *webtransportTransport) Protocol() centrifuge.ProtocolType {
return t.protoType
}
// Unidirectional implementation.
func (t *webtransportTransport) Unidirectional() bool {
return false
}
// DisabledPushFlags ...
func (t *webtransportTransport) DisabledPushFlags() uint64 {
return 0
}
// ProtocolVersion ...
func (t *webtransportTransport) ProtocolVersion() centrifuge.ProtocolVersion {
return centrifuge.ProtocolVersion2
}
// Emulation ...
func (t *webtransportTransport) Emulation() bool {
return false
}
// PingPongConfig ...
func (t *webtransportTransport) PingPongConfig() centrifuge.PingPongConfig {
return t.pingPongConfig
}
// AcceptProtocol ...
func (t *webtransportTransport) AcceptProtocol() string {
return tools.GetAcceptProtocolLabel(t.protoMajor)
}
const writeTimeout = 1 * time.Second
// Write ...
func (t *webtransportTransport) Write(message []byte) error {
select {
case <-t.closeCh:
return nil
default:
protoType := protocol.TypeJSON
if t.protoType == centrifuge.ProtocolTypeProtobuf {
protoType = protocol.TypeProtobuf
}
encoder := protocol.GetDataEncoder(protoType)
defer protocol.PutDataEncoder(protoType, encoder)
_ = encoder.Encode(message)
_ = t.stream.SetWriteDeadline(time.Now().Add(writeTimeout))
_, err := t.stream.Write(encoder.Finish())
if err != nil {
return err
}
if protoType == protocol.TypeJSON {
// Need extra new line since WebTransport is stream-based, not frame-based.
_, err = t.stream.Write([]byte("\n"))
}
_ = t.stream.SetWriteDeadline(time.Time{})
return err
}
}
// WriteMany ...
func (t *webtransportTransport) WriteMany(messages ...[]byte) error {
select {
case <-t.closeCh:
return nil
default:
protoType := protocol.TypeJSON
if t.protoType == centrifuge.ProtocolTypeProtobuf {
protoType = protocol.TypeProtobuf
}
encoder := protocol.GetDataEncoder(protoType)
defer protocol.PutDataEncoder(protoType, encoder)
for i := range messages {
err := encoder.Encode(messages[i])
if err != nil {
return err
}
}
_ = t.stream.SetWriteDeadline(time.Now().Add(writeTimeout))
_, err := t.stream.Write(encoder.Finish())
if err != nil {
return err
}
if protoType == protocol.TypeJSON {
// Need extra new line since WebTransport is stream-based, not frame-based.
_, err = t.stream.Write([]byte("\n"))
}
_ = t.stream.SetWriteDeadline(time.Time{})
return err
}
}
// Close ...
func (t *webtransportTransport) Close(d centrifuge.Disconnect) error {
t.mu.Lock()
if t.closed {
t.mu.Unlock()
return nil
}
t.closed = true
close(t.closeCh)
t.mu.Unlock()
_ = t.stream.Close()
// TODO: remove this Sleep.
// We are closing session with SessionErrorCode below but browser client does not receive
// it for some reason. So for now we are sending Disconnect Pushes to pass disconnect code
// to a client. While sending Disconnect Push we hit https://github.com/quic-go/quic-go/issues/3291 –
// adding Sleep give client a chance to receive it. If code sent in CloseWithError will
// reach the client we won't need Sleep here and can disable sending Disconnect Pushes.
time.Sleep(time.Second)
return t.session.CloseWithError(webtransport.SessionErrorCode(d.Code), d.Reason)
}