Skip to content

Commit

Permalink
Merge pull request #73 from xp-forge/feature/transmit
Browse files Browse the repository at this point in the history
Add new async method Response::transmit() replacing Response::transfer()
  • Loading branch information
thekid authored Mar 31, 2021
2 parents 291855e + d5a8756 commit ad1840b
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 3 deletions.
37 changes: 36 additions & 1 deletion src/main/php/web/Response.class.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php namespace web;

use lang\IllegalStateException;
use io\Channel;
use io\streams\InputStream;
use lang\{IllegalStateException, IllegalArgumentException};
use web\io\WriteChunks;

/**
Expand Down Expand Up @@ -170,6 +172,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
Expand All @@ -188,11 +191,43 @@ public function transfer($in, $mediaType= 'application/octet-stream', $size= nul
}
}

/**
* Transmits a given source to the output asynchronously.
*
* @param io.Channel|io.streams.InputStream $source
* @param string $mediaType
* @param int $size If omitted, uses chunked transfer encoding
* @return iterable
* @throws lang.IllegalArgumentException
*/
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()) {
$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];
Expand Down
64 changes: 62 additions & 2 deletions src/test/php/web/unittest/ResponseTest.class.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<?php namespace web\unittest;

use io\Channel;
use io\streams\MemoryInputStream;
use unittest\{Test, Values};
use lang\IllegalArgumentException;
use unittest\{Test, Expect, Values, TestCase};
use util\URI;
use web\io\{Buffered, TestOutput};
use web\{Cookie, Response};

class ResponseTest extends \unittest\TestCase {
class ResponseTest extends TestCase {

/**
* Assertion helper
Expand Down Expand Up @@ -203,6 +205,64 @@ public function transfer_stream_buffered() {
);
}

#[Test]
public function transmit_stream_with_length() {
$res= new Response(new TestOutput());
foreach ($res->transmit(new MemoryInputStream('<h1>Test</h1>'), 'text/html', 13) as $_) { }

$this->assertResponse(
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 13\r\n\r\n".
"<h1>Test</h1>",
$res
);
}

#[Test]
public function transmit_stream_chunked() {
$res= new Response(new TestOutput());
foreach ($res->transmit(new MemoryInputStream('<h1>Test</h1>'), '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<h1>Test</h1>\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('<h1>Test</h1>'), 'text/html') as $_) { }

$this->assertResponse(
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 13\r\n\r\n".
"<h1>Test</h1>",
$res
);
}

#[Test]
public function transmit_channel() {
$res= new Response(new TestOutput());
$channel= new class() implements Channel {
public function in() { return new MemoryInputStream('<h1>Test</h1>'); }
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".
"<h1>Test</h1>",
$res
);
}

#[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());
Expand Down

0 comments on commit ad1840b

Please sign in to comment.