Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 238 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
[![CI status](https://github.com/clue/reactphp-eventsource/actions/workflows/ci.yml/badge.svg)](https://github.com/clue/reactphp-eventsource/actions)
[![installs on Packagist](https://img.shields.io/packagist/dt/clue/reactphp-eventsource?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/reactphp-eventsource)

Event-driven EventSource client, receiving streaming messages from any HTML5 Server-Sent Events (SSE) server,
built on top of [ReactPHP](https://reactphp.org/).
Instant real-time updates. Lightweight EventSource client receiving live
messages via HTML5 Server-Sent Events (SSE). Fast stream processing built on top
of [ReactPHP](https://reactphp.org/)'s event-driven architecture.

> Note: This project is in early alpha stage! Feel free to report any issues you encounter.

Expand All @@ -13,25 +14,48 @@ built on top of [ReactPHP](https://reactphp.org/).
* [Quickstart example](#quickstart-example)
* [Usage](#usage)
* [EventSource](#eventsource)
* [message event](#message-event)
* [open event](#open-event)
* [error event](#error-event)
* [EventSource::$readyState](#eventsourcereadystate)
* [EventSource::$url](#eventsourceurl)
* [close()](#close)
* [MessageEvent](#messageevent)
* [MessageEvent::$data](#messageeventdata)
* [MessageEvent::$lastEventId](#messageeventlasteventid)
* [MessageEvent::$type](#messageeventtype)
* [Install](#install)
* [Tests](#tests)
* [License](#license)
* [More](#more)

## Quickstart example

Once [installed](#install), you can use the following code to stream messages
from any Server-Sent Events (SSE) server endpoint:

```
data: {"name":"Alice","message":"Hello everybody!"}

data: {"name":"Bob","message":"Hey Alice!"}

data: {"name":"Carol","message":"Nice to see you Alice!"}

data: {"name":"Alice","message":"What a lovely chat!"}

data: {"name":"Bob","message":"Yeah, all powered by ReactPHP, such an awesome piece of technology :)"}
```

```php
$es = new Clue\React\EventSource\EventSource('https://example.com/stream.php');

$es->on('message', function (Clue\React\EventSource\MessageEvent $message) {
//$data = json_decode($message->data);
var_dump($message);
$json = json_decode($message->data);
echo $json->name . ': ' . $json->message . PHP_EOL;
});
```

See the [examples](examples).
See the [examples](examples/).

## Usage

Expand Down Expand Up @@ -77,6 +101,205 @@ here in order to use the [default loop](https://github.com/reactphp/event-loop#l
This value SHOULD NOT be given unless you're sure you want to explicitly use a
given event loop instance.

#### message event

The `message` event will be emitted whenever an EventSource message is received.

```php
$es->on('message', function (Clue\React\EventSource\MessageEvent $message) {
// $json = json_decode($message->data);
var_dump($message);
});
```

The EventSource stream may emit any number of messages over its lifetime. Each
`message` event will receive a [`MessageEvent` object](#messageevent).

The [`MessageEvent::$data` property](#messageeventdata) can be used to access
the message payload data. It is commonly used for transporting structured data
such as JSON:

```
data: {"name":"Alice","age":30}

data: {"name":"Bob","age":50}
```
```php
$es->on('message', function (Clue\React\EventSource\MessageEvent $message) {
$json = json_decode($message->data);
echo "{$json->name} is {$json->age} years old" . PHP_EOL;
});
```

The EventSource stream may specify an event type for each incoming message. This
`event` field can be used to emit appropriate event types like this:

```
data: Alice
event: join

data: Hello!
event: chat

data: Bob
event: leave
```
```php
$es->on('join', function (Clue\React\EventSource\MessageEvent $message) {
echo $message->data . ' joined' . PHP_EOL;
});

$es->on('chat', function (Clue\React\EventSource\MessageEvent $message) {
echo 'Message: ' . $message->data . PHP_EOL;
});

$es->on('leave', function (Clue\React\EventSource\MessageEvent $message) {
echo $message->data . ' left' . PHP_EOL;
});
```

See also [`MessageEvent::$type` property](#messageeventtype) for more details.

#### open event

The `open` event will be emitted when the EventSource connection is successfully established.

```php
$es->on('open', function () {
echo 'Connection opened' . PHP_EOL;
});
```

Once the EventSource connection is open, it may emit any number of
[`message` events](#message-event).

If the connection can not be opened successfully, it will emit an
[`error` event](#error-event) instead.

#### error event

The `error` event will be emitted when the EventSource connection fails.
The event receives a single `Exception` argument for the error instance.

```php
$redis->on('error', function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```

The EventSource connection will be retried automatically when it is temporarily
disconnected. If the server sends a non-successful HTTP status code or an
invalid `Content-Type` response header, the connection will fail permanently.

```php
$es->on('error', function (Exception $e) use ($es) {
if ($es->readyState === Clue\React\EventSource\EventSource::CLOSED) {
echo 'Permanent error: ' . $e->getMessage() . PHP_EOL;
} else {
echo 'Temporary error: ' . $e->getMessage() . PHP_EOL;
}
});
```

See also the [`EventSource::$readyState` property](#eventsourcereadystate).

#### EventSource::$readyState

The `int $readyState` property can be used to
check the current EventSource connection state.

The state is read-only and can be in one of three states over its lifetime:

* `EventSource::CONNECTING`
* `EventSource::OPEN`
* `EventSource::CLOSED`

#### EventSource::$url

The `readonly string $url` property can be used to
get the EventSource URL as given to the constructor.

#### close()

The `close(): void` method can be used to
forcefully close the EventSource connection.

This will close any active connections or connection attempts and go into the
`EventSource::CLOSED` state.

### MessageEvent

The `MessageEvent` class represents an incoming EventSource message.

#### MessageEvent::$data

The `readonly string $data` property can be used to
access the message payload data.

```
data: hello
```
```php
assert($message->data === 'hello');
```

The `data` field may also span multiple lines. This is commonly used for
transporting structured data such as JSON:

```
data: {
data: "message": "hello"
data: }
```
```php
$json = json_decode($message->data);
assert($json->message === 'hello');
```

If the message does not contain a `data` field or the `data` field is empty, the
message will be discarded without emitting an event.

#### MessageEvent::$lastEventId

The `readonly string $lastEventId` property can be used to
access the last event ID.

```
data: hello
id: 1
```
```php
assert($message->data === 'hello');
assert($message->lastEventId === '1');
```

Internally, the `id` field will automatically be used as the `Last-Event-ID` HTTP
request header in case the connection is interrupted.

If the message does not contain an `id` field, the `$lastEventId` property will
be the value of the last ID received. If no previous message contained an ID, it
will default to an empty string.

#### MessageEvent::$type

The `readonly string $type` property can be used to
access the message event type.

```
data: Alice
event: join
```
```php
assert($message->data === 'Alice');
assert($message->type === 'join');
```

Internally, the `event` field will be used to emit the appropriate event type.
See also [`message` event](#message-event).

If the message does not contain a `event` field or the `event` field is empty,
the `$type` property will default to `message`.

## Install

The recommended way to install this library is [through Composer](https://getcomposer.org/).
Expand Down Expand Up @@ -105,7 +328,7 @@ $ composer install
To run the test suite, go to the project root and run:

```bash
$ php vendor/bin/phpunit
$ vendor/bin/phpunit
```

## License
Expand All @@ -114,3 +337,12 @@ This project is released under the permissive [MIT license](LICENSE).

> Did you know that I offer custom development services and issuing invoices for
sponsorships of releases and for contributions? Contact me (@clue) for details.

## More

* If you want to learn more about processing streams of data, refer to the documentation of
the underlying [react/stream](https://github.com/reactphp/stream) component.

* If you're looking to run the server side of your Server-Sent Events (SSE)
application, you may want to use the powerful server implementation provided
by [Framework X](https://framework-x.org/).
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "clue/reactphp-eventsource",
"description": "Event-driven EventSource client, receiving streaming messages from any HTML5 Server-Sent Events (SSE) server, built on top of ReactPHP",
"description": "Instant real-time updates. Lightweight EventSource client receiving live messages via HTML5 Server-Sent Events (SSE). Fast stream processing built on top of ReactPHP's event-driven architecture.",
"keywords": ["EventSource", "Server-Side Events", "SSE", "event-driven", "ReactPHP", "async"],
"homepage": "https://github.com/clue/reactphp-eventsource",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion examples/stream.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
$es = new Clue\React\EventSource\EventSource($argv[1]);

$es->on('message', function (Clue\React\EventSource\MessageEvent $message) {
//$data = json_decode($message->data);
// $json = json_decode($message->data);
var_dump($message);
});

Expand Down