@@ -22,35 +22,42 @@ final class HTTP1ProxyConnectHandler: ChannelDuplexHandler, RemovableChannelHand
22
22
23
23
enum State {
24
24
// transitions to `.connectSent` or `.failed`
25
- case initialized( EventLoopPromise < Void > )
25
+ case initialized
26
26
// transitions to `.headReceived` or `.failed`
27
- case connectSent( EventLoopPromise < Void > )
27
+ case connectSent( Scheduled < Void > )
28
28
// transitions to `.completed` or `.failed`
29
- case headReceived( EventLoopPromise < Void > )
29
+ case headReceived( Scheduled < Void > )
30
30
// final error state
31
31
case failed( Error )
32
32
// final success state
33
33
case completed
34
34
}
35
35
36
- private var state : State
36
+ private var state : State = . initialized
37
37
38
38
let targetHost : String
39
39
let targetPort : Int
40
40
let proxyAuthorization : HTTPClient . Authorization ?
41
+ let deadline : NIODeadline
42
+
43
+ private var proxyEstablishedPromise : EventLoopPromise < Void > ?
44
+ var proxyEstablishedFuture : EventLoopFuture < Void > ! {
45
+ return self . proxyEstablishedPromise? . futureResult
46
+ }
41
47
42
48
init ( targetHost: String ,
43
49
targetPort: Int ,
44
50
proxyAuthorization: HTTPClient . Authorization ? ,
45
- connectPromise : EventLoopPromise < Void > ) {
51
+ deadline : NIODeadline ) {
46
52
self . targetHost = targetHost
47
53
self . targetPort = targetPort
48
54
self . proxyAuthorization = proxyAuthorization
49
-
50
- self . state = . initialized( connectPromise)
55
+ self . deadline = deadline
51
56
}
52
57
53
58
func handlerAdded( context: ChannelHandlerContext ) {
59
+ self . proxyEstablishedPromise = context. eventLoop. makePromise ( of: Void . self)
60
+
54
61
if context. channel. isActive {
55
62
self . sendConnect ( context: context)
56
63
}
@@ -61,7 +68,9 @@ final class HTTP1ProxyConnectHandler: ChannelDuplexHandler, RemovableChannelHand
61
68
case . failed, . completed:
62
69
break
63
70
case . initialized, . connectSent, . headReceived:
64
- preconditionFailure ( " Removing the handler, while connecting seems wrong " )
71
+ struct NoResult : Error { }
72
+ self . state = . failed( NoResult ( ) )
73
+ self . proxyEstablishedPromise? . fail ( NoResult ( ) )
65
74
}
66
75
}
67
76
@@ -71,10 +80,14 @@ final class HTTP1ProxyConnectHandler: ChannelDuplexHandler, RemovableChannelHand
71
80
72
81
func channelInactive( context: ChannelHandlerContext ) {
73
82
switch self . state {
74
- case . initialized( let promise) , . connectSent( let promise) , . headReceived( let promise) :
83
+ case . initialized:
84
+ preconditionFailure ( " How can we receive a channelInactive before a channelActive? " )
85
+ case . connectSent( let timeout) , . headReceived( let timeout) :
86
+ timeout. cancel ( )
75
87
let error = HTTPClientError . remoteConnectionClosed
76
88
self . state = . failed( error)
77
- promise. fail ( error)
89
+ self . proxyEstablishedPromise? . fail ( error)
90
+ context. fireErrorCaught ( error)
78
91
case . failed:
79
92
break
80
93
case . completed:
@@ -102,25 +115,31 @@ final class HTTP1ProxyConnectHandler: ChannelDuplexHandler, RemovableChannelHand
102
115
case 407 :
103
116
let error = HTTPClientError . proxyAuthenticationRequired
104
117
self . state = . failed( error)
105
- context. close ( promise: nil )
106
- promise. fail ( error)
118
+ self . proxyEstablishedPromise? . fail ( error)
119
+ context. fireErrorCaught ( error)
120
+ context. close ( mode: . all, promise: nil )
121
+
107
122
default :
108
123
// Any response other than a successful response
109
124
// indicates that the tunnel has not yet been formed and that the
110
125
// connection remains governed by HTTP.
111
126
let error = HTTPClientError . invalidProxyResponse
112
127
self . state = . failed( error)
113
- context. close ( promise: nil )
114
- promise. fail ( error)
128
+ self . proxyEstablishedPromise? . fail ( error)
129
+ context. fireErrorCaught ( error)
130
+ context. close ( mode: . all, promise: nil )
115
131
}
116
132
case . body:
117
133
switch self . state {
118
- case . headReceived( let promise) :
134
+ case . headReceived( let timeout) :
135
+ timeout. cancel ( )
119
136
// we don't expect a body
120
137
let error = HTTPClientError . invalidProxyResponse
121
138
self . state = . failed( error)
122
- context. close ( promise: nil )
123
- promise. fail ( error)
139
+ self . proxyEstablishedPromise? . fail ( error)
140
+ context. fireErrorCaught ( error)
141
+ context. close ( mode: . all, promise: nil )
142
+
124
143
case . failed:
125
144
// ran into an error before... ignore this one
126
145
break
@@ -130,9 +149,11 @@ final class HTTP1ProxyConnectHandler: ChannelDuplexHandler, RemovableChannelHand
130
149
131
150
case . end:
132
151
switch self . state {
133
- case . headReceived( let promise) :
152
+ case . headReceived( let timeout) :
153
+ timeout. cancel ( )
134
154
self . state = . completed
135
- promise. succeed ( ( ) )
155
+ self . proxyEstablishedPromise? . succeed ( ( ) )
156
+
136
157
case . failed:
137
158
// ran into an error before... ignore this one
138
159
break
@@ -143,12 +164,30 @@ final class HTTP1ProxyConnectHandler: ChannelDuplexHandler, RemovableChannelHand
143
164
}
144
165
145
166
func sendConnect( context: ChannelHandlerContext ) {
146
- guard case . initialized( let promise ) = self . state else {
167
+ guard case . initialized = self . state else {
147
168
// we might run into this handler twice, once in handlerAdded and once in channelActive.
148
169
return
149
170
}
150
171
151
- self . state = . connectSent( promise)
172
+ let timeout = context. eventLoop. scheduleTask ( deadline: self . deadline) {
173
+ switch self . state {
174
+ case . initialized:
175
+ preconditionFailure ( " How can we have a scheduled timeout, if the connection is not even up? " )
176
+
177
+ case . connectSent( let scheduled) , . headReceived( let scheduled) :
178
+ scheduled. cancel ( )
179
+ let error = HTTPClientError . httpProxyHandshakeTimeout
180
+ self . state = . failed( error)
181
+ self . proxyEstablishedPromise? . fail ( error)
182
+ context. fireErrorCaught ( error)
183
+ context. close ( mode: . all, promise: nil )
184
+
185
+ case . failed, . completed:
186
+ break
187
+ }
188
+ }
189
+
190
+ self . state = . connectSent( timeout)
152
191
153
192
var head = HTTPRequestHead (
154
193
version: . init( major: 1 , minor: 1 ) ,
0 commit comments