dunglas / mercure

🪽 An open, easy, fast, reliable and battery-efficient solution for real-time communications
https://mercure.rocks
GNU Affero General Public License v3.0
3.98k stars 297 forks source link

Match topic selector on subscription endpoint #947

Open oxodao opened 2 months ago

oxodao commented 2 months ago

Hi,

I've been trying to get a list of users connected, and thought of using the subscription endpoint alongside with its SSE topics to be able to keep a list of online user by checking for subscriptions against /users/1234/notifications, where 1234 is the user id, which is a topic everyone on the app is always subscribing to.

On the worker which will be checking those, I'm generating a JWT with the subscribe * permissions then I call the endpoint:

$resp = $this->httpClient->request(
            'GET',
            'http://mercure/.well-known/mercure/subscriptions',
            [
                'headers' => [
                    'Authorization' => 'Bearer ' .$mercureJwt,
                ],
            ],
        );

I indeed get all the logged in users. But when I try to specify a topic selector to have only those subscription and not every subscription for every topic, I get an empty response...

 $resp = $this->httpClient->request(
            'GET',
            'http://mercure/.well-known/mercure/subscriptions/' . urlencode('/users/{*}/notifications'), // I've also tried with urlencode('/users/{id}/notifications') and urlencode('/users/*/notifications')
            [
                'headers' => [
                    'Authorization' => 'Bearer ' .$mercureJwt,
                ],
            ],
        );

Am I missing something or is there an issue ? I've seen this GH issue https://github.com/dunglas/mercure/issues/706 but the proposed PR is merged and I'm running latest caddy/mercure hub

dunglas commented 2 months ago

Hi,

First of all, you need to use the absolute URI templates. Relative URLs will not work.

Aslo, prefer encoding with rawurlencode() instead of urlencode(), even if on this case this shouldn't matter.

If this doesn't fix the issue, could you please copy the full request and response please? (they are available in the HttpClient tab of the Symfony profiler).

Thanks,

oxodao commented 2 months ago

Hi Thanks for the answer, I was using the same topic on the submit and the subscription part, as the specs was saying a full iri was only recommended but I've updated my code to use a full URL and rawurlencode and I'm still having the issue.

The request from the browser is pretty standard:

GET /.well-known/mercure?topic=https%3A%2F%2Fapi.example.fr%2Fusers%2F447%2Fnotifications&topic=https%3A%2F%2Fapi.example.fr%2Fusers%2F447%2Froles HTTP/1.1
Accept: text/event-stream
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: fr-FR,fr;q=0.9
Cache-Control: no-cache
Connection: keep-alive
Cookie: mercureAuthorization=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjUzNjU0NjAuNDA4NzMxLCJtZXJjdXJlIjp7InB1Ymxpc2giOltdLCJzdWJzY3JpYmUiOlsiaHR0cHM6Ly9hcGkuZXhhbXBsZS5mci91c2Vycy80NDcvbm90aWZpY2F0aW9ucyIsImh0dHBzOi8vYXBpLmV4YW1wbGUuZnIvdXNlcnMvNDQ3L3JvbGVzIl0sInBheWxvYWQiOnsidXNlcl9pZCI6NDQ3fX19.HL491qVWQMcFf5wSmw9RQNlcq4OCmDc4qHbc99hSgkE
Host: localhost:81
Origin: http://localhost:5173
Referer: http://localhost:5173/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
sec-ch-ua: "Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"

Its response:

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://localhost:5173
Cache-Control: private, no-cache, no-store, must-revalidate, max-age=0
Connection: keep-alive
Content-Security-Policy: default-src 'self' mercure.rocks cdn.jsdelivr.net
Content-Type: text/event-stream
Expire: 0
Pragma: no-cache
Server: Caddy
Vary: Origin
X-Accel-Buffering: no
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Xss-Protection: 1; mode=block
Date: Tue, 03 Sep 2024 12:01:09 GMT
Transfer-Encoding: chunked

My call to the subscription API, while having a react app subscribed to "https://api.example.fr/users/447/notifications":

Request: http://mercure/.well-known/mercure/subscriptions/https%3A%2F%2Fapi.example.fr%2Fusers%2F%2A%2Fnotifications

[▼
  "headers" => [▼
    "Authorization" => "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjUzNjU3NDUuMjM2NjU1LCJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdLCJzdWJzY3JpYmUiOlsiKiJdLCJwYXlsb2FkIjp7ImNvbW1hbmQiOiJteWNvbW1uZCJ9fX0.jmWBBXv90mTY5Eng59wVv4cYuUWUbMgs_0WS1m3Dqaw"
  ]
]

Response:

[▼
  "info" => [▼
    "header_size" => 345
    "request_size" => 444
    "total_time" => 0.003674
    "namelookup_time" => 0.001725
    "connect_time" => 0.002012
    "pretransfer_time" => 0.002075
    "size_download" => 263.0
    "speed_download" => 71584.0
    "download_content_length" => 263.0
    "starttransfer_time" => 0.002933
    "primary_ip" => "172.22.0.5"
    "primary_port" => 80
    "local_ip" => "172.22.0.7"
    "local_port" => 34924
    "http_version" => 2
    "protocol" => 1
    "scheme" => "HTTP"
    "connect_time_us" => 2012
    "namelookup_time_us" => 1725
    "pretransfer_time_us" => 2075
    "starttransfer_time_us" => 2933
    "total_time_us" => 3674
    "effective_method" => "GET"
    "capath" => "/etc/ssl/certs"
    "cainfo" => "/etc/ssl/certs/ca-certificates.crt"
    "start_time" => 1725365145.2405
    "original_url" => "http://mercure/.well-known/mercure/subscriptions/https%3A%2F%2Fapi.example.fr%2Fusers%2F%2A%2Fnotifications"
    "pause_handler" => [Closure(float $duration)](file:///application/vendor/symfony/http-client/Response/CurlResponse.php#L102) {#1007 ▶}
    "debug" => """
      *   Trying 172.22.0.5:80...
      * Connected to mercure (172.22.0.5) port 80 (#0)
      > GET /.well-known/mercure/subscriptions/https%3A%2F%2Fapi.example.fr%2Fusers%2F%2A%2Fnotifications HTTP/1.1
      Host: mercure
      Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjUzNjU3NDUuMjM2NjU1LCJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdLCJzdWJzY3JpYmUiOlsiKiJdLCJwYXlsb2FkIjp7ImNvbW1hbmQiOiJteWNvbW1uZCJ9fX0.jmWBBXv90mTY5Eng59wVv4cYuUWUbMgs_0WS1m3Dqaw
      Accept: */*
      User-Agent: Symfony HttpClient (Curl)
      Accept-Encoding: gzip
      < HTTP/1.1 200 OK
      < Content-Security-Policy: default-src 'self' mercure.rocks cdn.jsdelivr.net
      < Content-Type: application/ld+json
      < Etag: urn:uuid:1a64bc80-2b6b-42e8-afbe-d169d9593e02
      < Server: Caddy
      < X-Content-Type-Options: nosniff
      < X-Frame-Options: DENY
      < X-Xss-Protection: 1; mode=block
      < Date: Tue, 03 Sep 2024 12:05:45 GMT
      < Content-Length: 263
      < 
      """
  ]
  "response_headers" => [▼
    "HTTP/1.1 200 OK"
    "Content-Security-Policy: default-src 'self' mercure.rocks cdn.jsdelivr.net"
    "Content-Type: application/ld+json"
    "Etag: urn:uuid:1a64bc80-2b6b-42e8-afbe-d169d9593e02"
    "Server: Caddy"
    "X-Content-Type-Options: nosniff"
    "X-Frame-Options: DENY"
    "X-Xss-Protection: 1; mode=block"
    "Date: Tue, 03 Sep 2024 12:05:45 GMT"
    "Content-Length: 263"
  ]
  "response_content" => [▼
    """
      {
        "@context": "https://mercure.rocks/",
        "id": "/.well-known/mercure/subscriptions/https%3A%2F%2Fapi.example.fr%2Fusers%2F%2A%2Fnotifications",
        "type": "Subscriptions",
        "lastEventID": "urn:uuid:1a64bc80-2b6b-42e8-afbe-d169d9593e02",
        "subscriptions": []
      }
      """
  ]
]

One thing to note, is that when I am subscribed to the https://api.example.fr/users/*/notifications topic on my react app, I do see this subscription in the API call so my guess is that the * in the API call does not match 447 that the user is subscribed to for some reason. I've also tried with an {id} in place of the * but it does not work either

By the way, if I understood the specs correctly, I should be able to subscribe to the subscription event from my server but I'm not sure what the topic should be ? Having the EventSourceHttpClient url like https://mercure/.well-known/mercure?topic=/.well-known/mercure/subscriptions/https://api.example.fr/users/*/notifications with the topic url encoded ?

EDIT: According to the uri template tester, I should use the {id} instead of * but that does not work either