Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.

Commit e18cf51

Browse files
committed
Merging develop to master in preparation for 1.6.0 release
2 parents 2e7fce1 + efd8463 commit e18cf51

11 files changed

+109
-139
lines changed

CHANGELOG.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,44 @@
22

33
All notable changes to this project will be documented in this file, in reverse chronological order by release.
44

5+
## 1.6.0 - TBD
6+
7+
### Added
8+
9+
- Nothing.
10+
11+
### Changed
12+
13+
- [#270](https://github.com/zendframework/zend-diactoros/pull/270) changes the
14+
behavior of `Zend\Diactoros\Server`: it no longer creates an output buffer.
15+
16+
- [#270](https://github.com/zendframework/zend-diactoros/pull/270) changes the
17+
behavior of the two SAPI emitters in two backwards-incompatible ways:
18+
19+
- They no longer auto-inject a `Content-Length` header. If you need this
20+
functionality, zendframework/zend-expressive-helpers 4.1+ provides it via
21+
`Zend\Expressive\Helper\ContentLengthMiddleware`.
22+
23+
- They no longer flush the output buffer. Instead, if headers have been sent,
24+
or the output buffer exists and has a non-zero length, the emitters raise an
25+
exception, as mixed PSR-7/output buffer content creates a blocking issue.
26+
If you are emitting content via `echo`, `print`, `var_dump`, etc., or not
27+
catching PHP errors or exceptions, you will need to either fix your
28+
application to always work with a PSR-7 response, or provide your own
29+
emitters that allow mixed output mechanisms.
30+
31+
### Deprecated
32+
33+
- Nothing.
34+
35+
### Removed
36+
37+
- Nothing.
38+
39+
### Fixed
40+
41+
- Nothing.
42+
543
## 1.5.1 - TBD
644

745
### Added

doc/book/emitting-responses.md

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
If you are using a non-SAPI PHP implementation and wish to use the `Server` class, or if you do not
44
want to use the `Server` implementation but want to emit a response, this package provides an
55
interface, `Zend\Diactoros\Response\EmitterInterface`, defining a method `emit()` for emitting the
6-
response. A single implementation is currently available, `Zend\Diactoros\Response\SapiEmitter`,
7-
which will use the native PHP functions `header()` and `echo` in order to emit the response. If you
8-
are using a non-SAPI implementation, you will need to create your own `EmitterInterface`
9-
implementation.
6+
response.
7+
8+
Diactoros provides two implementations currently, both for working with
9+
traditional Server API (SAPI) implementations: `Zend\Diactoros\Response\SapiEmitter`
10+
and `Zend\Diactoros\Response\SapiStreamEmitter`. Each uses the native `header()`
11+
PHP function to emit headers, and `echo()` to emit the response body.
12+
13+
If you are using a non-SAPI implementation, you will need to create your own
14+
`EmitterInterface` implementation.
1015

1116
For example, the `SapiEmitter` implementation of the `EmitterInterface` can be used thus:
1217

@@ -16,3 +21,43 @@ $response->getBody()->write("some content\n");
1621
$emitter = new Zend\Diactoros\Response\SapiEmitter();
1722
$emitter->emit($response);
1823
```
24+
25+
## Emitting ranges of streamed files
26+
27+
The `SapiStreamEmitter` is useful when you want to emit a `Content-Range`. As an
28+
example, to stream a range of bytes from a file to a client, the client can pass
29+
the following header:
30+
31+
```http
32+
Range: bytes=1024-2047
33+
```
34+
35+
Your application would then populate the response with a `Content-Range` header:
36+
37+
```php
38+
$range = $request->getHeaderLine('range');
39+
$range = str_replace('=', ' ', $range);
40+
41+
$body = new Stream($pathToFile);
42+
$size = $body->getSize();
43+
$range .= '/' . $size;
44+
45+
$response = new Response($body);
46+
$response = $response->withHeader('Content-Range', $range);
47+
```
48+
49+
> Note: you will likely want to ensure the range specified falls within the
50+
> content size of the streamed body!
51+
52+
The `SapiStreamEmitter` detects the `Content-Range` header and emits only the
53+
bytes specified.
54+
55+
```php
56+
$emitter = new SapiStreamEmitter();
57+
$emitter->emit($response);
58+
```
59+
60+
The `SapiStreamEmitter` may be used in place of the `SapiEmitter`, even when not
61+
sending files. However, unlike the `SapiEmitter`, it will emit a chunk of
62+
content at a time instead of the full content at once, which could lead to
63+
performance overhead. The default chunk size is 8192 bytes.

src/Response/SapiEmitter.php

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,13 @@ class SapiEmitter implements EmitterInterface
2323
* body content via the output buffer.
2424
*
2525
* @param ResponseInterface $response
26-
* @param null|int $maxBufferLevel Maximum output buffering level to unwrap.
2726
*/
28-
public function emit(ResponseInterface $response, $maxBufferLevel = null)
27+
public function emit(ResponseInterface $response)
2928
{
30-
if (headers_sent()) {
31-
throw new RuntimeException('Unable to emit response; headers already sent');
32-
}
33-
34-
$response = $this->injectContentLength($response);
29+
$this->assertNoPreviousOutput();
3530

3631
$this->emitStatusLine($response);
3732
$this->emitHeaders($response);
38-
$this->flush($maxBufferLevel);
3933
$this->emitBody($response);
4034
}
4135

src/Response/SapiEmitterTrait.php

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,28 @@
1010
namespace Zend\Diactoros\Response;
1111

1212
use Psr\Http\Message\ResponseInterface;
13+
use RuntimeException;
1314

1415
trait SapiEmitterTrait
1516
{
1617
/**
17-
* Inject the Content-Length header if is not already present.
18+
* Checks to see if content has previously been sent.
1819
*
19-
* @param ResponseInterface $response
20-
* @return ResponseInterface
20+
* If either headers have been sent or the output buffer contains content,
21+
* raises an exception.
22+
*
23+
* @throws RuntimeException if headers have already been sent.
24+
* @throws RuntimeException if output is present in the output buffer.
2125
*/
22-
private function injectContentLength(ResponseInterface $response)
26+
private function assertNoPreviousOutput()
2327
{
24-
if (! $response->hasHeader('Content-Length')) {
25-
// PSR-7 indicates int OR null for the stream size; for null values,
26-
// we will not auto-inject the Content-Length.
27-
if (null !== $response->getBody()->getSize()) {
28-
return $response->withHeader('Content-Length', (string) $response->getBody()->getSize());
29-
}
28+
if (headers_sent()) {
29+
throw new RuntimeException('Unable to emit response; headers already sent');
3030
}
3131

32-
return $response;
32+
if (ob_get_level() > 0 && ob_get_length() > 0) {
33+
throw new RuntimeException('Output has been emitted previously; cannot emit response');
34+
}
3335
}
3436

3537
/**
@@ -77,23 +79,6 @@ private function emitHeaders(ResponseInterface $response)
7779
}
7880
}
7981

80-
/**
81-
* Loops through the output buffer, flushing each, before emitting
82-
* the response.
83-
*
84-
* @param int|null $maxBufferLevel Flush up to this buffer level.
85-
*/
86-
private function flush($maxBufferLevel = null)
87-
{
88-
if (null === $maxBufferLevel) {
89-
$maxBufferLevel = ob_get_level();
90-
}
91-
92-
while (ob_get_level() > $maxBufferLevel) {
93-
ob_end_flush();
94-
}
95-
}
96-
9782
/**
9883
* Filter a header name to wordcase
9984
*

src/Response/SapiStreamEmitter.php

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,13 @@ class SapiStreamEmitter implements EmitterInterface
2424
* body content via the output buffer.
2525
*
2626
* @param ResponseInterface $response
27-
* @param null|int $maxBufferLevel Maximum output buffering level to unwrap.
2827
* @param int $maxBufferLength Maximum output buffering size for each iteration
2928
*/
30-
public function emit(ResponseInterface $response, $maxBufferLevel = null, $maxBufferLength = 8192)
29+
public function emit(ResponseInterface $response, $maxBufferLength = 8192)
3130
{
32-
if (headers_sent()) {
33-
throw new RuntimeException('Unable to emit response; headers already sent');
34-
}
35-
36-
$response = $this->injectContentLength($response);
37-
31+
$this->assertNoPreviousOutput();
3832
$this->emitStatusLine($response);
3933
$this->emitHeaders($response);
40-
$this->flush($maxBufferLevel);
4134

4235
$range = $this->parseContentRange($response->getHeaderLine('Content-Range'));
4336

src/Server.php

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -150,24 +150,18 @@ public static function createServerFromRequest(
150150
* If provided a $finalHandler, that callable will be used for
151151
* incomplete requests.
152152
*
153-
* Output buffering is enabled prior to invoking the attached
154-
* callback; any output buffered will be sent prior to any
155-
* response body content.
156-
*
157153
* @param null|callable $finalHandler
158154
*/
159155
public function listen(callable $finalHandler = null)
160156
{
161157
$callback = $this->callback;
162158

163-
ob_start();
164-
$bufferLevel = ob_get_level();
165-
166159
$response = $callback($this->request, $this->response, $finalHandler);
167160
if (! $response instanceof ResponseInterface) {
168161
$response = $this->response;
169162
}
170-
$this->getEmitter()->emit($response, $bufferLevel);
163+
164+
$this->getEmitter()->emit($response);
171165
}
172166

173167
/**

test/Response/AbstractEmitterTest.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ public function testEmitsResponseHeaders()
4444
ob_end_clean();
4545
$this->assertContains('HTTP/1.1 200 OK', HeaderStack::stack());
4646
$this->assertContains('Content-Type: text/plain', HeaderStack::stack());
47-
$this->assertContains('Content-Length: 8', HeaderStack::stack());
4847
}
4948

5049
public function testEmitsMessageBody()

test/Response/SapiEmitterTest.php

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,4 @@
1313

1414
class SapiEmitterTest extends AbstractEmitterTest
1515
{
16-
public function testEmitsBufferLevel()
17-
{
18-
ob_start();
19-
echo "level" . ob_get_level() . " "; // 2
20-
ob_start();
21-
echo "level" . ob_get_level() . " "; // 3
22-
ob_start();
23-
echo "level" . ob_get_level() . " "; // 4
24-
$response = (new Response())
25-
->withStatus(200)
26-
->withAddedHeader('Content-Type', 'text/plain');
27-
$response->getBody()->write('Content!');
28-
ob_start();
29-
$this->emitter->emit($response);
30-
$this->assertSame('Content!', ob_get_contents());
31-
ob_end_clean();
32-
$this->assertSame('level4 ', ob_get_contents(), 'current buffer level string must remains after emit');
33-
ob_end_clean();
34-
$this->emitter->emit($response, 2);
35-
$this->assertSame('level2 level3 Content!', ob_get_contents(), 'must buffer until specified level');
36-
ob_end_clean();
37-
}
3816
}

test/Response/SapiStreamEmitterTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ function ($bufferLength) use (& $peakBufferLength) {
210210
->withBody($stream->reveal());
211211

212212
ob_start();
213-
$this->emitter->emit($response, null, $maxBufferLength);
213+
$this->emitter->emit($response, $maxBufferLength);
214214
$emittedContents = ob_get_clean();
215215

216216
if ($seekable) {
@@ -351,7 +351,7 @@ function ($bufferLength) use (& $peakBufferLength) {
351351
->withBody($stream->reveal());
352352

353353
ob_start();
354-
$this->emitter->emit($response, null, $maxBufferLength);
354+
$this->emitter->emit($response, $maxBufferLength);
355355
$emittedContents = ob_get_clean();
356356

357357
$stream->rewind()->shouldNotBeCalled();
@@ -497,7 +497,7 @@ function () use (& $closureTrackMemoryUsage) {
497497

498498
gc_disable();
499499

500-
$this->emitter->emit($response, null, $maxBufferLength);
500+
$this->emitter->emit($response, $maxBufferLength);
501501

502502
ob_end_flush();
503503

test/ServerIntegrationTest.php

Lines changed: 0 additions & 49 deletions
This file was deleted.

0 commit comments

Comments
 (0)