Использование Sever Sent Events (SSE) в плагине

В процессе создания и эксплуатации плагина WatchMan-Site7 мне пришла в голову идея создать такой механизм, который мог бы сам, без ручного вмешательства со стороны админа – обновлять основной экран посещений всех типов посетителей (людей, роботов) по мере их появления на сайте. Потому что – надоело: сидишь в админке и как дятел стучишь по клавише “обновить” экран браузера, что бы увидеть новых посетителей сайта. Пусть бы сам сайт посылал мне в браузер информацию о новых посещениях по мере их поступления!

Пришлось углубиться в теорию. И вот, что я накопал… Существуют три технологии динамической связи “клиент-сервер”:
1. AJAX (набор ранее существующих технологий – JS, PHP, HTML)
2. WebSocket (мощная вещь, но достаточно сложная в реализации)
3. SSE (Server-Sent Events)
Пришлось познакомиться с перечисленными технологиями, причем зная, что базовый класс WP_LIST_TABLE в WordPress адаптирован к технологии AJAX. И поэтому, подспудно, думая на какой технологии остановиться, я склонялся к AJAX. Однако, спустя некоторое время, я все-таки остановился на технологии SSE ( спецификация SSE ). И вот, как это удалось реализовать.
На стороне сервера:

<?php
/**
 * Description: Used to send a count of records of visitor, number of unseen emails.
 *
 * PHP version 5
 *
 * @category   Wms7-sse.php
 * @package    WatchMan-Site7
 * @author     Oleg Klenitskiy <klenitskiy.oleg@mail.ru>
 * @version    3.0.0
 * @license    GPLv2 or later
 */

/**
 * We specify that we need at least WP.
 */
define( 'SHORTINIT', true );
// loadable environment WordPress.
$_document_root = filter_input( INPUT_SERVER, 'DOCUMENT_ROOT', FILTER_SANITIZE_STRING );
require_once $_document_root . '/wp-load.php';

/**
 * Used for mail inbox connection.
 * Function with the same name is in the file wms7-mail.php plugin.
 *
 * @return object.
 */
function wms7_mail_inbox_connection() {
	$val        = get_option( 'wms7_main_settings' );
	$select_box = $val['mail_select'];
	$box        = $val[ $select_box ];

	$server = '{' . $box['imap_server'] . ':' . $box['mail_box_port'] . '/imap/' . $box['mail_box_encryption'] . '/novalidate-cert}INBOX';

	$username = $box['mail_box_name'];
	$password = $box['mail_box_pwd'];

	if ( $box && '' !== $username && '' !== $password ) {
		try {
			$imap = imap_open( $server, $username, $password );
		} catch ( Exception $e ) {
				$imap =
				$e->getMessage() .
				'<br>server: ' . $server .
				'<br>username: ' . $username .
				'<br>password: ' . $password;
		}
		return $imap;
	}
}
/**
 * Used for mail inbox unseen. Function with the same name is in the file wms7-mail.php plugin.
 *
 * @return number.
 */
function wms7_mail_unseen() {
	$imap = wms7_mail_inbox_connection();
	$i    = 0;
	if ( $imap ) {
		$mc = imap_check( $imap );
		// Get an overview of all the letters in the box.
		$result = imap_fetch_overview( $imap, "1:{$mc->Nmsgs}", 0 );
		foreach ( $result as $overview ) {
			if ( 0 === $overview->seen ) {
				$i++;
			}
		}
		imap_close( $imap );
	}
	return $i;
}
/**
 * Used for to obtain the number of visits records.
 *
 * @return number.
 */
function wms7_count_rows() {
	global $wpdb;

	$cache_key = 'all_total';
	$results   = wp_cache_get( $cache_key );
	if ( ! $results ) {
		$results = $wpdb->get_var(
			$wpdb->prepare(
				"
                SELECT count(%s) FROM {$wpdb->prefix}watchman_site
                ",
				'*'
			)
		);// db call ok; cache ok.
	}
	return $results;
}
/**
 * Used for Serves to send information to the client browser (admin-panel of site).
 *
 * @param string $data1 Number of visits records.
 * @param string $data2 Number of mails inbox unseen.
 */
function wms7_send_message( $data1, $data2 ) {
	if ( ! headers_sent() ) {
		header( 'Content-Type: text/event-stream' );
		header( 'Cache-Control: no-cache' );
		header( 'Connection: keep-alive' );
	}
	echo 'data: ' . intval( $data1 );
	echo '|' . intval( $data2 );
	echo "\n\n";
	// check for output_buffering activation.
	if ( 0 !== count( ob_get_status() ) ) {
		ob_flush();
	}
	flush();
}

while ( true ) {
	$new_count_rows = wms7_count_rows();
	$mail_unseen    = wms7_mail_unseen();
	wms7_send_message( $new_count_rows, $mail_unseen );
	sleep( 10 );
}

Прокомментирую:
В выше показанном коде – основное действие в строках 116-121. Остальное – необходимая прелюдия.
Создается цикл с периодом ожидания 10 сек. Здесь, конечно можно было бы придумать что-то по-изящнее. Как-то, через хуки ловить появления новых записей в БД, или как-то иначе… Но пока – так, просто в цикле 🙂
На стороне клиента:

/**
 * Description: Used to manage the plug-in on the client side.
 *
 * @category    Wms7_script.js
 * @package     WatchMan-Site7
 * @author      Oleg Klenitskiy <klenitskiy.oleg@mail.ru>
 * @version     3.0.0
 * @license     GPLv2 or later
 */

/**
 * Process Control Server Sent Events on the client side.
 */
function wms7_sse() {
	var myElement = document.getElementById( 'sse' );

	if (myElement.checked) {
		document.cookie = 'wms7_sse=on';
		if ( ! ! window.EventSource ) {
			var source = new EventSource( wms7_url + 'includes/wms7-sse.php' );

			source.addEventListener(
				'message',
				function(e) {
					console.log( e.data );
					var arr = e.data.split( '|' );
					if (get_cookie( 'wms7_records_count' ) !== arr[0] || get_cookie( 'wms7_unseen_count' ) !== arr[1]) {
						document.cookie = 'wms7_records_count=' + arr[0];
						document.cookie = 'wms7_unseen_count=' + arr[1];
						wms7_beep();
						location.replace( window.location.href );
					}
				},
				false
			);

			source.addEventListener(
				'open',
				function(e) {
					console.log( 'Connection was opened.' );
				},
				false
			);

			source.addEventListener(
				'error',
				function(e) {
					console.log( 'Error - connection was lost.' );
				},
				false
			);

		} else {
			alert( 'Your browser does not support Server-Sent Events. Please upgrade it.' );
			return;
		}
	} else {
		// stop SSE.
		document.cookie = 'wms7_sse=off';
		location.replace( window.location.href );
	}
	wms_ctrl_btn_href();
}

Прокомментирую:
В последнем коде, в строке 27 происходит сравнение ранее сохраненных данных в cookies с вновь пришедшими данными и сохраняются в cookies для последующего сравнения (когда придут с сервера новые данные). Затем, в строке 30 – звуковой сигнал, и в строке 31 – обновление экрана. Все действие можно наблюдать в консоли, нажав F12.
😉Совет:
Такой подход мог бы быть полезен при проведении каких-либо коммерческих акций, опросов, политических компаний, голосования…

Leave a Reply

Your email address will not be published. Required fields are marked *