mirror of
https://github.com/MetaCubeX/Clash.Meta.git
synced 2025-04-06 06:33:31 +03:00
118 lines
2.8 KiB
Go
118 lines
2.8 KiB
Go
package gun
|
|
|
|
import (
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/metacubex/mihomo/common/buf"
|
|
N "github.com/metacubex/mihomo/common/net"
|
|
C "github.com/metacubex/mihomo/constant"
|
|
|
|
"golang.org/x/net/http2"
|
|
"golang.org/x/net/http2/h2c"
|
|
)
|
|
|
|
const idleTimeout = 30 * time.Second
|
|
|
|
type ServerOption struct {
|
|
ServiceName string
|
|
ConnHandler func(conn net.Conn)
|
|
HttpHandler http.Handler
|
|
}
|
|
|
|
func NewServerHandler(options ServerOption) http.Handler {
|
|
path := "/" + options.ServiceName + "/Tun"
|
|
connHandler := options.ConnHandler
|
|
httpHandler := options.HttpHandler
|
|
if httpHandler == nil {
|
|
httpHandler = http.NewServeMux()
|
|
}
|
|
// using h2c.NewHandler to ensure we can work in plain http2
|
|
// and some tls conn is not *tls.Conn (like *reality.Conn)
|
|
return h2c.NewHandler(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
|
if request.URL.Path == path &&
|
|
request.Method == http.MethodPost &&
|
|
strings.HasPrefix(request.Header.Get("Content-Type"), "application/grpc") {
|
|
|
|
writer.Header().Set("Content-Type", "application/grpc")
|
|
writer.Header().Set("TE", "trailers")
|
|
writer.WriteHeader(http.StatusOK)
|
|
|
|
conn := &Conn{
|
|
initFn: func() (io.ReadCloser, netAddr, error) {
|
|
nAddr := netAddr{}
|
|
if request.RemoteAddr != "" {
|
|
metadata := C.Metadata{}
|
|
if err := metadata.SetRemoteAddress(request.RemoteAddr); err == nil {
|
|
nAddr.remoteAddr = net.TCPAddrFromAddrPort(metadata.AddrPort())
|
|
}
|
|
}
|
|
if addr, ok := request.Context().Value(http.LocalAddrContextKey).(net.Addr); ok {
|
|
nAddr.localAddr = addr
|
|
}
|
|
return request.Body, nAddr, nil
|
|
},
|
|
writer: writer,
|
|
}
|
|
_ = conn.Init()
|
|
|
|
wrapper := &h2ConnWrapper{
|
|
// gun.Conn can't correct handle ReadDeadline
|
|
// so call N.NewDeadlineConn to add a safe wrapper
|
|
ExtendedConn: N.NewDeadlineConn(conn),
|
|
}
|
|
connHandler(wrapper)
|
|
wrapper.CloseWrapper()
|
|
|
|
return
|
|
}
|
|
|
|
httpHandler.ServeHTTP(writer, request)
|
|
}), &http2.Server{
|
|
IdleTimeout: idleTimeout,
|
|
})
|
|
}
|
|
|
|
// h2ConnWrapper used to avoid "panic: Write called after Handler finished" for gun.Conn
|
|
type h2ConnWrapper struct {
|
|
N.ExtendedConn
|
|
access sync.Mutex
|
|
closed bool
|
|
}
|
|
|
|
func (w *h2ConnWrapper) Write(p []byte) (n int, err error) {
|
|
w.access.Lock()
|
|
defer w.access.Unlock()
|
|
if w.closed {
|
|
return 0, net.ErrClosed
|
|
}
|
|
return w.ExtendedConn.Write(p)
|
|
}
|
|
|
|
func (w *h2ConnWrapper) WriteBuffer(buffer *buf.Buffer) error {
|
|
w.access.Lock()
|
|
defer w.access.Unlock()
|
|
if w.closed {
|
|
return net.ErrClosed
|
|
}
|
|
return w.ExtendedConn.WriteBuffer(buffer)
|
|
}
|
|
|
|
func (w *h2ConnWrapper) CloseWrapper() {
|
|
w.access.Lock()
|
|
defer w.access.Unlock()
|
|
w.closed = true
|
|
}
|
|
|
|
func (w *h2ConnWrapper) Close() error {
|
|
w.CloseWrapper()
|
|
return w.ExtendedConn.Close()
|
|
}
|
|
|
|
func (w *h2ConnWrapper) Upstream() any {
|
|
return w.ExtendedConn
|
|
}
|