From d975c0da097e56ee97f4dd2a6fc752f018c5102a Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Wed, 31 Mar 2021 20:13:43 +0200 Subject: [PATCH 1/3] Add new async method Response::transmit() replacing Response::transfer() --- src/main/php/web/Response.class.php | 25 +++++++++++++ .../php/web/unittest/ResponseTest.class.php | 37 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/main/php/web/Response.class.php b/src/main/php/web/Response.class.php index d23776d9..40e46398 100755 --- a/src/main/php/web/Response.class.php +++ b/src/main/php/web/Response.class.php @@ -170,6 +170,7 @@ public function hint($status, $message= null, $headers= []) { /** * Transfers a stream * + * @deprecated Use `yield from $this->transmit(...)` instead! * @param io.streams.InputStream $in * @param string $mediaType * @param int $size If omitted, uses chunked transfer encoding @@ -188,11 +189,35 @@ public function transfer($in, $mediaType= 'application/octet-stream', $size= nul } } + /** + * Transmits a stream + * + * @param io.streams.InputStream $in + * @param string $mediaType + * @param int $size If omitted, uses chunked transfer encoding + * @return iterable + */ + public function transmit($in, $mediaType= 'application/octet-stream', $size= null) { + $this->headers['Content-Type']= [$mediaType]; + + $out= $this->stream($size); + try { + while ($in->available()) { + $out->write($in->read()); + yield; + } + } finally { + $out->close(); + $in->close(); + } + } + /** * Sends some content * * @param string $content * @param string $mediaType + * @return void */ public function send($content, $mediaType= 'text/html') { $this->headers['Content-Type']= [$mediaType]; diff --git a/src/test/php/web/unittest/ResponseTest.class.php b/src/test/php/web/unittest/ResponseTest.class.php index a78baf4f..a6e2bed7 100755 --- a/src/test/php/web/unittest/ResponseTest.class.php +++ b/src/test/php/web/unittest/ResponseTest.class.php @@ -203,6 +203,43 @@ public function transfer_stream_buffered() { ); } + #[Test] + public function transmit_stream_with_length() { + $res= new Response(new TestOutput()); + foreach ($res->transmit(new MemoryInputStream('

Test

'), 'text/html', 13) as $_) { } + + $this->assertResponse( + "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 13\r\n\r\n". + "

Test

", + $res + ); + } + + #[Test] + public function transmit_stream_chunked() { + $res= new Response(new TestOutput()); + foreach ($res->transmit(new MemoryInputStream('

Test

'), 'text/html') as $_) { } + + $this->assertResponse( + "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n". + "d\r\n

Test

\r\n0\r\n\r\n", + $res + ); + } + + #[Test] + public function transmit_stream_buffered() { + $res= new Response((new TestOutput())->using(Buffered::class)); + foreach ($res->transmit(new MemoryInputStream('

Test

'), 'text/html') as $_) { } + + $this->assertResponse( + "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 13\r\n\r\n". + "

Test

", + $res + ); + } + + #[Test] public function cookies_and_headers_are_merged() { $res= new Response(new TestOutput()); From 71815cb10774d190ca39e0497e388d9e4ab2fd68 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Wed, 31 Mar 2021 20:21:36 +0200 Subject: [PATCH 2/3] Change transmit() to also accept io.Channel instances --- src/main/php/web/Response.class.php | 18 +++++++++++++---- .../php/web/unittest/ResponseTest.class.php | 20 +++++++++++++++++-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/main/php/web/Response.class.php b/src/main/php/web/Response.class.php index 40e46398..9029e28b 100755 --- a/src/main/php/web/Response.class.php +++ b/src/main/php/web/Response.class.php @@ -1,5 +1,7 @@ headers['Content-Type']= [$mediaType]; + public function transmit($source, $mediaType= 'application/octet-stream', $size= null) { + if ($source instanceof InputStream) { + $in= $source; + } else if ($source instanceof Channel) { + $in= $source->in(); + } else { + throw new IllegalArgumentException('Expected either a channel or an input stream, have '.typeof($source)); + } + $this->headers['Content-Type']= [$mediaType]; $out= $this->stream($size); try { while ($in->available()) { diff --git a/src/test/php/web/unittest/ResponseTest.class.php b/src/test/php/web/unittest/ResponseTest.class.php index a6e2bed7..b3b13a8d 100755 --- a/src/test/php/web/unittest/ResponseTest.class.php +++ b/src/test/php/web/unittest/ResponseTest.class.php @@ -1,12 +1,13 @@ Test'); } + public function out() { /* Not implemented */ } + }; + foreach ($res->transmit($channel, 'text/html', 13) as $_) { } + + $this->assertResponse( + "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 13\r\n\r\n". + "

Test

", + $res + ); + } #[Test] public function cookies_and_headers_are_merged() { From d5a8756c9b66627d08a9e5e817773ed90cc07d36 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Wed, 31 Mar 2021 20:24:49 +0200 Subject: [PATCH 3/3] Test calling transmit() with NULL values --- src/main/php/web/Response.class.php | 2 +- src/test/php/web/unittest/ResponseTest.class.php | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/php/web/Response.class.php b/src/main/php/web/Response.class.php index 9029e28b..a369eeab 100755 --- a/src/main/php/web/Response.class.php +++ b/src/main/php/web/Response.class.php @@ -2,7 +2,7 @@ use io\Channel; use io\streams\InputStream; -use lang\IllegalStateException; +use lang\{IllegalStateException, IllegalArgumentException}; use web\io\WriteChunks; /** diff --git a/src/test/php/web/unittest/ResponseTest.class.php b/src/test/php/web/unittest/ResponseTest.class.php index b3b13a8d..7ffae488 100755 --- a/src/test/php/web/unittest/ResponseTest.class.php +++ b/src/test/php/web/unittest/ResponseTest.class.php @@ -2,7 +2,8 @@ use io\Channel; use io\streams\MemoryInputStream; -use unittest\{Test, Values, TestCase}; +use lang\IllegalArgumentException; +use unittest\{Test, Expect, Values, TestCase}; use util\URI; use web\io\{Buffered, TestOutput}; use web\{Cookie, Response}; @@ -256,6 +257,12 @@ public function out() { /* Not implemented */ } ); } + #[Test, Expect(IllegalArgumentException::class)] + public function transmit_null() { + $res= new Response(new TestOutput()); + foreach ($res->transmit(null) as $_) { } + } + #[Test] public function cookies_and_headers_are_merged() { $res= new Response(new TestOutput());