Add xhttp transport

This commit is contained in:
Shtorm
2025-06-08 19:35:59 +03:00
parent dfc6fce8bb
commit db5da47a9d
50 changed files with 4691 additions and 7 deletions

215
common/xray/pipe/impl.go Normal file
View File

@@ -0,0 +1,215 @@
package pipe
import (
"errors"
"io"
"runtime"
"sync"
"time"
"github.com/sagernet/sing-box/common/xray"
"github.com/sagernet/sing-box/common/xray/buf"
"github.com/sagernet/sing-box/common/xray/signal"
"github.com/sagernet/sing-box/common/xray/signal/done"
)
type state byte
const (
open state = iota
closed
errord
)
type pipeOption struct {
limit int32 // maximum buffer size in bytes
discardOverflow bool
}
func (o *pipeOption) isFull(curSize int32) bool {
return o.limit >= 0 && curSize > o.limit
}
type pipe struct {
sync.Mutex
data buf.MultiBuffer
readSignal *signal.Notifier
writeSignal *signal.Notifier
done *done.Instance
errChan chan error
option pipeOption
state state
}
var (
errBufferFull = errors.New("buffer full")
errSlowDown = errors.New("slow down")
)
func (p *pipe) Len() int32 {
data := p.data
if data == nil {
return 0
}
return data.Len()
}
func (p *pipe) getState(forRead bool) error {
switch p.state {
case open:
if !forRead && p.option.isFull(p.data.Len()) {
return errBufferFull
}
return nil
case closed:
if !forRead {
return io.ErrClosedPipe
}
if !p.data.IsEmpty() {
return nil
}
return io.EOF
case errord:
return io.ErrClosedPipe
default:
panic("impossible case")
}
}
func (p *pipe) readMultiBufferInternal() (buf.MultiBuffer, error) {
p.Lock()
defer p.Unlock()
if err := p.getState(true); err != nil {
return nil, err
}
data := p.data
p.data = nil
return data, nil
}
func (p *pipe) ReadMultiBuffer() (buf.MultiBuffer, error) {
for {
data, err := p.readMultiBufferInternal()
if data != nil || err != nil {
p.writeSignal.Signal()
return data, err
}
select {
case <-p.readSignal.Wait():
case <-p.done.Wait():
case err = <-p.errChan:
return nil, err
}
}
}
func (p *pipe) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error) {
timer := time.NewTimer(d)
defer timer.Stop()
for {
data, err := p.readMultiBufferInternal()
if data != nil || err != nil {
p.writeSignal.Signal()
return data, err
}
select {
case <-p.readSignal.Wait():
case <-p.done.Wait():
case <-timer.C:
return nil, buf.ErrReadTimeout
}
}
}
func (p *pipe) writeMultiBufferInternal(mb buf.MultiBuffer) error {
p.Lock()
defer p.Unlock()
if err := p.getState(false); err != nil {
return err
}
if p.data == nil {
p.data = mb
return nil
}
p.data, _ = buf.MergeMulti(p.data, mb)
return errSlowDown
}
func (p *pipe) WriteMultiBuffer(mb buf.MultiBuffer) error {
if mb.IsEmpty() {
return nil
}
for {
err := p.writeMultiBufferInternal(mb)
if err == nil {
p.readSignal.Signal()
return nil
}
if err == errSlowDown {
p.readSignal.Signal()
// Yield current goroutine. Hopefully the reading counterpart can pick up the payload.
runtime.Gosched()
return nil
}
if err == errBufferFull && p.option.discardOverflow {
buf.ReleaseMulti(mb)
return nil
}
if err != errBufferFull {
buf.ReleaseMulti(mb)
p.readSignal.Signal()
return err
}
select {
case <-p.writeSignal.Wait():
case <-p.done.Wait():
return io.ErrClosedPipe
}
}
}
func (p *pipe) Close() error {
p.Lock()
defer p.Unlock()
if p.state == closed || p.state == errord {
return nil
}
p.state = closed
common.Must(p.done.Close())
return nil
}
// Interrupt implements common.Interruptible.
func (p *pipe) Interrupt() {
p.Lock()
defer p.Unlock()
if p.state == closed || p.state == errord {
return
}
p.state = errord
if !p.data.IsEmpty() {
buf.ReleaseMulti(p.data)
p.data = nil
}
common.Must(p.done.Close())
}

53
common/xray/pipe/pipe.go Normal file
View File

@@ -0,0 +1,53 @@
package pipe
import (
"github.com/sagernet/sing-box/common/xray/signal"
"github.com/sagernet/sing-box/common/xray/signal/done"
)
// Option for creating new Pipes.
type Option func(*pipeOption)
// WithoutSizeLimit returns an Option for Pipe to have no size limit.
func WithoutSizeLimit() Option {
return func(opt *pipeOption) {
opt.limit = -1
}
}
// WithSizeLimit returns an Option for Pipe to have the given size limit.
func WithSizeLimit(limit int32) Option {
return func(opt *pipeOption) {
opt.limit = limit
}
}
// DiscardOverflow returns an Option for Pipe to discard writes if full.
func DiscardOverflow() Option {
return func(opt *pipeOption) {
opt.discardOverflow = true
}
}
// New creates a new Reader and Writer that connects to each other.
func New(opts ...Option) (*Reader, *Writer) {
p := &pipe{
readSignal: signal.NewNotifier(),
writeSignal: signal.NewNotifier(),
done: done.New(),
errChan: make(chan error, 1),
option: pipeOption{
limit: -1,
},
}
for _, opt := range opts {
opt(&(p.option))
}
return &Reader{
pipe: p,
}, &Writer{
pipe: p,
}
}

View File

@@ -0,0 +1,41 @@
package pipe
import (
"time"
"github.com/sagernet/sing-box/common/xray/buf"
)
// Reader is a buf.Reader that reads content from a pipe.
type Reader struct {
pipe *pipe
}
// ReadMultiBuffer implements buf.Reader.
func (r *Reader) ReadMultiBuffer() (buf.MultiBuffer, error) {
return r.pipe.ReadMultiBuffer()
}
// ReadMultiBufferTimeout reads content from a pipe within the given duration, or returns buf.ErrTimeout otherwise.
func (r *Reader) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error) {
return r.pipe.ReadMultiBufferTimeout(d)
}
// Interrupt implements common.Interruptible.
func (r *Reader) Interrupt() {
r.pipe.Interrupt()
}
// ReturnAnError makes ReadMultiBuffer return an error, only once.
func (r *Reader) ReturnAnError(err error) {
r.pipe.errChan <- err
}
// Recover catches an error set by ReturnAnError, if exists.
func (r *Reader) Recover() (err error) {
select {
case err = <-r.pipe.errChan:
default:
}
return
}

View File

@@ -0,0 +1,29 @@
package pipe
import (
"github.com/sagernet/sing-box/common/xray/buf"
)
// Writer is a buf.Writer that writes data into a pipe.
type Writer struct {
pipe *pipe
}
// WriteMultiBuffer implements buf.Writer.
func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
return w.pipe.WriteMultiBuffer(mb)
}
// Close implements io.Closer. After the pipe is closed, writing to the pipe will return io.ErrClosedPipe, while reading will return io.EOF.
func (w *Writer) Close() error {
return w.pipe.Close()
}
func (w *Writer) Len() int32 {
return w.pipe.Len()
}
// Interrupt implements common.Interruptible.
func (w *Writer) Interrupt() {
w.pipe.Interrupt()
}