HOME FORUMS MEMBERS RECENT POSTS LOG IN  
× Авторизация
Имя пользователя:
Пароль:
Нет аккаунта? Регистрация
Баннер 1   Баннер 2
НОВЫЕ ТОРГОВАЯ НОВОСТИ ЧАТ
loading...
Скрыть
Вернуться   ANTICHAT > ИНФО > Статьи
   
Ответ
 
Опции темы Поиск в этой теме Опции просмотра

  #1  
Старый 28.03.2012, 19:14
mr.Penguin
Познающий
Регистрация: 08.03.2012
Сообщений: 40
С нами: 7463126

Репутация: 74
По умолчанию

Я думаю, очень много людей думало над тем, чтобы сделать свой эмулятор терминала на PHP, и обычно останавливались на решениях вроде следующего:

PHP код:
[COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"]'[/COLOR][COLOR="#007700"]; if(isset([/COLOR][COLOR="#0000BB"]$_GET[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#DD0000"]'cmd'[/COLOR][COLOR="#007700"]]))[/COLOR][COLOR="#0000BB"]system[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$_GET[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#DD0000"]'cmd'[/COLOR][COLOR="#007700"]]);[/COLOR][/COLOR] 
Конечно же, такое решение вызывает целый набор проблем, самая незначительная из которых — это то, что ошибки на экран не попадают. Есть и намного более значительные вещи, например запуск vi просто «подвесит» выполнение команды и придется открывать новую консоль и писать killall vi. И что уж точно не получится сделать, так это выполнить команды ssh или sudo, которые требуют чтения пароля прямо с терминала. Я постараюсь показать способ, с помощью которого можно устранить большую часть описанных выше проблем.

Пишем простецкий эмулятор терминала на PHP

Для нашего эмулятора терминала понадобятся:

PHP код:
[COLOR="#000000"][COLOR="#0000BB"]putenv[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]'TERM=vt100'[/COLOR][COLOR="#007700"]);

[/
COLOR][COLOR="#0000BB"]$cols[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]80[/COLOR][COLOR="#007700"];[/COLOR][COLOR="#FF8000"]// можно задать любые числа, в общем-то

[/COLOR][COLOR="#0000BB"]$rows[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]24[/COLOR][COLOR="#007700"];[/COLOR][/COLOR
  • PHP 5.2+ (с напильником — PHP 4.3+)
  • Работающие функции system() и proc_open()
  • Работающая функция flush() (flush() для nginx)
  • term.js и utils.js из проекта JSLinux
  • Linux, Mac OS X на стороне сервера (на *BSD не проверял, возможно, требуются доработки)
Возможно, увидев ссылку на JSLinux, вы уже начали догадываться, что мы будем делать .

Основная идея реализации

В документации к PHP сказано, что proc_open() предназначен для двухсторонней коммуникации с процессами, поэтому мы откроем bash в интерактивном режиме с помощью этой функции и будем дальше с ним работать. К сожалению, поддержки псевдотерминалов в PHP из коробки нет, поэтому реализацию нужной нам прослойки мы напишем на Си.

Какой-либо защиты, а также проверок на ошибки и корректного завершения терминала в этом примере не предполагается(!), об этом вам придется думать самим .

Файл shell.php

Получение ввода пользователя

Мы должны каким-то образом получать ввод от пользователя, например через FIFO-файл:

[PHP]
[COLOR="#000000"][COLOR="#007700"]

PHP код:
[COLOR="#000000"][COLOR="#0000BB"]putenv[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]'TERM=vt100'[/COLOR][COLOR="#007700"]);

[/
COLOR][COLOR="#0000BB"]$cols[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]80[/COLOR][COLOR="#007700"];[/COLOR][COLOR="#FF8000"]// можно задать любые числа, в общем-то

[/COLOR][COLOR="#0000BB"]$rows[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]24[/COLOR][COLOR="#007700"];[/COLOR][/COLOR
Команда для запуска bash

В принципе, мы можем просто запустить "bash -i", и это будет работать (даже "sh -i" будет работать), но ещё лучше, если мы сможем работать через псевдотерминал, программы будут вести себя более «естественно» в таком случае. Заодно мы можем использовать свой bashrc, в котором настроим цветное приглашение .

PHP код:
[COLOR="#000000"][COLOR="#0000BB"]chdir[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]dirname[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]__FILE__[/COLOR][COLOR="#007700"]));

[/
COLOR][COLOR="#0000BB"]$cmd[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#DD0000"]"bash --rcfile ./bashrc -i 2>&1"[/COLOR][COLOR="#007700"];

[/
COLOR][COLOR="#FF8000"]// попробуем скомпилировать нашу утилиту для работы с псевдотерминалом (pt.c, будет приведена далее)

// если программа не компилируется, просто не используем эту дополнительную прослойку

[/COLOR][COLOR="#007700"]if (![/COLOR][COLOR="#0000BB"]file_exists[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]'pt'[/COLOR][COLOR="#007700"])) {

[/
COLOR][COLOR="#0000BB"]system[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]'cc -D__'[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]strtoupper[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]trim[/COLOR][COLOR="#007700"](`[/COLOR][COLOR="#DD0000"]uname[/COLOR][COLOR="#007700"]`)).[/COLOR][COLOR="#DD0000"]'__ -o pt pt.c -lutil 2>&1'[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$retval[/COLOR][COLOR="#007700"]);

if ([/COLOR][COLOR="#0000BB"]$retval[/COLOR][COLOR="#007700"]) echo([/COLOR][COLOR="#DD0000"]'Warning: Cannot compile pseudotty helper'[/COLOR][COLOR="#007700"]);

}

[/
COLOR][COLOR="#0000BB"]clearstatcache[/COLOR][COLOR="#007700"]();

if ([/COLOR][COLOR="#0000BB"]file_exists[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]'pt'[/COLOR][COLOR="#007700"]))[/COLOR][COLOR="#0000BB"]$cmd[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#DD0000"]"./pt[/COLOR][COLOR="#0000BB"]$rows[/COLOR][COLOR="#DD0000"][/COLOR][COLOR="#0000BB"]$cols[/COLOR][COLOR="#DD0000"][/COLOR][COLOR="#0000BB"]$cmd[/COLOR][COLOR="#DD0000"]"[/COLOR][COLOR="#007700"];

[/COLOR][COLOR="#0000BB"]$pp[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]proc_open[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$cmd[/COLOR][COLOR="#007700"], array(array([/COLOR][COLOR="#DD0000"]'pipe'[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#DD0000"]'r'[/COLOR][COLOR="#007700"]), array([/COLOR][COLOR="#DD0000"]'pipe'[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#DD0000"]'w'[/COLOR][COLOR="#007700"])),[/COLOR][COLOR="#0000BB"]$pipes[/COLOR][COLOR="#007700"]);

[/
COLOR][COLOR="#0000BB"]stream_set_blocking[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$pipes[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#0000BB"]0[/COLOR][COLOR="#007700"]],[/COLOR][COLOR="#0000BB"]0[/COLOR][COLOR="#007700"]);

[/
COLOR][COLOR="#0000BB"]stream_set_blocking[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$pipes[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#0000BB"]1[/COLOR][COLOR="#007700"]],[/COLOR][COLOR="#0000BB"]0[/COLOR][COLOR="#007700"]);

[/
COLOR][COLOR="#0000BB"]?>[/COLOR][/COLOR] 
Посылка команд из javascript

Мы будем делать по одному HTTP-запросу на символ (или больше символов, если сервер «не успевает»). Да, это может быть неоправданной тратой ресурсов в данном случае и можно всё сделать через вебсокеты, но в плане реализации моя схема намного проще .

Код:
Terminal

    var pipeName = , pending_str = '', processing = false;
    var sendCmdInterv = setInterval(function() {
        if (processing) return;
        if (pending_str.length) {
            processing = true;
            var previous_str = pending_str;
            pending_str = '';
            var http = new XMLHttpRequest();
            http.open("GET", "send-cmd.php?pipe=" + pipeName + "&cmd=" + encodeURIComponent(previous_str), true);
            http.onreadystatechange = function() {
                if (http.readyState == 4 && http.status == 200) {
                    processing = false;
                    pending_str = '';
                } else {
                    pending_str = previous_str + pending_str;
                }
            };
            http.send(null);
        }
    }, 16);

    function send_cmd(val) {
        pending_str += val;
    }
Эмулятор терминала на javascript

В JSLinux есть много разных полезных частей, в данном случае это эмулятор терминала. Автор запрещает распространение и модификацию файла term.js без его ведома, поэтому в этом примере мы будем просто ссылаться на его библиотеку и использовать её, как есть.

Код:
    .term {
        font-family: monaco,courier,fixed,monospace,swiss,sans-serif;
        font-size: 13px;
        line-height: 16px;
        color: #f0f0f0;
        background: #000000;
    }

    tr {
        height: 16px;
    }

    .termReverse {
        color: #000000;
        background: #00ff00;
    }

var term = new Term(, , send_cmd); term.open();
Чтение пользовательского ввода и исполнение команд

Читаем пользовательский ввод из нашего FIFO-файла, вывод нашей команды из соответствующего пайпа, всё в неблокирующем режиме, для простоты. Также, в качестве небольшого костыля, заменяем "\n" на "\r\n", так как на самом деле, term.js обрабатывает вывод драйвера последовательного порта, а не «сырой» вывод программы .

PHP код:
[COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"]\n"[/COLOR][COLOR="#007700"];

[/COLOR][COLOR="#0000BB"]flush[/COLOR][COLOR="#007700"]();

while (![/COLOR][COLOR="#0000BB"]feof[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$pipes[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#0000BB"]1[/COLOR][COLOR="#007700"]])) {

[/
COLOR][COLOR="#0000BB"]$ln[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]fgets[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$pipes[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#0000BB"]1[/COLOR][COLOR="#007700"]],[/COLOR][COLOR="#0000BB"]4096[/COLOR][COLOR="#007700"]);

if ([/COLOR][COLOR="#0000BB"]$ln[/COLOR][COLOR="#007700"]!==[/COLOR][COLOR="#0000BB"]false[/COLOR][COLOR="#007700"]) {

[/
COLOR][COLOR="#0000BB"]$ln[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]str_replace[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"\n"[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#DD0000"]"\r\n"[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$ln[/COLOR][COLOR="#007700"]);

echo[/COLOR][COLOR="#DD0000"]'term.write('[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]json_encode[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$ln[/COLOR][COLOR="#007700"]).[/COLOR][COLOR="#DD0000"]');'[/COLOR][COLOR="#007700"];

[/
COLOR][COLOR="#0000BB"]flush[/COLOR][COLOR="#007700"]();

continue;

}



[/COLOR][COLOR="#0000BB"]$inp_ln[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]fgets[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$cmdfp[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]4096[/COLOR][COLOR="#007700"]);

if ([/COLOR][COLOR="#0000BB"]$inp_ln[/COLOR][COLOR="#007700"]!==[/COLOR][COLOR="#0000BB"]false[/COLOR][COLOR="#007700"]) {

[/
COLOR][COLOR="#FF8000"]// ensure that command is fully written by setting blocking to 1

[/COLOR][COLOR="#0000BB"]stream_set_blocking[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$pipes[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#0000BB"]0[/COLOR][COLOR="#007700"]],[/COLOR][COLOR="#0000BB"]1[/COLOR][COLOR="#007700"]);

[/
COLOR][COLOR="#0000BB"]fwrite[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$pipes[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#0000BB"]0[/COLOR][COLOR="#007700"]],[/COLOR][COLOR="#0000BB"]$inp_ln[/COLOR][COLOR="#007700"]);

[/
COLOR][COLOR="#0000BB"]stream_set_blocking[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$pipes[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#0000BB"]0[/COLOR][COLOR="#007700"]],[/COLOR][COLOR="#0000BB"]0[/COLOR][COLOR="#007700"]);

}



[/COLOR][COLOR="#0000BB"]usleep[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]20000[/COLOR][COLOR="#007700"]);

}

[/
COLOR][COLOR="#0000BB"]proc_close[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$pp[/COLOR][COLOR="#007700"]);

[/
COLOR][COLOR="#0000BB"]?>[/COLOR][/COLOR] 
Очищаем прием команд после выхода

После того, как процесс завершился (например человек нажал Ctrl+D или ввел «exit»), нужно перестать отправлять пользовательский ввод на сервер, ибо принимать его всё равно больше некому .

Код:
clearInterval(sendCmdInterv);
Файл send-cmd.php

Файл, на который мы отправляем команды, называется send-cmd.php и состоит вот из чего (да-да, никаких проверок входных параметров ):

[PHP]
[COLOR="#000000"][COLOR="#007700"] '
export PS4='+ '
export LANG=en_US.UTF-8

echo Welcome to simple terminal emulator'!'
echo Scroll up and down using Ctrl-Up, Ctrl-Down, Ctrl-PageUp and Ctrl-PageDown.
echo Output handling is based on JSLinux term.js library. Enjoy'!'
[/CODE]
Файл pt.c

Утилиты для работы с псевдотерминалом уже существуют, например есть хорошая библиотека expect, которая делает почти то, что нам нужно. Тем не менее, мне показалось интересным написать свою утилиту, которая будет просто устанавливать нужные размеры терминала и выводить всё на stdout и принимать ввод на stdin:

[CODE]
#include
#include
#include
#ifdef __LINUX__
#include
#else
#include
#endif

static void set_fds(fd_set *reads, int pttyno) {
FD_ZERO(reads);
FD_SET(0, reads);
FD_SET(pttyno, reads);
}

int main(int argc, char *argv[]) {
char buf[1024];
int pttyno, n = 0;
int pid;
struct winsize winsz;

if (argc [args]\n", argv[0]);
return 1;
}

winsz.ws_row = atoi(argv[1]);
winsz.ws_col = atoi(argv[2]);
winsz.ws_xpixel = winsz.ws_col * 14;
winsz.ws_ypixel = winsz.ws_row * 14;

pid = forkpty(&pttyno, NULL, NULL, &winsz);
if (pid . Тем не менее, я записал короткое видео, которое показывает, как это работает:

Ссылка на YouTube

Всё вместе

По идее, все исходные коды должны быть в данной статье. Если вам лениво, я выложил архив с соответствующими файлами на народ.диск

UPD:

Если программа pt.c не компилируется под FreeBSD, добавьте следующие заголовочные файлы в начало файла (а #include уберите):

Код:
#include 
#include 
#include 
#include
Автор: youROCK

Источник: http://habrahabr.ru/post/139878/
 
Ответить с цитированием
Ответ



Предыдущая тема Следующая тема

Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
 


Быстрый переход




ANTICHAT ™ © 2001- Antichat Kft.