Skip to content

Commit 9c522a9

Browse files
committed
Move the code from pr#455
1 parent a7c4179 commit 9c522a9

File tree

4 files changed

+318
-2
lines changed

4 files changed

+318
-2
lines changed

src/Server/Response.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
namespace Hyperf\HttpMessage\Server;
1414

1515
use Hyperf\HttpMessage\Cookie\Cookie;
16+
use Hyperf\HttpMessage\Stream\FileInterface;
1617
use Hyperf\HttpMessage\Stream\SwooleStream;
1718

1819
class Response extends \Hyperf\HttpMessage\Base\Response
@@ -45,8 +46,11 @@ public function send()
4546
}
4647

4748
$this->buildSwooleResponse($this->swooleResponse, $this);
48-
49-
$this->swooleResponse->end($this->getBody()->getContents());
49+
$content = $this->getBody();
50+
if ($content instanceof FileInterface) {
51+
return $this->swooleResponse->sendfile($content->getFilename());
52+
}
53+
$this->swooleResponse->end($content->getContents());
5054
}
5155

5256
/**

src/Stream/FileInterface.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of Hyperf.
6+
*
7+
* @link https://www.hyperf.io
8+
* @document https://doc.hyperf.io
9+
* @contact group@hyperf.io
10+
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
11+
*/
12+
13+
namespace Hyperf\HttpMessage\Stream;
14+
15+
interface FileInterface
16+
{
17+
public function getFilename(): string;
18+
}

src/Stream/SwooleFileStream.php

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of Hyperf.
6+
*
7+
* @link https://www.hyperf.io
8+
* @document https://doc.hyperf.io
9+
* @contact group@hyperf.io
10+
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
11+
*/
12+
13+
namespace Hyperf\HttpMessage\Stream;
14+
15+
use Hyperf\HttpServer\Exception\Http\FileException;
16+
use Psr\Http\Message\StreamInterface;
17+
18+
class SwooleFileStream implements StreamInterface, FileInterface
19+
{
20+
/**
21+
* @var string
22+
*/
23+
protected $size;
24+
25+
/**
26+
* @var \SplFileInfo
27+
*/
28+
protected $file;
29+
30+
/**
31+
* SwooleFileStream constructor.
32+
*
33+
* @param string|\SplFileInfo $file
34+
*/
35+
public function __construct($file)
36+
{
37+
if (! $file instanceof \SplFileInfo) {
38+
$file = new \SplFileInfo($file);
39+
}
40+
if (! $file->isReadable()) {
41+
throw new FileException('File must be readable.');
42+
}
43+
$this->file = $file;
44+
$this->size = $file->getSize();
45+
}
46+
47+
/**
48+
* Reads all data from the stream into a string, from the beginning to end.
49+
* This method MUST attempt to seek to the beginning of the stream before
50+
* reading data and read the stream until the end is reached.
51+
* Warning: This could attempt to load a large amount of data into memory.
52+
* This method MUST NOT raise an exception in order to conform with PHP's
53+
* string casting operations.
54+
*
55+
* @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
56+
* @return string
57+
*/
58+
public function __toString()
59+
{
60+
try {
61+
return $this->getContents();
62+
} catch (\Throwable $e) {
63+
return '';
64+
}
65+
}
66+
67+
/**
68+
* Closes the stream and any underlying resources.
69+
*/
70+
public function close()
71+
{
72+
throw new \BadMethodCallException('Not implemented');
73+
}
74+
75+
/**
76+
* Separates any underlying resources from the stream.
77+
* After the stream has been detached, the stream is in an unusable state.
78+
*
79+
* @return null|resource Underlying PHP stream, if any
80+
*/
81+
public function detach()
82+
{
83+
throw new \BadMethodCallException('Not implemented');
84+
}
85+
86+
/**
87+
* Get the size of the stream if known.
88+
*
89+
* @return null|int returns the size in bytes if known, or null if unknown
90+
*/
91+
public function getSize()
92+
{
93+
if (! $this->size) {
94+
$this->size = filesize($this->getContents());
95+
}
96+
return $this->size;
97+
}
98+
99+
/**
100+
* Returns the current position of the file read/write pointer.
101+
*
102+
* @throws \RuntimeException on error
103+
* @return int Position of the file pointer
104+
*/
105+
public function tell()
106+
{
107+
throw new \BadMethodCallException('Not implemented');
108+
}
109+
110+
/**
111+
* Returns true if the stream is at the end of the stream.
112+
*
113+
* @return bool
114+
*/
115+
public function eof()
116+
{
117+
throw new \BadMethodCallException('Not implemented');
118+
}
119+
120+
/**
121+
* Returns whether or not the stream is seekable.
122+
*
123+
* @return bool
124+
*/
125+
public function isSeekable()
126+
{
127+
throw new \BadMethodCallException('Not implemented');
128+
}
129+
130+
/**
131+
* Seek to a position in the stream.
132+
*
133+
* @see http://www.php.net/manual/en/function.fseek.php
134+
* @param int $offset Stream offset
135+
* @param int $whence Specifies how the cursor position will be calculated
136+
* based on the seek offset. Valid values are identical to the built-in
137+
* PHP $whence values for `fseek()`. SEEK_SET: Set position equal to
138+
* offset bytes SEEK_CUR: Set position to current location plus offset
139+
* SEEK_END: Set position to end-of-stream plus offset.
140+
* @throws \RuntimeException on failure
141+
*/
142+
public function seek($offset, $whence = SEEK_SET)
143+
{
144+
throw new \BadMethodCallException('Not implemented');
145+
}
146+
147+
/**
148+
* Seek to the beginning of the stream.
149+
* If the stream is not seekable, this method will raise an exception;
150+
* otherwise, it will perform a seek(0).
151+
*
152+
* @throws \RuntimeException on failure
153+
* @see http://www.php.net/manual/en/function.fseek.php
154+
* @see seek()
155+
*/
156+
public function rewind()
157+
{
158+
throw new \BadMethodCallException('Not implemented');
159+
}
160+
161+
/**
162+
* Returns whether or not the stream is writable.
163+
*
164+
* @return bool
165+
*/
166+
public function isWritable()
167+
{
168+
return false;
169+
}
170+
171+
/**
172+
* Write data to the stream.
173+
*
174+
* @param string $string the string that is to be written
175+
* @throws \RuntimeException on failure
176+
* @return int returns the number of bytes written to the stream
177+
*/
178+
public function write($string)
179+
{
180+
throw new \BadMethodCallException('Not implemented');
181+
}
182+
183+
/**
184+
* Returns whether or not the stream is readable.
185+
*
186+
* @return bool
187+
*/
188+
public function isReadable()
189+
{
190+
return true;
191+
}
192+
193+
/**
194+
* Read data from the stream.
195+
*
196+
* @param int $length Read up to $length bytes from the object and return
197+
* them. Fewer than $length bytes may be returned if underlying stream
198+
* call returns fewer bytes.
199+
* @throws \RuntimeException if an error occurs
200+
* @return string returns the data read from the stream, or an empty string
201+
* if no bytes are available
202+
*/
203+
public function read($length)
204+
{
205+
throw new \BadMethodCallException('Not implemented');
206+
}
207+
208+
/**
209+
* Returns the remaining contents in a string.
210+
*
211+
* @throws \RuntimeException if unable to read or an error occurs while
212+
* reading
213+
* @return string
214+
*/
215+
public function getContents()
216+
{
217+
return $this->getFilename();
218+
}
219+
220+
/**
221+
* Get stream metadata as an associative array or retrieve a specific key.
222+
* The keys returned are identical to the keys returned from PHP's
223+
* stream_get_meta_data() function.
224+
*
225+
* @see http://php.net/manual/en/function.stream-get-meta-data.php
226+
* @param string $key specific metadata to retrieve
227+
* @return null|array|mixed Returns an associative array if no key is
228+
* provided. Returns a specific key value if a key is provided and the
229+
* value is found, or null if the key is not found.
230+
*/
231+
public function getMetadata($key = null)
232+
{
233+
throw new \BadMethodCallException('Not implemented');
234+
}
235+
236+
public function getFilename(): string
237+
{
238+
return $this->file->getPathname();
239+
}
240+
}

tests/SwooleStreamTest.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of Hyperf.
6+
*
7+
* @link https://www.hyperf.io
8+
* @document https://doc.hyperf.io
9+
* @contact group@hyperf.io
10+
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
11+
*/
12+
13+
namespace HyperfTest\HttpMessage;
14+
15+
use Hyperf\HttpMessage\Server\Response;
16+
use Hyperf\HttpMessage\Stream\SwooleFileStream;
17+
use Hyperf\HttpMessage\Stream\SwooleStream;
18+
use Mockery;
19+
use PHPUnit\Framework\TestCase;
20+
use Swoole\Http\Response as SwooleResponse;
21+
22+
/**
23+
* @internal
24+
* @coversNothing
25+
*/
26+
class SwooleStreamTest extends TestCase
27+
{
28+
public function testSwooleFileStream()
29+
{
30+
$swooleResponse = Mockery::mock(SwooleResponse::class);
31+
$file = __FILE__;
32+
$swooleResponse->shouldReceive('sendfile')->with($file)->once()->andReturn(null);
33+
$swooleResponse->shouldReceive('status')->with(Mockery::any())->once()->andReturn(200);
34+
35+
$response = new Response($swooleResponse);
36+
$response = $response->withBody(new SwooleFileStream($file));
37+
38+
$this->assertSame(null, $response->send());
39+
}
40+
41+
public function testSwooleStream()
42+
{
43+
$swooleResponse = Mockery::mock(SwooleResponse::class);
44+
$content = '{"id":1}';
45+
$swooleResponse->shouldReceive('end')->with($content)->once()->andReturn(null);
46+
$swooleResponse->shouldReceive('status')->with(Mockery::any())->once()->andReturn(200);
47+
$swooleResponse->shouldReceive('header')->with('TOKEN', 'xxx')->once()->andReturn(null);
48+
49+
$response = new Response($swooleResponse);
50+
$response = $response->withBody(new SwooleStream($content))->withHeader('TOKEN', 'xxx');
51+
52+
$this->assertSame(null, $response->send());
53+
}
54+
}

0 commit comments

Comments
 (0)