github.com/xmplusdev/xmcore@v1.8.11-0.20240412132628-5518b55526af/transport/internet/httpupgrade/dialer.go (about) 1 package httpupgrade 2 3 import ( 4 "bufio" 5 "context" 6 "net/http" 7 "net/url" 8 "strings" 9 10 "github.com/xmplusdev/xmcore/common" 11 "github.com/xmplusdev/xmcore/common/net" 12 "github.com/xmplusdev/xmcore/common/session" 13 "github.com/xmplusdev/xmcore/transport/internet" 14 "github.com/xmplusdev/xmcore/transport/internet/stat" 15 "github.com/xmplusdev/xmcore/transport/internet/tls" 16 ) 17 18 type ConnRF struct { 19 net.Conn 20 Req *http.Request 21 First bool 22 } 23 24 func (c *ConnRF) Read(b []byte) (int, error) { 25 if c.First { 26 c.First = false 27 // TODO The bufio usage here is unreliable 28 resp, err := http.ReadResponse(bufio.NewReader(c.Conn), c.Req) // nolint:bodyclose 29 if err != nil { 30 return 0, err 31 } 32 if resp.Status != "101 Switching Protocols" || 33 strings.ToLower(resp.Header.Get("Upgrade")) != "websocket" || 34 strings.ToLower(resp.Header.Get("Connection")) != "upgrade" { 35 return 0, newError("unrecognized reply") 36 } 37 } 38 return c.Conn.Read(b) 39 } 40 41 func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) { 42 transportConfiguration := streamSettings.ProtocolSettings.(*Config) 43 44 pconn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings) 45 if err != nil { 46 newError("failed to dial to ", dest).Base(err).AtError().WriteToLog() 47 return nil, err 48 } 49 50 var conn net.Conn 51 var requestURL url.URL 52 if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { 53 tlsConfig := config.GetTLSConfig(tls.WithDestination(dest), tls.WithNextProto("http/1.1")) 54 if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil { 55 conn = tls.UClient(pconn, tlsConfig, fingerprint) 56 if err := conn.(*tls.UConn).WebsocketHandshakeContext(ctx); err != nil { 57 return nil, err 58 } 59 } else { 60 conn = tls.Client(pconn, tlsConfig) 61 } 62 requestURL.Scheme = "https" 63 } else { 64 conn = pconn 65 requestURL.Scheme = "http" 66 } 67 68 requestURL.Host = dest.NetAddr() 69 requestURL.Path = transportConfiguration.GetNormalizedPath() 70 req := &http.Request{ 71 Method: http.MethodGet, 72 URL: &requestURL, 73 Host: transportConfiguration.Host, 74 Header: make(http.Header), 75 } 76 for key, value := range transportConfiguration.Header { 77 req.Header.Add(key, value) 78 } 79 req.Header.Set("Connection", "upgrade") 80 req.Header.Set("Upgrade", "websocket") 81 82 err = req.Write(conn) 83 if err != nil { 84 return nil, err 85 } 86 87 connRF := &ConnRF{ 88 Conn: conn, 89 Req: req, 90 First: true, 91 } 92 93 if transportConfiguration.Ed == 0 { 94 _, err = connRF.Read([]byte{}) 95 if err != nil { 96 return nil, err 97 } 98 } 99 100 return connRF, nil 101 } 102 103 func dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) { 104 newError("creating connection to ", dest).WriteToLog(session.ExportIDToError(ctx)) 105 106 conn, err := dialhttpUpgrade(ctx, dest, streamSettings) 107 if err != nil { 108 return nil, newError("failed to dial request to ", dest).Base(err) 109 } 110 return stat.Connection(conn), nil 111 } 112 113 func init() { 114 common.Must(internet.RegisterTransportDialer(protocolName, dial)) 115 }