@@ -16,6 +16,7 @@ import (
1616 "github.com/xtls/xray-core/common/buf"
1717 "github.com/xtls/xray-core/common/net"
1818 "github.com/xtls/xray-core/common/session"
19+ "github.com/xtls/xray-core/common/signal/done"
1920 "github.com/xtls/xray-core/common/signal/semaphore"
2021 "github.com/xtls/xray-core/common/uuid"
2122 "github.com/xtls/xray-core/transport/internet"
4445 globalDialerAccess sync.Mutex
4546)
4647
47- func destroyHTTPClient (ctx context.Context , dest net.Destination , streamSettings * internet.MemoryStreamConfig ) {
48- globalDialerAccess .Lock ()
49- defer globalDialerAccess .Unlock ()
50-
51- if globalDialerMap == nil {
52- globalDialerMap = make (map [dialerConf ]reusedClient )
53- }
54-
55- delete (globalDialerMap , dialerConf {dest , streamSettings })
56-
57- }
58-
5948func getHTTPClient (ctx context.Context , dest net.Destination , streamSettings * internet.MemoryStreamConfig ) reusedClient {
6049 globalDialerAccess .Lock ()
6150 defer globalDialerAccess .Unlock ()
@@ -77,15 +66,15 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in
7766 }
7867
7968 dialContext := func (ctxInner context.Context ) (net.Conn , error ) {
80- conn , err := internet .DialSystem (ctx , dest , streamSettings .SocketSettings )
69+ conn , err := internet .DialSystem (ctxInner , dest , streamSettings .SocketSettings )
8170 if err != nil {
8271 return nil , err
8372 }
8473
8574 if gotlsConfig != nil {
8675 if fingerprint := tls .GetFingerprint (tlsConfig .Fingerprint ); fingerprint != nil {
8776 conn = tls .UClient (conn , gotlsConfig , fingerprint )
88- if err := conn .(* tls.UConn ).HandshakeContext (ctx ); err != nil {
77+ if err := conn .(* tls.UConn ).HandshakeContext (ctxInner ); err != nil {
8978 return nil , err
9079 }
9180 } else {
@@ -171,49 +160,73 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
171160
172161 var remoteAddr gonet.Addr
173162 var localAddr gonet.Addr
163+ // this is done when the TCP/UDP connection to the server was established,
164+ // and we can unblock the Dial function and print correct net addresses in
165+ // logs
166+ gotConn := done .New ()
174167
175- trace := & httptrace.ClientTrace {
176- GotConn : func (connInfo httptrace.GotConnInfo ) {
177- remoteAddr = connInfo .Conn .RemoteAddr ()
178- localAddr = connInfo .Conn .LocalAddr ()
179- },
180- }
168+ var downResponse io.ReadCloser
169+ gotDownResponse := done .New ()
181170
182171 sessionIdUuid := uuid .New ()
183172 sessionId := sessionIdUuid .String ()
184173
185- req , err := http .NewRequestWithContext (
186- httptrace .WithClientTrace (ctx , trace ),
187- "GET" ,
188- requestURL .String ()+ "?session=" + sessionId ,
189- nil ,
190- )
191- if err != nil {
192- return nil , err
193- }
174+ go func () {
175+ trace := & httptrace.ClientTrace {
176+ GotConn : func (connInfo httptrace.GotConnInfo ) {
177+ remoteAddr = connInfo .Conn .RemoteAddr ()
178+ localAddr = connInfo .Conn .LocalAddr ()
179+ gotConn .Close ()
180+ },
181+ }
194182
195- req .Header = transportConfiguration .GetRequestHeader ()
196-
197- downResponse , err := httpClient .download .Do (req )
198- if err != nil {
199- // workaround for various connection pool related issues, mostly around
200- // HTTP/1.1. if the http client ever fails to send a request, we simply
201- // delete it entirely.
202- // in HTTP/1.1, it was observed that pool connections would immediately
203- // fail with "context canceled" if the previous http response body was
204- // not explicitly BOTH drained and closed. at the same time, sometimes
205- // the draining itself takes forever and causes more problems.
206- // see also https://github.com/golang/go/issues/60240
207- destroyHTTPClient (ctx , dest , streamSettings )
208- return nil , newError ("failed to send download http request, destroying client" ).Base (err )
209- }
183+ // in case we hit an error, we want to unblock this part
184+ defer gotConn .Close ()
210185
211- if downResponse .StatusCode != 200 {
212- downResponse .Body .Close ()
213- return nil , newError ("invalid status code on download:" , downResponse .Status )
214- }
186+ req , err := http .NewRequestWithContext (
187+ httptrace .WithClientTrace (context .WithoutCancel (ctx ), trace ),
188+ "GET" ,
189+ requestURL .String ()+ sessionId ,
190+ nil ,
191+ )
192+ if err != nil {
193+ newError ("failed to construct download http request" ).Base (err ).WriteToLog ()
194+ gotDownResponse .Close ()
195+ return
196+ }
197+
198+ req .Header = transportConfiguration .GetRequestHeader ()
199+
200+ response , err := httpClient .download .Do (req )
201+ gotConn .Close ()
202+ if err != nil {
203+ newError ("failed to send download http request" ).Base (err ).WriteToLog ()
204+ gotDownResponse .Close ()
205+ return
206+ }
207+
208+ if response .StatusCode != 200 {
209+ response .Body .Close ()
210+ newError ("invalid status code on download:" , response .Status ).WriteToLog ()
211+ gotDownResponse .Close ()
212+ return
213+ }
214+
215+ // skip "ok" response
216+ trashHeader := []byte {0 , 0 }
217+ _ , err = io .ReadFull (response .Body , trashHeader )
218+ if err != nil {
219+ response .Body .Close ()
220+ newError ("failed to read initial response" ).Base (err ).WriteToLog ()
221+ gotDownResponse .Close ()
222+ return
223+ }
215224
216- uploadUrl := requestURL .String () + "?session=" + sessionId + "&seq="
225+ downResponse = response .Body
226+ gotDownResponse .Close ()
227+ }()
228+
229+ uploadUrl := requestURL .String () + sessionId + "/"
217230
218231 uploadPipeReader , uploadPipeWriter := pipe .New (pipe .WithSizeLimit (maxUploadSize ))
219232
@@ -266,7 +279,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
266279 for i := 0 ; i < 5 ; i ++ {
267280 uploadConn = httpClient .uploadRawPool .Get ()
268281 if uploadConn == nil {
269- uploadConn , err = httpClient .dialUploadConn (ctx )
282+ uploadConn , err = httpClient .dialUploadConn (context . WithoutCancel ( ctx ) )
270283 if err != nil {
271284 newError ("failed to connect upload" ).Base (err ).WriteToLog ()
272285 uploadPipeReader .Interrupt ()
@@ -293,21 +306,27 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
293306 }
294307 }()
295308
296- // skip "ok" response
297- trashHeader := []byte {0 , 0 }
298- _ , err = io .ReadFull (downResponse .Body , trashHeader )
299- if err != nil {
300- downResponse .Body .Close ()
301- return nil , newError ("failed to read initial response" )
302- }
309+ // we want to block Dial until we know the remote address of the server,
310+ // for logging purposes
311+ <- gotConn .Wait ()
303312
304313 // necessary in order to send larger chunks in upload
305314 bufferedUploadPipeWriter := buf .NewBufferedWriter (uploadPipeWriter )
306315 bufferedUploadPipeWriter .SetBuffered (false )
307316
317+ lazyDownload := & LazyReader {
318+ CreateReader : func () (io.ReadCloser , error ) {
319+ <- gotDownResponse .Wait ()
320+ if downResponse == nil {
321+ return nil , newError ("downResponse failed" )
322+ }
323+ return downResponse , nil
324+ },
325+ }
326+
308327 conn := splitConn {
309328 writer : bufferedUploadPipeWriter ,
310- reader : downResponse . Body ,
329+ reader : lazyDownload ,
311330 remoteAddr : remoteAddr ,
312331 localAddr : localAddr ,
313332 }
0 commit comments