4
4
5
5
use Evenement \EventEmitter ;
6
6
use Psr \Http \Message \RequestInterface ;
7
+ use Psr \Http \Message \ResponseInterface ;
8
+ use React \Http \Message \Response ;
7
9
use React \Promise ;
8
10
use React \Socket \ConnectionInterface ;
9
11
use React \Socket \ConnectorInterface ;
14
16
* @event response
15
17
* @event drain
16
18
* @event error
17
- * @event end
19
+ * @event close
18
20
* @internal
19
21
*/
20
22
class ClientRequestStream extends EventEmitter implements WritableStreamInterface
@@ -31,9 +33,11 @@ class ClientRequestStream extends EventEmitter implements WritableStreamInterfac
31
33
private $ request ;
32
34
33
35
/** @var ?ConnectionInterface */
34
- private $ stream ;
36
+ private $ connection ;
37
+
38
+ /** @var string */
39
+ private $ buffer = '' ;
35
40
36
- private $ buffer ;
37
41
private $ responseFactory ;
38
42
private $ state = self ::STATE_INIT ;
39
43
private $ ended = false ;
@@ -56,22 +60,22 @@ private function writeHead()
56
60
$ this ->state = self ::STATE_WRITING_HEAD ;
57
61
58
62
$ request = $ this ->request ;
59
- $ streamRef = &$ this ->stream ;
63
+ $ connectionRef = &$ this ->connection ;
60
64
$ stateRef = &$ this ->state ;
61
65
$ pendingWrites = &$ this ->pendingWrites ;
62
66
$ that = $ this ;
63
67
64
68
$ promise = $ this ->connect ();
65
69
$ promise ->then (
66
- function (ConnectionInterface $ stream ) use ($ request , &$ streamRef , &$ stateRef , &$ pendingWrites , $ that ) {
67
- $ streamRef = $ stream ;
68
- assert ($ streamRef instanceof ConnectionInterface);
70
+ function (ConnectionInterface $ connection ) use ($ request , &$ connectionRef , &$ stateRef , &$ pendingWrites , $ that ) {
71
+ $ connectionRef = $ connection ;
72
+ assert ($ connectionRef instanceof ConnectionInterface);
69
73
70
- $ stream ->on ('drain ' , array ($ that , 'handleDrain ' ));
71
- $ stream ->on ('data ' , array ($ that , 'handleData ' ));
72
- $ stream ->on ('end ' , array ($ that , 'handleEnd ' ));
73
- $ stream ->on ('error ' , array ($ that , 'handleError ' ));
74
- $ stream ->on ('close ' , array ($ that , 'handleClose ' ));
74
+ $ connection ->on ('drain ' , array ($ that , 'handleDrain ' ));
75
+ $ connection ->on ('data ' , array ($ that , 'handleData ' ));
76
+ $ connection ->on ('end ' , array ($ that , 'handleEnd ' ));
77
+ $ connection ->on ('error ' , array ($ that , 'handleError ' ));
78
+ $ connection ->on ('close ' , array ($ that , 'close ' ));
75
79
76
80
assert ($ request instanceof RequestInterface);
77
81
$ headers = "{$ request ->getMethod ()} {$ request ->getRequestTarget ()} HTTP/ {$ request ->getProtocolVersion ()}\r\n" ;
@@ -81,7 +85,7 @@ function (ConnectionInterface $stream) use ($request, &$streamRef, &$stateRef, &
81
85
}
82
86
}
83
87
84
- $ more = $ stream ->write ($ headers . "\r\n" . $ pendingWrites );
88
+ $ more = $ connection ->write ($ headers . "\r\n" . $ pendingWrites );
85
89
86
90
assert ($ stateRef === ClientRequestStream::STATE_WRITING_HEAD );
87
91
$ stateRef = ClientRequestStream::STATE_HEAD_WRITTEN ;
@@ -111,7 +115,7 @@ public function write($data)
111
115
112
116
// write directly to connection stream if already available
113
117
if (self ::STATE_HEAD_WRITTEN <= $ this ->state ) {
114
- return $ this ->stream ->write ($ data );
118
+ return $ this ->connection ->write ($ data );
115
119
}
116
120
117
121
// otherwise buffer and try to establish connection
@@ -155,26 +159,50 @@ public function handleData($data)
155
159
$ response = gPsr \parse_response ($ this ->buffer );
156
160
$ bodyChunk = (string ) $ response ->getBody ();
157
161
} catch (\InvalidArgumentException $ exception ) {
158
- $ this ->emit ('error ' , array ($ exception ));
159
- }
160
-
161
- $ this ->buffer = null ;
162
-
163
- $ this ->stream ->removeListener ('drain ' , array ($ this , 'handleDrain ' ));
164
- $ this ->stream ->removeListener ('data ' , array ($ this , 'handleData ' ));
165
- $ this ->stream ->removeListener ('end ' , array ($ this , 'handleEnd ' ));
166
- $ this ->stream ->removeListener ('error ' , array ($ this , 'handleError ' ));
167
- $ this ->stream ->removeListener ('close ' , array ($ this , 'handleClose ' ));
168
-
169
- if (!isset ($ response )) {
162
+ $ this ->closeError ($ exception );
170
163
return ;
171
164
}
172
165
173
- $ this ->stream ->on ('close ' , array ($ this , 'handleClose ' ));
166
+ // response headers successfully received => remove listeners for connection events
167
+ $ connection = $ this ->connection ;
168
+ assert ($ connection instanceof ConnectionInterface);
169
+ $ connection ->removeListener ('drain ' , array ($ this , 'handleDrain ' ));
170
+ $ connection ->removeListener ('data ' , array ($ this , 'handleData ' ));
171
+ $ connection ->removeListener ('end ' , array ($ this , 'handleEnd ' ));
172
+ $ connection ->removeListener ('error ' , array ($ this , 'handleError ' ));
173
+ $ connection ->removeListener ('close ' , array ($ this , 'close ' ));
174
+ $ this ->connection = null ;
175
+ $ this ->buffer = '' ;
176
+
177
+ // take control over connection handling and close connection once response body closes
178
+ $ that = $ this ;
179
+ $ input = $ body = new CloseProtectionStream ($ connection );
180
+ $ input ->on ('close ' , function () use ($ connection , $ that ) {
181
+ $ connection ->close ();
182
+ $ that ->close ();
183
+ });
184
+
185
+ // determine length of response body
186
+ $ length = null ;
187
+ $ code = $ response ->getStatusCode ();
188
+ if ($ this ->request ->getMethod () === 'HEAD ' || ($ code >= 100 && $ code < 200 ) || $ code == Response::STATUS_NO_CONTENT || $ code == Response::STATUS_NOT_MODIFIED ) {
189
+ $ length = 0 ;
190
+ } elseif (\strtolower ($ response ->getHeaderLine ('Transfer-Encoding ' )) === 'chunked ' ) {
191
+ $ body = new ChunkedDecoder ($ body );
192
+ } elseif ($ response ->hasHeader ('Content-Length ' )) {
193
+ $ length = (int ) $ response ->getHeaderLine ('Content-Length ' );
194
+ }
195
+ $ response = $ response ->withBody ($ body = new ReadableBodyStream ($ body , $ length ));
174
196
175
- $ this ->emit ('response ' , array ($ response , $ this ->stream ));
197
+ // emit response with streaming response body (see `Sender`)
198
+ $ this ->emit ('response ' , array ($ response , $ body ));
176
199
177
- $ this ->stream ->emit ('data ' , array ($ bodyChunk ));
200
+ // re-emit HTTP response body to trigger body parsing if parts of it are buffered
201
+ if ($ bodyChunk !== '' ) {
202
+ $ input ->handleData ($ bodyChunk );
203
+ } elseif ($ length === 0 ) {
204
+ $ input ->handleEnd ();
205
+ }
178
206
}
179
207
}
180
208
@@ -196,12 +224,6 @@ public function handleError(\Exception $error)
196
224
));
197
225
}
198
226
199
- /** @internal */
200
- public function handleClose ()
201
- {
202
- $ this ->close ();
203
- }
204
-
205
227
/** @internal */
206
228
public function closeError (\Exception $ error )
207
229
{
@@ -220,9 +242,11 @@ public function close()
220
242
221
243
$ this ->state = self ::STATE_END ;
222
244
$ this ->pendingWrites = '' ;
245
+ $ this ->buffer = '' ;
223
246
224
- if ($ this ->stream ) {
225
- $ this ->stream ->close ();
247
+ if ($ this ->connection instanceof ConnectionInterface) {
248
+ $ this ->connection ->close ();
249
+ $ this ->connection = null ;
226
250
}
227
251
228
252
$ this ->emit ('close ' );
0 commit comments