Repository: reactphp/http (php)

Issue:
EmptyBodyStream as request body not working
There seems to be a problem when attempting to pass `EmptyBodyStream` as body to HTTP requests:

```php
(new Browser())
    ->requestStreaming("GET", "https://stackoverflow.com", [], new \React\Http\Io\EmptyBodyStream())
    ->then(var_dump(...)); // never get's triggered
```

Whereas, empty resource works just fine:

```php
(new Browser())
    ->requestStreaming("GET", "https://stackoverflow.com", [], new ReadableResourceStream(tmpfile()))
    ->then(var_dump(...));
```

I have tried other servers (URLs) as well, and for some of them I get a response immediatelly, for some there is a 10s delay. Maybe the socket isn't being closed when using `EmptyBodyStream`?

---

The full example (reason I care) is that I though this would work as a simple HTTP proxy:

```php
$browser = new Browser();
$browser = $browser->withRejectErrorResponse(false);

$server = new HttpServer(
    new StreamingRequestMiddleware(),
    fn(ServerRequestInterface $req) => $browser
        ->requestStreaming($req->getMethod(), TARGET_URL, [], $req->getBody())
        ->then(fn(ResponseInterface $res) => new Response($res->getStatusCode(), [], $res->getBody())),
);
```

But it only works for requests with non-empty bodies. Otherwise `$req->getBody()` is `EmptyBodyStream` and the requests get stuck.

Pull request:
HTTP client: Fix empty streaming request body, omit `Transfer-Encoding: chunked`
This changeset fixes sending an outgoing streaming request body that is known to be empty. In this case, we do not want to send a `Content-Length: 0` or `Transfer-Encoding: chunked`. Prior to this change, we would send a `Transfer-Encoding: chunked` header without ever sending an end chunk, thus never signalling the end of the request body stream to the HTTP server. In other words, the updated version is now compliant with https://www.rfc-editor.org/rfc/rfc9112.html.

Resolves #497

Patch:
diff --git a/src/Io/Sender.php b/src/Io/Sender.php
index 3598d31..1d56389 100644
--- a/src/Io/Sender.php
+++ b/src/Io/Sender.php
@@ -90,7 +90,7 @@ class Sender
         } elseif ($size === 0 && \in_array($request->getMethod(), array('POST', 'PUT', 'PATCH'))) {
             // only assign a "Content-Length: 0" request header if the body is expected for certain methods
             $request = $request->withHeader('Content-Length', '0');
-        } elseif ($body instanceof ReadableStreamInterface && $body->isReadable() && !$request->hasHeader('Content-Length')) {
+        } elseif ($body instanceof ReadableStreamInterface && $size !== 0 && $body->isReadable() && !$request->hasHeader('Content-Length')) {
             // use "Transfer-Encoding: chunked" when this is a streaming body and body size is unknown
             $request = $request->withHeader('Transfer-Encoding', 'chunked');
         } else {


Test patch:
diff --git a/tests/Io/SenderTest.php b/tests/Io/SenderTest.php
index 3c8c476..03a9b56 100644
--- a/tests/Io/SenderTest.php
+++ b/tests/Io/SenderTest.php
@@ -5,6 +5,7 @@ namespace React\Tests\Http\Io;
 use Psr\Http\Message\RequestInterface;
 use React\Http\Client\Client as HttpClient;
 use React\Http\Io\ClientConnectionManager;
+use React\Http\Io\EmptyBodyStream;
 use React\Http\Io\ReadableBodyStream;
 use React\Http\Io\Sender;
 use React\Http\Message\Request;
@@ -264,6 +265,21 @@ class SenderTest extends TestCase
         $sender->send($request);
     }
 
+    public function testSendGetWithEmptyBodyStreamWillNotPassContentLengthOrTransferEncodingHeader()
+    {
+        $client = $this->getMockBuilder('React\Http\Client\Client')->disableOriginalConstructor()->getMock();
+        $client->expects($this->once())->method('request')->with($this->callback(function (RequestInterface $request) {
+            return !$request->hasHeader('Content-Length') && !$request->hasHeader('Transfer-Encoding');
+        }))->willReturn($this->getMockBuilder('React\Http\Io\ClientRequestStream')->disableOriginalConstructor()->getMock());
+
+        $sender = new Sender($client);
+
+        $body = new EmptyBodyStream();
+        $request = new Request('GET', 'http://www.google.com/', array(), $body);
+
+        $sender->send($request);
+    }
+
     public function testSendCustomMethodWillNotPassContentLengthHeaderForEmptyRequestBody()
     {
         $client = $this->getMockBuilder('React\Http\Client\Client')->disableOriginalConstructor()->getMock();