Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/bpolaszek/freddie

A Mercure Hub, written in PHP (8.1 + ReactPHP)
https://github.com/bpolaszek/freddie

hub mercure php sse

Last synced: 15 days ago
JSON representation

A Mercure Hub, written in PHP (8.1 + ReactPHP)

Awesome Lists containing this project

README

        

[![Application](https://github.com/bpolaszek/mercure-x/actions/workflows/app.yml/badge.svg)](https://github.com/bpolaszek/mercure-x/actions/workflows/app.yml)
[![Coverage](https://codecov.io/gh/bpolaszek/freddie/branch/main/graph/badge.svg?token=uB4gRHyS6r)](https://codecov.io/gh/bpolaszek/freddie)

# Freddie

Freddie is a PHP implementation of the [Mercure Hub Specification](https://mercure.rocks/spec).

It is blazing fast, built on the shoulders of giants:
- [PHP](https://www.php.net/releases/8.1/en.php) 8.1
- [Framework X](https://framework-x.org/) and [ReactPHP](https://reactphp.org/)
- [Symfony](https://symfony.com/) 6
- [Redis](https://redis.io/) (optionally).

See what features are covered and what aren't (yet) [here](#feature-coverage).

## Installation

PHP 8.1+ is required to run the hub.

### As a standalone Mercure hub

```bash
composer create-project freddie/mercure-x freddie && cd freddie
bin/freddie
```

This will start a Freddie instance on `127.0.0.1:8080`, with anonymous subscriptions enabled.

You can publish updates to the hub by generating a valid JWT signed with the `!ChangeMe!` key with `HMAC SHA256` algorithm.

To change these values, see [Security](#security).

### As a bundle of your existing Symfony application

```bash
composer req freddie/mercure-x
```

You can then start the hub by doing:

```bash
bin/console freddie:serve
```

You can override relevant env vars in your `.env.local`
and services in your `config/services.yaml` as usual.

Then, you can inject `Freddie\Hub\HubInterface` in your services so that you can call `$hub->publish($update)`,
or listening to dispatched updates in a CLI context 👍

Keep in mind this only works when using the Redis transport.

⚠️ **Freddie** uses its own routing/authentication system (because of async / event loop).

The controllers it exposes cannot be imported in your `routes.yaml`, and get out of your `security.yaml` scope.

## Usage

```bash
./bin/freddie
```

It will start a new Mercure hub on `127.0.0.1:8080`.
To change this address, use the `X_LISTEN` environment variable:

```bash
X_LISTEN="0.0.0.0:8000" ./bin/freddie
```

### Security

The default JWT key is `!ChangeMe!` with a `HS256` signature.

You can set different values by changing the environment variables (in `.env.local` or at the OS level):
`X_LISTEN`, `JWT_SECRET_KEY`, `JWT_ALGORITHM`, `JWT_PUBLIC_KEY` and `JWT_PASSPHRASE` (when using RS512 or ECDSA)

Please refer to the [authorization](https://mercure.rocks/spec#authorization) section of the Mercure specification to authenticate as a publisher and/or a subscriber.

### PHP Transport (default)

By default, the hub will run as a simple event-dispatcher, in a single PHP process.

It can fit common needs for a basic usage, but using this transport prevents scalability,
as opening another process won't share the same event emitter.

It's still prefectly usable as soon as :
- You don't expect more than a few hundreds updates per second
- Your application is served from a single server.

### Redis transport

On the other hand, you can launch the hub on **multiple ports** and/or **multiple servers** with a Redis transport
(as soon as they share the same Redis instance), and optionally use a load-balancer to distribute the traffic.

The [official open-source version](https://mercure.rocks/docs/hub/install) of the hub doesn't allow scaling
because of concurrency restrictions on the _bolt_ transport.

To launch the hub with the Redis transport, change the `TRANSPORT_DSN` environment variable:

```bash
TRANSPORT_DSN="redis://127.0.0.1:6379" ./bin/freddie
```

Optional parameters you can pass in the DSN's query string:
- `pingInterval` - regularly ping Redis connection, which will help detect outages (default `2.0`)
- `readTimeout` - max duration in seconds of a ping or publish request (default `0.0`: considered disabled)

_Alternatively, you can set this variable into `.env.local`._

## Advantages and limitations

This implementation does not provide SSL nor HTTP2 termination, so you'd better put a reverse proxy in front of it.

### Example Nginx configuration

```nginx
upstream freddie {
# Example with a single node
server 127.0.0.1:8080;

# Example with several nodes (they must share the same Redis instance)
# 2 instances on 10.1.2.3
server 10.1.2.3:8080;
server 10.1.2.3:8081;

# 2 instances on 10.1.2.4
server 10.1.2.4:8080;
server 10.1.2.4:8081;
}

server {

listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;

ssl_certificate /etc/ssl/certs/example.com/example.com.cert;
ssl_certificate_key /etc/ssl/certs/example.com/example.com.key;
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;

location /.well-known/mercure {
proxy_pass http://freddie;
proxy_read_timeout 24h;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```

### Example Caddy configuration

#### Single node

```caddy
example.com

reverse_proxy 127.0.0.1:8080
```

#### With multiple nodes

```caddy
example.com

reverse_proxy 10.1.2.3:8080 10.1.2.3:8081 10.1.2.4:8080 10.1.2.4:8081
```

### Payload limitations
⚠ There's a known limit in [Framework-X](https://framework-x.org/docs/api/request/) which prevents request bodies to weigh more than [64 KB](https://github.com/reactphp/http/issues/412).
At the time of writing, this limit cannot be raised due to Framework-X encapsulating HTTP Server instantiation.

Publishing bigger updates to Freddie (through HTTP, at least) could result in 400 errors.

## Feature coverage

| Feature | Covered |
|---------------------------------------------|---------------------------------------|
| JWT through `Authorization` header | ✅ |
| JWT through `mercureAuthorization` Cookie | ✅ |
| Allow anonymous subscribers | ✅ |
| Alternate topics | ✅️ |
| Private updates | ✅ |
| URI Templates for topics | ✅ |
| HMAC SHA256 JWT signatures | ✅ |
| RS512 JWT signatures | ✅ |
| Environment variables configuration | ✅ |
| Custom message IDs | ✅ |
| Last event ID (including `earliest`) | ✅️ |
| Customizable event type | ✅️ |
| Customizable `retry` directive | ✅️ |
| CORS | ❌ (configure them on your web server) |
| Health check endpoint | ❌ (PR welcome) |
| Logging | ❌ (PR welcome))️ |
| Metrics | ❌ (PR welcome)️ |
| Different JWTs for subscribers / publishers | ❌ (PR welcome) |
| Subscription API | ❌️ (TODO) |

## Tests

This project is 100% covered with [Pest](https://pestphp.com/) tests.

```bash
composer tests:run
```

## Contribute

If you want to improve this project, feel free to submit PRs:

- CI will yell if you don't follow [PSR-12 coding standards](https://www.php-fig.org/psr/psr-12/)
- In the case of a new feature, it must come along with tests
- [PHPStan](https://phpstan.org/) analysis must pass at level 8

You can run the following command before committing to ensure all CI requirements are successfully met:

```bash
composer ci:check
```

## License

GNU General Public License v3.0.