Skip to content

Commit 9bb244e

Browse files
committed
Improve Eventsource interface + dunglas comments
1 parent b88c4ca commit 9bb244e

File tree

8 files changed

+260
-206
lines changed

8 files changed

+260
-206
lines changed

src/Symfony/Component/HttpClient/Chunk/MessageChunk.php

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

src/Symfony/Component/HttpClient/EventSourceHttpClient.php

Lines changed: 0 additions & 97 deletions
This file was deleted.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
namespace Symfony\Component\HttpClient\ServerSentEvents;
4+
5+
use Symfony\Component\HttpClient\HttpClient;
6+
use Symfony\Contracts\HttpClient\HttpClientInterface;
7+
8+
final class EventSource implements EventSourceInterface
9+
{
10+
private $url;
11+
private $client;
12+
private $reconnectionTime = 5;
13+
private $shouldClose = false;
14+
private $lastEventId = null;
15+
private $response = null;
16+
17+
const CONNECTING = 0;
18+
const OPEN = 1;
19+
const CLOSED = 2;
20+
21+
public $readyState = self::CLOSED;
22+
23+
public function __construct(string $url, array $defaultOptions = [], HttpClientInterface $client = null)
24+
{
25+
$this->url = $url;
26+
$this->client = $client ?: HttpClient::create();
27+
}
28+
29+
public function getHttpClient(): HttpClientInterface
30+
{
31+
return $this->client;
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function getMessages(): \Iterator
38+
{
39+
while (false === $this->shouldClose) {
40+
$response = $this->request();
41+
$buffer = '';
42+
foreach ($this->client->stream($response, $this->reconnectionTime) as $chunk) {
43+
if ($chunk->isTimeout()) {
44+
break;
45+
}
46+
47+
// We have to gather our own Buffer that may be longer then a chunk
48+
$buffer .= $chunk->getContent();
49+
50+
if (!$buffer) {
51+
// var_dump('closed');
52+
// connection closed, should we throw ?
53+
return null;
54+
}
55+
56+
if (self::OPEN !== $this->readyState) {
57+
$this->readyState = self::OPEN;
58+
}
59+
60+
// If the line starts with a U+003A COLON character (':') ignore the line
61+
if (':' === $buffer[0]) {
62+
$buffer = '';
63+
continue;
64+
}
65+
66+
// Process if we got new line ending
67+
$end = substr($buffer, -1);
68+
if ("\n" !== $end && "\r" !== $end) {
69+
continue;
70+
}
71+
72+
$message = MessageEvent::parse($buffer);
73+
if (null !== $message->getId()) {
74+
$this->requestArgs[2]['headers']['Last-Event-ID'] = $message->getId();
75+
}
76+
77+
$buffer = '';
78+
79+
if (null !== $message->getRetry()) {
80+
$this->reconnectionTime = $message->retry;
81+
}
82+
83+
yield $message;
84+
}
85+
}
86+
}
87+
88+
public function close()
89+
{
90+
$this->shouldClose = true;
91+
}
92+
93+
private function request()
94+
{
95+
$this->readyState = self::CONNECTING;
96+
$response = $this->client->request('GET', $this->url, ['headers' => [
97+
'Accept' => 'text/event-stream',
98+
'Cache-Control' => 'no-store',
99+
]]);
100+
101+
if (200 !== $response->getStatusCode()) {
102+
throw new \Exception('Could not request ');
103+
}
104+
105+
return $response;
106+
}
107+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Symfony\Component\HttpClient\ServerSentEvents;
4+
5+
use Symfony\Contracts\HttpClient\HttpClientInterface;
6+
7+
interface EventSourceInterface
8+
{
9+
public function getHttpClient(): HttpClientInterface;
10+
11+
/**
12+
* @return \Iterator|MessageEvent[]
13+
*/
14+
public function getMessages(): \Iterator;
15+
16+
public function close();
17+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
namespace Symfony\Component\HttpClient\ServerSentEvents;
4+
5+
final class MessageEvent
6+
{
7+
private $data;
8+
private $id;
9+
private $type;
10+
private $retry;
11+
12+
public function __construct(string $data = '', string $id = null, string $type = 'message', int $retry = null)
13+
{
14+
$this->data = $data;
15+
$this->id = $id;
16+
$this->type = $type;
17+
$this->retry = $retry;
18+
}
19+
20+
public static function parse(string $rawData): self
21+
{
22+
preg_match_all('/^([a-z]*)\:? ?(.*)/m', $rawData, $matches, PREG_SET_ORDER);
23+
$data = '';
24+
$retry = $id = null;
25+
$type = 'message';
26+
27+
foreach ($matches as $match) {
28+
switch ($match[1]) {
29+
case 'id':
30+
$id = $match[2];
31+
break;
32+
case 'event':
33+
$type = $match[2];
34+
break;
35+
case 'data':
36+
$data .= $match[2]."\n";
37+
break;
38+
case 'retry':
39+
$retry = ctype_digit($match[2]) ? (int) $match[2] : null;
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, $type, $retry);
49+
}
50+
51+
public function getId(): ?int
52+
{
53+
return $this->id;
54+
}
55+
56+
public function getType(): ?string
57+
{
58+
return $this->type;
59+
}
60+
61+
public function getData(): string
62+
{
63+
return $this->data;
64+
}
65+
66+
public function getRetry(): ?int
67+
{
68+
return $this->retry;
69+
}
70+
}

src/Symfony/Component/HttpClient/Tests/Chunk/MessageChunkTest.php

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

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