Nginx configuration for Server Sent Event

Viewed 117

we're following a project with 3 docker containers:

  • PHP 7.4 (backend, CodeIgniter), which provides APIs
  • Next.js 12.1 (frontend)
  • nginx 1.19.1

We're trying to set up a live notification system. Here's the backend code:


namespace App\Controllers;

header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Credentials: true');
header("Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Content-Length, Accept-Encoding, Authorization");

header("Content-Type: text/event-stream");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Connection: keep-alive");

use CodeIgniter\API\ResponseTrait;
use CodeIgniter\RESTful\ResourceController;

class SSEtest extends ResourceController {
    use ResponseTrait;

    protected $format = 'json';

    const TIME_TO_SLEEP_IN_SECS = 60;

    public function run($id_user) {
        while(ob_get_level() > 0) {
            ob_end_flush();
        }

        @ini_set('output_buffering', 'Off');
        @ini_set('zlib.output_compression', 0);
        @ini_set('implicit_flush', 1);
        @ob_end_clean();
        set_time_limit(0);
        ob_start(); 
        
        $id = 0;
        while(true) { 
            $id++;
            $curDate = date(DATE_ISO8601);  
            
            $notifications = ['foo', 'bar'];

            if( is_array($notifications) ) {
                $tot = 0;
                foreach($notifications AS $n) {
                    $tot += $n['num'];
                }
                
                $this->sendMsg('msg', ['id' => $id, 'time' => $curDate, 'cnt' => $notifications, 'total' => $tot], $id);
            }
            else {
                $this->sendMsg('ping', ['id' => $id, 'time' => $curDate, 'cnt' => 'ping'], $id);
            }
        
            ob_flush(); 
            flush();
            
            if( connection_aborted() ) {
                break;
            }
        
            sleep(self::TIME_TO_SLEEP_IN_SECS);
        
        }       
    }

    protected function sendMsg($event, $data, $id) {
        echo 'event: '.$event.PHP_EOL;
        echo 'data: '.json_encode($data).PHP_EOL;
        echo 'id: '.$id.PHP_EOL;
        //echo 'retry: 1000'.PHP_EOL; // Tempo in ms
        //echo 'Time: '.date(DATE_ISO8601).PHP_EOL;
        echo PHP_EOL;
    }

}

And here's our frontend code in a component:

useEffect(() => {

        let isMounted = true

        const fetchNotifications = async () => {
            if(!!user){
                await fetchEventSource(API, {
                    method: "POST",
                    headers: {
                        Accept: "text/event-stream",
                    },
                    onopen(res) {
                        if (res.ok && res.status === 200) {
                            console.log("Connection made ", res);
                        }
                        else if (res.status >= 400 && res.status < 500 && res.status !== 429) {
                            console.log("Client side error ", res);
                        }
                    },
                    /*
                    event: ping
                    data: {"id":1,"time":"2022-05-10T11:32:24+0200","cnt":"ping"}
                    id: 1
                    retry: NULL
                    */
                    onmessage(e) {
                        const parsedData = JSON.parse(e.data);
                        console.log(e, parsedData);
                    },
                    onclose() {
                        console.log("Connection closed by the server");
                    },
                    onerror(err) {
                        console.log("There was an error from server", err);
                    },
                });
            }
        }

        fetchNotifications()

        return () => isMounted = false

}, [])

This is the error message we receive:

 There was an error from server TypeError: NetworkError when attempting to fetch resource. index.jsx:60:32

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost/foo/bar. (Reason: CORS request did not succeed). Status code: (null).

We suspect the error is due to our nginx configuration:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name localhost;

    index index.html index.htm index.debian-default.html index.php;

    root /var/www/html/public;

    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;

    set $virtualdir "";
    set $realdir "";

    if ($request_uri ~ ^/([^/]*)/.*$ ) {
        set $virtualdir /$1;
    }

    if (-d "$document_root$virtualdir") {
        set $realdir "${virtualdir}";
    }
    
    location / {
        try_files $uri $uri/ $realdir/index.php?$args;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass api:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

Any idea on how to solve the issue? Thank you!

1 Answers

The server needs to output this in the headers:

Access-Control-Allow-Origin: http://localhost

For CORS to be accepted. More about CORS: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

To accompish this in Nginx add the following line:

location ~ \.php$ {
        add_header 'Access-Control-Allow-Origin'  'http://localhost' always;
        ...
   }
Related