/freddie

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

Primary LanguagePHP

Application Coverage

Freddie

Freddie is a PHP implementation of the Mercure Hub Specification.

It is blazing fast, built on the shoulders of giants:

See what features are covered and what aren't (yet) here.

Installation

PHP 8.1 is required to run the hub.

As a standalone Mercure hub

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.

As a bundle of your existing Symfony application

composer req freddie/mercure-x

You can then start the hub by doing:

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

./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:

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 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 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:

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

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

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

example.com

reverse_proxy 127.0.0.1:8080

With multiple nodes

example.com

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

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 tests.

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
  • In the case of a new feature, it must come along with tests
  • PHPStan analysis must pass at level 8

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

composer ci:check

License

GNU General Public License v3.0.