Воскресенье, 05.05.2024, 08:26
Приветствую Вас Гость
Главная | Регистрация | Вход
Домик быта
Меню сайта
  • Главная страница
  • Информация о сайте
  • Обратная связь
  • Ремонт техники
  • Доска объявлений
  • Каталог статей
  • Каталог файлов
  • Каталог сайтов
  • Дневник
  • Форум
  • Гостевая книга
  • Всё для веб-мастера
  • Онлайн игры
  • Кино клипы музыка
  • Фотоальбомы
  • FAQ (вопрос/ответ)



  • Ремонт бытовой техники! Rambler's Top100

    Яндекс цитирования

    Рейтинг@Mail.ru

                 Полезные утилиты:
    SMS бесплатно С-Пб:

      Абонентам Билайн

      Абонентам МТС

      Абонентам Теле 2

      Абонентам Мегафон

      Абонентам Скайлинк

    Главная » Статьи » ПК Железо Soft » Вопрос-ответ "F1-Help" 2000-2002 г.

    Вопрос-ответ "F1-Help" 2002-19
    ЧАСТЬ 3.
    В этой части мы рассмотрим принципы работы с HTTP- и CGI-протоколами, а также начнём программировать CGI-приложения.


    HTTP, как он есть

    "GET мне вон тот mp3", - запросил клиент.
    "404 OK RTFM", - ответил сервер.
    c2002 cmapuk[0nline]

    Казалось бы, что начать рассказ о практическом применении Perl я должен был со столь популярного (особенно среди начинающих) CGI-программирования. Но для того, чтобы толком понять, как работает CGI, надо понять принципы взаимодействия клиента (броузера) и сервера, где лежат cgi-скрипты.

    Клиент и сервер - это, в простейшем варианте, консольные приложения, которые читают из стандартного ввода и пишут в стандартный вывод. Броузер, как программа для отображения страниц - всего лишь удобная красивая оболочка. Хотите убедиться? Пожалуйста!

    Наберите в консоли telnet. В Win32, если откроется белое окошко, в меню выберите "Подключить", введите адрес perl.ru, а в поле "Порт" поставьте 80. Если окошко не открылось, а открылась консоль вида telnet>, наберите open perl.ru 80. После подключения просто введите следующее:

    GET / HTTP/1.0
    User-Agent:Shmozilla 3000
    <Нажмите 2 раза Enter>

    После этого вы получите на экран HTML-страницу с сервера. Вот и вся хитрость! Сервер и клиент обмениваются текстовой информацией по определенным правилам, совокупность которых и называется протоколом. В данном случае - HTTP.

    Запрос к серверу состоит из трех частей, в зависимости от метода запроса GET или POST. Есть и другие методы, но они редко используются и мы их рассматривать не будем.

    Запрос методом GET: Получить.

    GET /path/to/file.cgi?param1=value HTTP/1.0
    User-Agent: Shmozilla 3000
    Referer: http://www.necrosoft.com
    Accept-Language:en;ru
    <пустая строка >

    Первая строка - 1-я часть запроса - делится на 3 части:

    - GET - определяет метод.

    - /path/to/file.cgi?param1=value1 - путь к файлу, который нам нужен, и параметры. Для строки в броузере http://www.perl.ru/go.cgi?action=forum это будет выглядеть как /go.cgi?action=forum.

    - HTTP/1.0 - определяет версию протокола (или HTTP/1.1)

    Далее идут переменные - 2-я часть запроса.

    Эти переменные определяют название клиента, поддерживаемые языки, кодировку, и многое другое.

    Полный список можно взять из спецификации по HTTP - RFC2616 (www.rfc.org). Кстати, все протоколы описаны в документах RFC, расшифровки номеров которых можно найти в документе, называемом rfcindex, проще говоря, в полном списке документов (опять же на www.rfc.org).

    Третья часть - область данных - отделяется от второй пустой строкой.

    В методе GET эта часть - пустая. То есть, признаком конца запроса будет последовательность из двух переводов строки - "\n\n".

    Запрос методом POST: Послать.

    POST /path/to/file.cgi HTTP/1.0
    User-Agent: Shmozilla 3000
    Referer: http://www.necrosoft.com
    Content-length:42
    Content-type:application/www-form-urlencoded
    <пустая строка >
    param1=value1&param2=value2&param3-=value3

    Метод POST отличается от GET следующими моментами:

    1) Данные передаются не в первой строке с именем скрипта, а в третьей части, после всех переменных и пустой строки.

    2) Переменная Content-length обязательна и должна содержать размер данных в байтах.

    3) Поле Content-type содержит mime-тип посылаемых данных.

    Из этих строк и состоит HTTP-запрос. Все они пишутся программой-клиентом в стандартный вывод (STDOUT). Ниже мы более подробно рассмотрим запросы, уже создавая клиентские программы.

    Теперь остается разобраться: что же отвечает сервер на все эти запросы?

    Ответ сервера:

    200 OK Found
    Content-Length:1024
    ...
    Content-type:text/plain
    <пустая строка>

    1024 байт данных

    Ответ сервера тоже состоит из трех частей:

    - Первая строка - 1 часть.

    - ХХХ - цифровой код ошибки.

    - OK, Error, etc. - Код словесный =)

    - Found, Not Found, etc. - расшифровка ответа.

    - Вторая часть - опять же переменные, говорящие о многом =).

    - Третья часть - после пустой строки - данные, размер которых обозначен в переменной Content-Length.

    Некоторые коды:

    - 2ХХ - различные ОК'ейные ответы.

    - 4ХХ - Ошибки категории File not found, Authorization error, и прочие.

    - 5ХХ - Ошибки сервера (проклятая 500 Error из этой категории).

    Расшифровку всех кодов, а также переменных можно увидеть все в том же RFC2616.

    Теперь можно перейти и к CGI.
    Клиент -> Сервер -> Скрипт, и обратно

    "GET /mp3filez/cool.mp3 тока быстро!", - опять запросил клиент.
    "404 RTFM, я ведь сказал", - ответил сервер.
    c2002 cmapuk[0nline]

    Нет, сейчас мы еще не будем писать скрипты. Сначала разберемся, что такое CGI.

    Итак, связь между клиентом и нашим скриптом происходит через посредничество самого сервера. Клиент с сервером общаются, как мы уже выяснили, по протоколу HTTP, а протокол CGI нужен для связи между сервером и скриптом. В предыдущей главе я намеренно назвал HTTP-заголовки(Content-type, etc.) переменными для того, чтобы проследить связи между этими заголовками и Perl-хешем переменных окружения %ENV. Разберемся.

    Сервер получает запрос и раскладывает его на составные части. По первой строке он определяет, какой скрипт запускать, метод запроса, версию протокола и заголовки. После этого он знает:

    1) Откуда брать данные: из первой строки после имени скрипта и "?" или из 3-й части запроса.

    2) Куда эти данные положить скрипту - в $ENV{QUERY_STRING} или в STDIN.

    Все заголовки, метод запроса и версию протокола сервер забивает в окружение, которое доступно скрипту из %ENV. Вот примерно таким образом мы и получаем в скрипте клиентские данные. Яснее разжевать не могу ;-).

    Теперь о том, как скрипт отдает данные серверу для клиента. Сервер разрешает скрипту самому ставить заголовки, которые он потом отправит клиенту. Виды и форматы заголовков все те же, что в HTTP-спецификации. Пишется это все скриптом в стандартный вывод. Самое главное правило - в выводе должен быть заголовок "Content-type" или "Location" и ОН ДОЛЖЕН БЫТЬ ПОСЛЕДНИМ!!!. После последнего заголовка пишется пустая строка (то есть "\n\n"), а затем, если это не Location, данные, которые должен получить клиент. Вот и весь принцип работы. Для того чтобы заниматься CGI-программированием, эти простые истины НУЖНО ЗНАТЬ!!! А теперь будем практиковаться )).
    CGI, или сетевая Камасутра

    В русском издании "Perl CookBook" глава "Программирование CGI" начинается со страницы ©666. Что бы это значило?
    Просто наблюдение.

    CGI-программирование - основная область работы для Перл-программистов, особенно начинающих. По этой причине объем вопросов в форумах по этой теме составляет, вероятно, процентов 45 (Еще 45 на базы данных, а оставшиеся 10 - вопросы настройки Apache ;-)). Поэтому можно смело сказать, что CGI - это главный враг начинающего программиста. Шутка =).

    По причинам личной неприязни, модуль CGI.pm я тут описывать не буду. Если понять принцип работы с протоколом CGI, то для использования этого модуля достаточно будет стандартной документации. Напротив, не зная, что из себя представляет протокол, программирование с CGI.pm будет неотрывно связано с ошибками, глупыми вопросами и т.п. Безусловно, модуль CGI.pm решает очень много программных задач, но если в скрипте все эти решения не нужны, зачем заставлять интерпретатор обрабатывать более 214 килобайт кода?!!! Итак, CGI без CGI.pm.

    # Как мы уже выяснили, данные клиента в скрипте мы принимаем либо из
    # $ENV{QUERY_STRING}, либо из STDIN. Выглядит это так.
    $ENV{REQUEST_METHOD} eq "GET"?$data=$ENV{QUERY_STRING}:-read(STDI-N,$data,$ENV{CONTENT_LENGTH});
    # Здесь мы использовали "хитрый" условный оператор "?:"
    # Принцип: (Выражение-условие)?(ВыражениеЕслиTrue):-(ВыражениеЕслиFalse);
    # Таким образом, если данные передаются как GET, переменная $data
    # заполняется из QUERY_STRING, то есть из строки параметров запроса.
    # Если же метод - POST, мы берем данные из стандартного ввода
    # с помощью функции read (perldoc -f read). Эта функция требует
    # в качестве третьего параметра - количество байт для чтения. Это
    # мы узнаем из CONTENT_LENGTH.
    # Теперь в $data лежит что-то типа param1=value1&param2=value2
    @pairs=split/\&/,$data;
    # Разбили на пары param=value
    # Теперь раскладываем наши пары на ключи и значения и кладем в хэш
    foreach $pair (@pairs){
    ($key,$val)=split/=/,$pair;
    $val=~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
    # Это для того, чтобы превратить белиберду типа %2C в нормальные
    # знаки. В частности, русские буквы.
    $val=~s/\+/ /g;
    # Пробелы передаются как "+", мы их возвращаем в нормальный вид
      $F{$key}=~s/\r//g;
      # В многострочном текстовом поле формы переводы строк могут
      # выглядеть как \r\n, мы эти \r убиваем, так как оно нам не надо.
      $F{$key}=$val;
      # Создаем хэш ключ=>значение
    }
    # Теперь все параметры формы у нас в хэше.
    # Если в форме присутствуют параметры с одинаковыми именами,
    # например чекбоксы, то строку $F{$key}=$val можно заменить на
    if(!$F{$key}){
      $F{$key}=$val;
    }else{
      $F{$key}.="\n$val";
    }
    # таким образом одноименные параметры мы превратили в многострочную
    # строку, которую потом можно разбить в массив.

    С приемом данных вроде разобрались. Стоит еще отметить, что все HTTP-заголовки, передаваемые клиентом в запросе, содержатся в хеше %ENV, а имена их содержат префикс HTTP_. Тире(дефисы) в этих заголовках в хеше %ENV превращаются в "_". Примеры:

    User-Agent $ENV{HTTP_USER_AGENT}
    Cookie $ENV{HTTP_COOKIE}
    Referer $ENV{HTTP_REFERER}

    Нельзя с уверенностью сказать, что данные в этих заголовках - чистая правда. Они передаются клиентом в запросе и могут быть запросто подделанными. Почему? Об этом мы поговорим ниже. А здесь надо заметить, что это не вся информация о киенте. Есть ведь еще REMOTE_ADDR, REMOTE_PORT, по которым можно идентифицировать клиента. Как я уже говорил, список всех заголовков вы найдете в RFC.

    Вернемся к программированию.

    # Теперь мы будем составлять ответ клиенту.
    # После получения данных, мы их обработали и уже решили,
    # какие данные нам вернуть.
    ...
    print "Cool-header:blablabla\n";
    print "Content-Charset:gluckowin-1251\n";
    print "Content-type:text/html\n\n";
    print "<html>Basile Pupkin was here!</html>";
    # Сначала мы выводим нужные заголовки.
    # Это могут быть описания данных(типа Content-Charset), куки и пр.
    # ПОСЛЕДНИМ заголовком должен быть либо Content-type, либо Location
    # После этого заголовка - \n\n, а дальше данные, если надо.
    # Здесь следует отметить важный момент.

    В Перл-скриптах, по умолчанию, работает буферизация вывода. Это означает, что все print'ы сначала печатаются в буфер, и только в конце скрипта весь вывод выдается. За буферизацию отвечает встроенная переменная $|. Если $| определена (например $|=1), то буфер отключен. В этом случае вышеприведенный кусок кода выдаст ошибку 500, а в лог запишется сообщение о неправильном заголовке. Включение буферизации происходит так - undef $|;. Часто модули, для своей работы отключают буферизацию (например, при использовании баз данных), и это надо учитывать. В этом случае, весь наш вывод надо сохранить в переменной, которую и вывести в конце ОДНИМ print'ом. 

    ...
    $OUT="Cool-header:blablabla\n";
    $OUT.="Content-Charset:gluckowin-1251\n";
    $OUT.="Content-type:text/html\n\n";
    $OUT.="<html>Basile Pupkin was here!</html>";
    ...
    print $OUT;

    # или так:
    ...
    $OUT=< Cool-header:blablabla
    Content-Charset:gluckowin-1251
    Content-type:text/html
    <html>Basile Pupkin was here!<br>
    Yo!
    ...
    </html>
    OUT_DATA
    print $OUT;
    #

    А зачем нужен заголовок Location? Для перенаправления клиента. Это похоже на перенаправление в . Пишется так:

    ...
    print "Location:http://www.perl.com\n\n";

    После этого уже никаких данных не надо, а все заголовки (и куки тоже) пишутся до этой строки. Что касается куков, то они записываются так:

    print "Set-Cookie:$COOKIE_DATA\n";

    А какой формат имеет $COOKIE_DATA, можно и НУЖНО посмотреть в соответствующей спецификации.

    Куки придумали в Netscape.Вот здесь можно про них почитать (кроме www.rfc.org конечно) http://developer.netscape.com/docs/manuals/js/client/jsref/cookies.htm

    Пример я все же приведу:

    Set-Cookie: user=admin ; Expires=Thursday, 12-Nov-02 19:19:19 GMT;

    Вот простенькая кука.

    В таком же виде куки принимаются из $ENV{HTTP_COOKIE}.

    Еще одна, часто встречающаяся задача для cgi-скрипта. Выдать клиенту файл.

    ...
    $bytes = -s "coolfile.zip"; # Определяем размер файла (perldoc perlop)
    open(F,"coolfile.zip");
    binmode F; # Устанавливаем двоичный режим чтения
    read(F,$zip,$bytes); # Читаем весь файл в переменную $zip
    close(F);
    print "Content-length: $bytes\n"; # Выводим заголовки
    print "Content-Disposition: attachment; filename=coolfile.zip\n";
    print "Content-type: application/octet-stream\n\n";
    binmode STDOUT; # На вывод тоже двоичный режим
    print $zip; # Выводим сам файл
    ...

    Вот и все. Теперь рассмотрим обратную ситуацию - upload файлов.

    Первое правило: форма должна отправляться методом POST.

    Второе правило: тег <form> должен содержать параметр enctype="multipart/form-data".

    Допустим, наша форма содержит 2 тектовых поля и поле для файла: text1, coolfile, text2.

    # В скрипте будем читать данные из STDIN в двоичном режиме.
    binmode STDIN;
    read(STDIN,$buff,$ENV{CONTENT_LENGTH});

    Прочитали. И вот что мы имеем в переменной $buff:

    -----------------------------7d22c527e0250
    Content-Disposition: form-data; name="text1"

    Это текст1 тралала
    -----------------------------7d22c527e0250
    Content-Disposition: form-data; name="cool"; filename="X:\file.gif"
    Content-Type: image/gif

    GIF87 Здесь двоичные данные файла
    -----------------------------7d22c527e0250
    Content-Disposition: form-data; name="text2"

    Это текст2 трулала
    -----------------------------7d22c527e0250--


    При этом $ENV{CONTENT_TYPE} будет выглядеть так:

    multipart/form-data; boundary=---------------------------7d22c527e0250
    Теперь из CONTENT_TYPE нужно взять параметр boundary и по нему разбить нашу переменную $buff в массив.
    ($boundary = $ENV{CONTENT_TYPE}) =~ s/^.*boundary=(.*)$/\1/;
    @blocks = split/--$boundary/, $buf;
    @blocks = splice(@blocks,1,$#blocks-1);
    # Крайние элементы окажутся пустыми - обрезаем.
    Теперь массив @blocks содержит 3 блока данных
    $blocks[0]
    Content-Disposition: form-data; name="text1"

    <Это текст1 тралала>
    $blocks[1]
    Content-Disposition: form-data; name="cool"; filename="X:\file.gif"
    Content-Type: image/gif

    <Здесь двоичные данные файла>
    $blocks[3]
    Content-Disposition: form-data; name="text2"

    <Это текст2 трулала>

    Теперь каждый элемент разобьем на заголовок и данные и вытащим из заголовка имена полей.

    foreach $i(@blocks){
      ($head,$data)=split/\n\n/,$i;
      $head=~/ name="(\w+)"/;
      $name=$1;
      $F{$name}=$data;
    }
    # А теперь записываем файл
    open(F,">$uploadedfile");
    binmode(F);
    print F $F{file};
    close(F);


    Можно еще учитывать тип данных (Content-type).

    Вот и все основные принципы работы с CGI.
    Немножко советов

    Часто возникает необходимось идентифицировать посетителя на большом количестве страниц. Например, при входе в зону "для пользователей" требуется логин/пароль, и если пользователь должен производить какие-либо действия, его надо как-то распознавать, не сохраняя в HTML или в куках его приватных данных. Вот один из вариантов решения проблемы.

    Допустим, пользователь имеет идентификатор(ID) в системе, по которому в базе ведется работа с его данными, логин (login) и пароль (password). ID - это, в данном случае, скрытый внутренний параметр для общения базы и скриптов. Итак, пользователь заполнил форму входа в систему, т.е. ввел логин и пароль...

    Проверив существование пользователя, правильность пароля, мы должны сгенерировать уникальный идентификатор сессии SID. Метод генерации может быть каким угодно, главное, чтобы подбор SID представлял собой неразрешимую задачу. Например:

    srand($$^time);
    $ipx=$ENV{REMOTE_ADDR};
    $timex=rand time;
    $SID.=crypt($ipx,$timex) x 5;
    # способ жуткий =)

    Хотя, в принципе, достаточно 10-значного числового SID. Здесь каждый действует, руководствуясь своей паранойей. Допустим, мы его все-таки сгенерили. Теперь мы записываем либо в БД, либо просто в текстовый файл следующие 3 параметра: SID, TIME, ID. Здесь SID - это то чудо, которое мы только что сгенерили, TIME - это время в секундах с начала эпохи, полученное функцией time(), а ID - это идентификатор пользователя в системе. Теперь нам нужно снабдить пользователя нашим SID'ом. Это можно сделать двумя способами:

    1) Записать SID ему в куки.

    2) Использовать SID во всех ссылках и формах внутри пользовательской зоны.

    К чему все это? К тому, что после входа в закрытую зону пользователь при любой операции будет отправлять нам SID. А мы будем:

    1) Проверять, есть ли такой SID (если нет - отправляем на страницу входа).

    2) Проверять параметр TIME и сравнивать его с полученным снова числом из time().

    Если TIME меньше значения функции time() на 300 и более сек., значит, данный пользователь уже 5 минут нас не юзал (видимо, он ушел на обед, а злой сослуживец решил ему напакостить ;-)), - отправляем на вход.

    Если же такой SID существует и "время не вышло", то мы перезаписываем в БД или файл параметр TIME на текущий и используем для обработки данных тот ID, который соответствует SID в записях. И все повторяется снова. Таким образом, мы сохраняем безопасность пользователя. Его пароль не останется ни в кэше браузера, ни в злосчастных куках. Реализацию способа, при котором все линки снабжаются SID'ом, можно посмотреть на примере чата www.divan.ru. Зайдите в чат и посмотрите HTML-ресурсы его страниц...

    Вообще, методов много, включая "не-cgi-скриптовые" типа htaccess и т.п. А это был всего лишь маленький пример для раззадоривания фантазии )).
    Опасная профессия

    Я не националист и не коммунист, но глубокая уверенность в криворукости рук и прямоизвилинности мозга "буржуинских" программеров во мне живет и крепчает благодаря постоянным подтверждениям моей позиции. CGI-скрипты - это широкие ворота для допуска клиента к информации на сервере. И задача программиста на 50% заключается в том, чтобы подобрать надежную охрану для этих ворот (которые, впрочем, он сам и открывает). Проверка параметров, передаваемых в функцию open(), для меня лично стала чуть ли не автоматически выполняемой задачей при программировании. Я уже не говорю о таких опасностях, как system() и т.п. По сути дела, open() - это почти шелл (командная строка), и обращаться с ней надо с соответствующей осторожностью. Итак, совет первый: при написании программы использовать в первой строке скрипта ключ -T. Этот параметр включает так назваемый "зараженный" режим. В этом режиме, при попытке передать непроверенные данные, полученные извне, в опасную часть кода - Перл выдаст соответствующее сообщение. Это заставит вас "обеззаразить" все переменные. Подробнее об этом (и не только) ключе - perldoc perlrun.

    Если вы все же не хотите мучиться с "зараженными" переменными и работать с файлами, то...

    1. Если ваш скрипт выдает клиенту файлы (как в примере про coolfile.zip), то НИ В КОЕМ СЛУЧАЕ НЕЛЬЗЯ делать так: script.cgi?file=/zips/coolfile.zip !!! Не уподобляйтесь Василию Пупкину и создайте таблицу соответствий (БД/Хэш/файл), в которой будут пары типа: 0001=/zips/coolfile.zip, и принимайте в качестве параметра скрипта 0001. Кстати, скрипт go.cgi на perl.ru принимаемое значение параметра board вставляет без проверки в open(). Выглядит это примерно так:

    open(F,"/path/to/dir/$PARAM.dat");

    БАРДАК!!!

    Даже если и вставлять параметр таким образом, то достаточно простой проверки:

    print_error("Go home,LLamaz!") if $PARAM=~/[^a-zA-Z]+/;

    Лучше все-таки быть параноиком =).

    Файлы - не единственная опасность. Допустим, новости на нашем сайте берутся из базы. Вызов скрипта, который все это показывает, выглядит так: script.cgi?display=news. Невероятно часто в скриптах встречается такая ошибка: строка news является именем таблицы (или колонки таблицы) БД и НЕ ПРОВЕРЯЕТСЯ. И представьте, что теперь можно сделать с этой базой! script.cgi?display=тут_любые_sql_команды. Вот такая история.

    БУДЬТЕ БДИТЕЛЬНЫ!

    В следующей части этого опуса мы будем учиться программировать клиент-серверные приложения с использованием модулей Socket, IO::Socket и LWP, а также рассмотрим вопросы работы с базами данных посредством Perl.

    По материалам журнала Компьютер Price (www.comprice.ru)

    Категория: Вопрос-ответ "F1-Help" 2000-2002 г. | Добавил: stachek36 (19.10.2008)
    Просмотров: 1532 | Рейтинг: 0.0/0
    Всего комментариев: 0
    Добавлять комментарии могут только зарегистрированные пользователи.
    [ Регистрация | Вход ]
    Форма входа
    Категории раздела
    Вопрос-ответ "F1-Help" 2000-2002 г. [7]
    Вопрос-ответ "F1-Help" 2003 г. [33]
    Вопрос-ответ "F1-Help" 2004 г. [24]
    Вопрос-ответ "F1-Help" 2005 г. [17]
    Вопрос-ответ "F1-Help" 2006 г. [49]
    Вопрос-ответ "F1-Help" 2007 г. [51]
    Вопрос-ответ "F1-Help" 2008 г. [44]
    Вопрос-ответ "F1-Help" 2009 г. [2]
    SOFT [3]
    Поиск
    Наш опрос
    Оцените мой сайт
    Всего ответов: 199
    Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • Инструкции для uCoz
  • Гоголь-Моголь
  • Статистика

    Онлайн всего: 1
    Гостей: 1
    Пользователей: 0
    Мобайл сервис
    Сочи, ул. Московская, 19 Режим работы:
    Пн-Вс: 10.00-18.00
    Без выходных
    Контакты:
    +7(988) 238-00-94
    //stachek36.ucoz.ru
    e-mail: stachek36@mail.ru
    Copyright MyCorp © 2024Сайт создан в системе uCoz