github.com/yaling888/clash@v1.53.0/transport/h2/h2.go (about)

     1  package h2
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"math/rand/v2"
     7  	"net"
     8  	"net/http"
     9  	"net/url"
    10  	"sync"
    11  	"time"
    12  
    13  	"golang.org/x/net/http2"
    14  )
    15  
    16  type Config struct {
    17  	Hosts   []string
    18  	Path    string
    19  	Headers http.Header
    20  }
    21  
    22  var _ net.Conn = (*h2Conn)(nil)
    23  
    24  type h2Conn struct {
    25  	net.Conn
    26  	*http2.ClientConn
    27  	pWriter *io.PipeWriter
    28  	resp    *http.Response
    29  	cfg     *Config
    30  	mux     sync.Mutex
    31  	done    chan struct{}
    32  	eErr    error
    33  }
    34  
    35  func (hc *h2Conn) establishConn() error {
    36  	hc.mux.Lock()
    37  	defer hc.mux.Unlock()
    38  
    39  	select {
    40  	case <-hc.done:
    41  		return hc.eErr
    42  	default:
    43  	}
    44  
    45  	defer func() {
    46  		close(hc.done)
    47  	}()
    48  
    49  	pReader, pWriter := io.Pipe()
    50  
    51  	host := hc.cfg.Hosts[rand.IntN(len(hc.cfg.Hosts))]
    52  	path := hc.cfg.Path
    53  	headers := hc.cfg.Headers
    54  	headers.Del("Content-Type")
    55  	headers.Del("Content-Length")
    56  	headers.Set("Accept-Encoding", "identity")
    57  	// TODO: connect use VMess Host instead of H2 Host
    58  	req := &http.Request{
    59  		Method: http.MethodPut,
    60  		Host:   host,
    61  		URL: &url.URL{
    62  			Scheme: "https",
    63  			Host:   host,
    64  			Path:   path,
    65  		},
    66  		Proto:      "HTTP/2",
    67  		ProtoMajor: 2,
    68  		ProtoMinor: 0,
    69  		Body:       pReader,
    70  		Header:     headers,
    71  	}
    72  
    73  	// it will be close at :  `func (hc *h2Conn) Close() error`
    74  	resp, err := hc.ClientConn.RoundTrip(req)
    75  	if err != nil {
    76  		hc.eErr = err
    77  		return err
    78  	}
    79  
    80  	hc.pWriter = pWriter
    81  	hc.resp = resp
    82  
    83  	return nil
    84  }
    85  
    86  func (hc *h2Conn) Read(b []byte) (n int, err error) {
    87  	if hc.resp != nil {
    88  		return hc.resp.Body.Read(b)
    89  	}
    90  
    91  	<-hc.done
    92  
    93  	if hc.resp != nil {
    94  		return hc.resp.Body.Read(b)
    95  	}
    96  
    97  	err = hc.eErr
    98  	if err == nil {
    99  		err = io.EOF
   100  	}
   101  
   102  	return
   103  }
   104  
   105  func (hc *h2Conn) Write(b []byte) (n int, err error) {
   106  	if hc.pWriter != nil {
   107  		return hc.pWriter.Write(b)
   108  	}
   109  
   110  	if err = hc.establishConn(); err != nil {
   111  		return
   112  	}
   113  
   114  	if hc.pWriter != nil {
   115  		return hc.pWriter.Write(b)
   116  	}
   117  
   118  	err = hc.eErr
   119  	if err == nil {
   120  		err = net.ErrClosed
   121  	}
   122  
   123  	return
   124  }
   125  
   126  func (hc *h2Conn) Close() error {
   127  	if hc.pWriter != nil {
   128  		if err := hc.pWriter.Close(); err != nil {
   129  			return err
   130  		}
   131  	}
   132  	var ctx context.Context
   133  	if hc.resp != nil {
   134  		ctx = hc.resp.Request.Context()
   135  	} else {
   136  		ctx1, cancel := context.WithTimeout(context.Background(), time.Second)
   137  		defer cancel()
   138  		ctx = ctx1
   139  	}
   140  	if err := hc.ClientConn.Shutdown(ctx); err != nil {
   141  		return err
   142  	}
   143  	return hc.Conn.Close()
   144  }
   145  
   146  func StreamH2Conn(conn net.Conn, cfg *Config) (net.Conn, error) {
   147  	transport := &http2.Transport{}
   148  
   149  	cConn, err := transport.NewClientConn(conn)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	return &h2Conn{
   155  		Conn:       conn,
   156  		ClientConn: cConn,
   157  		cfg:        cfg,
   158  		done:       make(chan struct{}),
   159  	}, nil
   160  }