Skip to content

Commit b88c4ca

Browse files
committed
Implement chunk generator
1 parent c6983fe commit b88c4ca

File tree

6 files changed

+233
-166
lines changed

6 files changed

+233
-166
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Symfony\Component\HttpClient\Chunk;
4+
5+
final class MessageChunk
6+
{
7+
public $data;
8+
public $id;
9+
public $event;
10+
public $retry;
11+
12+
public function __construct($data = '', $id = null, $event = '', $retry = -1)
13+
{
14+
$this->data = $data;
15+
$this->id = $id;
16+
$this->event = $event;
17+
$this->retry = $retry;
18+
}
19+
20+
public static function parse($rawData): ?self
21+
{
22+
preg_match_all('/^([a-z]*)\: ?(.*)/m', $rawData, $matches, PREG_SET_ORDER);
23+
$event = $data = '';
24+
$id = null;
25+
$retry = -1;
26+
27+
foreach ($matches as $match) {
28+
switch ($match[1]) {
29+
case 'id':
30+
$id = $match[2];
31+
break;
32+
case 'event':
33+
$event = $match[2];
34+
break;
35+
case 'data':
36+
$data .= $match[2]."\n";
37+
break;
38+
case 'retry':
39+
$retry = is_numeric($match[2]) ? (int) ($match[2]) : -1;
40+
break;
41+
}
42+
}
43+
44+
if ("\n" === substr($data, -1)) {
45+
$data = substr($data, 0, -1);
46+
}
47+
48+
return new self($data, $id, $event, $retry);
49+
}
50+
}

src/Symfony/Component/HttpClient/EventSource.php

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

src/Symfony/Component/HttpClient/EventSource/MessageEvent.php

Lines changed: 0 additions & 45 deletions
This file was deleted.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
namespace Symfony\Component\HttpClient;
4+
5+
use Symfony\Component\HttpClient\Chunk\MessageChunk;
6+
use Symfony\Contracts\HttpClient\HttpClientInterface;
7+
use Symfony\Contracts\HttpClient\ResponseInterface;
8+
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
9+
10+
final class EventSourceHttpClient implements HttpClientInterface
11+
{
12+
use HttpClientTrait;
13+
14+
private $client;
15+
private $reconnectionTime = 30;
16+
private $shouldDisconnect = false;
17+
private $lastEventId = -1;
18+
private $defaultOptions = self::OPTIONS_DEFAULTS;
19+
20+
public function __construct(HttpClientInterface $client = null, array $defaultOptions = [])
21+
{
22+
$this->client = $client ?: HttpClient::create();
23+
$this->defaultOptions['headers'] = [
24+
'Accept' => 'text/event-stream',
25+
'Cache-Control' => 'no-store',
26+
];
27+
28+
if ($defaultOptions) {
29+
[, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions);
30+
}
31+
}
32+
33+
/**
34+
* {@inheritdoc}
35+
*/
36+
public function request(string $method, string $url, array $options = []): ResponseInterface
37+
{
38+
if (-1 !== $this->lastEventId) {
39+
$headers['Last-Event-ID'] = $this->lastEventId;
40+
}
41+
42+
return $this->client->request($method, $url, $options);
43+
}
44+
45+
/**
46+
* {@inheritdoc}
47+
*/
48+
public function stream($responses, float $timeout = null): ResponseStreamInterface
49+
{
50+
return $this->client->stream($responses, $timeout);
51+
}
52+
53+
public function messages($response): \Iterator
54+
{
55+
$buffer = '';
56+
foreach ($this->stream($response, $this->reconnectionTime) as $chunk) {
57+
if ($this->shouldDisconnect || $chunk->isTimeout()) {
58+
if (false === $this->shouldDisconnect) {
59+
// @TODO handle reconnection?
60+
// $response = $this->request()
61+
}
62+
63+
return null;
64+
}
65+
66+
// We have to gather our own Buffer that may be longer then a chunk
67+
$buffer .= $chunk->getContent();
68+
69+
if (!$buffer) {
70+
// connection closed, should we throw ?
71+
return null;
72+
}
73+
74+
// If the line starts with a U+003A COLON character (':') ignore the line
75+
if (':' === $buffer[0]) {
76+
$buffer = '';
77+
continue;
78+
}
79+
80+
// Process if we got new line ending
81+
$end = substr($buffer, -1);
82+
if ("\n" !== $end && "\r" !== $end) {
83+
continue;
84+
}
85+
86+
$message = MessageChunk::parse($buffer);
87+
$this->lastEventId = $message->id;
88+
$buffer = '';
89+
90+
if (-1 !== $message->retry) {
91+
$this->reconnectionTime = $message->retry;
92+
}
93+
94+
yield $message;
95+
}
96+
}
97+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpClient\Tests;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpClient\Chunk\MessageChunk;
16+
17+
class MessageChunkTest extends TestCase
18+
{
19+
public function testParse()
20+
{
21+
$rawData = <<<XML
22+
data: test
23+
data:test
24+
id: 12
25+
event: testEvent
26+
27+
XML;
28+
$this->assertEquals(new MessageChunk("test\ntest", 12, 'testEvent'), MessageChunk::parse($rawData));
29+
}
30+
31+
public function testParseValid()
32+
{
33+
$rawData = <<<XML
34+
event: testEvent
35+
data
36+
37+
XML;
38+
$this->assertEquals(new MessageChunk('', null, 'testEvent'), MessageChunk::parse($rawData));
39+
}
40+
41+
public function testParseRetry()
42+
{
43+
$rawData = <<<XML
44+
retry: 12
45+
XML;
46+
$this->assertEquals(new MessageChunk('', null, '', 12), MessageChunk::parse($rawData));
47+
}
48+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy