CGI шаг за шагом

Шаг 1 - Что такое CGI?

CGI (CGI - Common Gateway Interface ) является стандартом интерфейса, который служит для связи внешней программы с веб-сервером. Программу, которая работает по такому интерфейсу совместно с веб-сервером, принято называть шлюзом, многие больше любят названия скрипт или CGI-программа.

Сам протокол разработан таким образом, чтобы можно было использовать любой язык программирования, который может работать со стандартными устройствами ввода/вывода. А это умеет даже сама операционная система, поэтому часто если вам не требуется сложный скрипт, его можно просто сделать в виде командного файла.

Все скрипты, как правило, помещают в директорию cgi-bin сервера, но это совсем даже не обязательно, в принципе скрипт может располагаться где угодно только при этом большинство Web-серверов требуют специальной настройки.

В web-сервере Apache такая настройка производится с помощью файла .htaccess в той директории, где содержится этот скрипт. Вот содержание такого файла:

Options ExecCGI
Также Apache позволяет запускать все скрипты имеющие рассширение .cgi, если в файле настроек сервера httpd.cong есть настройка:
AddHandler cgi-script .cgi

Чаще всего, хотя наверно почти всегда, скрипты используются для создания динамических страниц. Связано это с тем, что само содержимое веб-сервера является статическим и не будет меняться просто так, для этого должен приложить руку веб-мастер. Технология CGI позволяет просто поменять содержимое веб-сервера. Простым примером может служить скрипт, который при каждом новом обновлении страницы вставляет в нее новую ссылку(банер) или анекдот. Более сложными скриптами являются гостевые книги, чаты, форумы и естественно поисковые сервера или базы данных построенные на технологиях интернета.

Шаг 2 - Передача данных шлюзу.

Передача данных шлюзу осуществляется в следующем формате:

имя=значение&имя1=значение1&... 
Здесь "имя" это название параметра, а "значение" его содержимое. Методов передачи данных в таком формате существует два - GET и POST. При использовании метода GET данные передаются серверу вместе с URL:
http://.../cgi-bin/test.cgi?имя=значение&имя1=значение1&...
При использовании метода POST данные посылаются внутри самого HTTP запроса.

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

Получение данных самим скриптом также различается. При использовании метода GET данные следующие за "?" помещаются в переменную среды QUERY_STRING. При использовании POST содержимое запроса перенаправляется в стандартный поток ввода, т.е. в stdin.

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

В имена и значения параметров при передаче кодируются браузером URL методом, т.е. все символы не принадлежащие к латинскому алфавиту и числам кодируются в виде %HH, где HH - шестнадцатеричное значение кода символа. Также кодируются все символы , которые нельзя использовать, т.е. !#%^&()=+ и пробел. Символ "&" используется, как мы уже видели для разделения пар "имя=значение", "=" используется в парах "имя=значение", "%" для кодирования символов, "пробел" кодируется символом "+"(плюс), сам же плюс кодируется через "%", и т.д. Поэтому при анализе полученных данных требуется их переводить в нормальных вид.

Пример кодировки символов и букв при передаче:

Передается строка: !@#$%^&*()-=_+ абвгд
Скрипт получает  : %21@%23%24%25%5E%26*%28%29-%3D_%2B+%E0%E1%E2%E3%E4
Размер передаваемых данных методом POST содержится в переменной окружения CONTENT_LENGTH:
Передается : a=4&b=1
CONTENT_LENGTH = 7

Шаг 3 - Структура CGI программы.

Как и любая программа, программа CGI шлюза должна получить данные, обработать их и вывести результат работы в наглядной форме.

Как получать данные?

Весь процесс получения данных от веб-сервера можно представить следуюшим образом:
  1. Получить все необходимые переменные окружения. Наиболее важной на данном этапе является REQUEST_METHOD.
  2. Получить данные от сервера в зависимости от метода передачи:
     Если (REQUEST_METHOD="GET") то
       Взять данные из переменной окружения QUERY_STRING
     Иначе
       Если (REQUEST_METHOD="POST") то
         Проанализировать переменную QUERY_STRING
         Получить длинну данных из CONTENT_LENGTH
         Если (CONTENT_LENGTH>0) то
           Считать CONTENT_LENGTH байт из sdtin как данные.
       Иначе
         Выдать сообщение об ошибке и выйти.
    
  3. Декодировать все полученные данные и, если надо, разбить их на пары "имя=значение" в удобную для программы форму.

С анализом переменной REQUEST_METHOD думаю все ясно, а вот ,что значит в методе POST проанализировать QUERY_STRING, наверно, не все ясно. При передаче методом POST сервер посылает данные через стандартный поток ввода, но это не значит, что пользователь не пользовался URL'ом для передачи данных. Примером может служить многопользовательский шлюз, в котором для идентификации пользователя используется URL, а для передачи данных stdin:

http://.../cgi-bin/guestbook.cgi?user=bob&rec=0
В этом случае шлюзу гостевой книги (guestbook.cgi) сообщается два параметра user и rec, с помощью которых она может узнать "куда записывать" или "как обрабатывать" данные поступающие через поток ввода.

Считывание данных через поток sdtin должно осуществляться в динамическую память, или же во временный файл, если размер памяти ограничен или данные слишком велики для полного размещения в ОЗУ. С чем это связано? Это связано с тем, что при использовании статичестких буферов может произойти его переполнение.

Пример:

char cgi_data[1000];
...
long content_length=atol(getenv("CONTENT_LENGTH"));
fread(cgi_data,content_length,1,stdin);
...
Надеюсь все сразу видно :-)). Если content_length>1000, то произойдет переполнение cgi_data. Переполнение буферов это излюбленный метод атаки хакеров. Вместо этого лучше выделять память динамически:
char *cgi_data;
...
long content_length=atol(getenv("CONTENT_LENGTH"));
cgi_data=(char *)malloc(content_length);
if (cgi_data!=NULL)
  fread(cgi_data,content_length,1,stdin);
...
После получения данных от сервера их надо еще декодировать. Можно это сделать сразу, а можно по мере надобности. Если Вы будете это делать сразу, то Вам также придется их разбить на куски, так как при декодировании могут появиться лишние знаки "&" и "=", которые больше не позволят вам отделять пары "имя=значение" друг от друга.

Вот пример процедуры, которая декодирует данные из буфера:

/* Возвращает верхний регистр символа*/
char upperchar(char ch)
{
  if ((ch>='a') && (ch<='z'))
  {
      ch='A'+(ch - 'a');
      return ch;
   }
  else return ch;
};

/* Переводит из Hex в Dec*/
char gethex(char ch)
{
  ch=upperchar(ch);
  if ((ch>='0')&&(ch<= '9')) return (ch-'0');
  if ((ch>='A')&&( ch<='F')) return (ch-'A'+10);
};

/* 
  Ищет и возвращает параметр с именем name, в buffer.
  Если параметр name не найден, возвращает NULL.
  
Пример : message = getparam(post_buffer,"message=");

Замечание : символ "=" после имени параметра не удаляется
  и входит в возвращаемый результат, поэтому рекомендуется
  искать параметр вместе с символом "=".
 */
 
char *getparam(char *buffer,char *name)
{
 if (buffer==NULL) return NULL;

 char *pos;
 long leng=512,i=0,j=0;
 char h1,h2,Hex;

 char *p=(char *)malloc(leng);
 pos=strstr(buffer,name);
 if (pos == NULL) return NULL;

 if ((pos!=buffer) && (*(pos-1)!='&')) return NULL; 

 pos+=strlen(name);
 
 while ( (*(pos+i)!='&')&&( *(pos+i)!='\0' ))
 {
  if ( *(pos+i)=='%' )
  {
    i++;
    h1=gethex(*(pos+i));
    i++;
    h2=gethex(*(pos+i));
    h1=h1<<4;
    *(p+j)=h1+h2;
  }
  else
  {
    if (*(pos+i)!='+') *(p+j)=*(pos+i);
     else *(p+j)=' ';
  };
  i++;
  j++;
  if (j >= leng) p=(char*)realloc(p,leng+20);
  leng+=20;
 };
 if (j < leng) p=(char*)realloc(p,j+1);
      
 *(p+j)='\0';
 return p;
};

Теперь используя функцию getparam Вы сможете в любое время получить какой-нибудь параметр. Конечно эта процедура еще далека от совершенства, так как использует стандартный realloc и не обрабатывает ошибку нехватки памяти, но всеже ее можно использовать для данных не особо больших размеров.

Кстати надо еще сказать, что нецелесообразно размещать поступающие данные размером >64 Кб. в памяти, если, например, скрипт предназначен для закачки файлов, то можно (а вернее нужно) напрямую организовать запись в файл, иначе файл размером в пару мегабайт ваш скрипт не обработает :-)).

Шаг 4 - Обработка данных шлюзом.

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

Естественно, первое о чем Вы должны подумать, это как организовать возможность мгопользовательской работы скрипта. Т.е. скрипт должен отслеживать одновременный запуск нескольких копий программы. Почти все шлюзы в процессе работы производят запись или чтение каких-либо данных с диска. А одновременно записать в конец одного файла две запущенные программы не могут. Для этого и требуется отделить их друг от друга, чтобы не испортить результат их работы.

Думаю способов сделать это можно найти достаточно много, и в каждой операционной системе обязательно найдутся необходимые процедуры. Например, в Юниксах можно заблокировать на запись или чтение определенный участок файла. Такие способы могут очень помочь, но они зачастую достаточно сложны и в повседневной жизни (вернее программировании :-) редко используются, поэтому у вас могут возникнуть определенные сложности на начальном этапе.

Есть способ, который просто реализовать и он не требует больших сил. Он заключается в следующем. Ваша программа при запуске создает временный файл, например с именем lock.$. Такой же скрипт при запуске проверяет наличие этого файла, и если он не находит его, то создает его и продолжает работу, если же он его находит, то ждет несколько секунд и снова проверяет его наличие. По окончании работы каждый скрипт обязан этот временный файл удалить. Т.е. получается программа будет ждать пока существует временный файл, созданный другой программой.

Пример реализации данного метода:

FILE f_lock;

/* ..... */

int counter;
/* Проверяем наличие временного файла */
while ((f_lock = fopen("lock.$", "r")) != NULL)
  {
    fclose(f_lock);
    counter++;
    if (counter>15000)
    {
      /*Сообщаем об ошибке и выходим */
      printf("Error !!! Can't delete lock.$ file.");
      return 1;
    };
  };

/* Создаем временный файл */
f_lock = fopen("lock.$", "w");
fclose(f_lock);

/*... Работа основной части скрипта ...*/

/* Удаляем временный файл */
while (remove("lock.$")!=0);
Как видно этот способ достаточно прост в реализации и точно убережет Вас от неправильной работы скрипта.

Думаю к минусам этого метода надо отнести то, что он не позволяет выполнять свою работу другим запущенным скриптам, из-за того что и им приходится ждать. Если у Вас происходят десятки обращений в минуту или даже секунду, то лучше конечно блокировать части файлов. Но это уже возможности самой системы, под которую расчитан скрипт (и не всегда это можно реализовать). А для обычных гостевых книг данный метод просто идеален.

Если уж мы как бы заговорили о быстродействии скриптов, то надо назвать один большой минус технологии CGI в целом. Заключается он в том, что при каждом обращении к скрипту веб-сервер запускает отдельную программу для обработки запроса, а это требует достаточно много ресурсов системы и процессорного времени. В случае действительно больших нагрузок Вам придется подумать о реализации таких скриптов в совершенно ином виде, но, вероятно, встраивания кода шлюза в код Веб-сервера вы не сможете избежать. Встроить код достаточно сложно, так как вы должны не только иметь код веб сервера (что иногда просто невозможно), но и знать как все это "чудо" работает. На такое, естественно, способны не многие :-(. Хотя, написать свой сервер бывает очень полезно, для того чтобы на своей "шкуре" прочувствовать всю силу и сложность современных технологий :-))).

Шаг 5 - Вывод информации.

Для того, чтобы начать вывод данных шлюз должен сообщить браузеру тип выводимых данных и как с ними работать. Это действие производит заголовок.

Существуют два типа заголовков, вернее даже три, но один из них используется реже (о нем мы поговорим отдельно). Так вот, первый заголовок осуществляет переадресацию браузера на другой ресурс в сети и записывается вот как:

Location: URL-адрес ресурса

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

Пример:

Location: http://www.mjk.msk.ru/~dron/88.gif

Второй тип заголовка описывает данные которые будет выводить непосредственно скрипт. Задает их тип, длину и другие вспомогательные параметры. Самый простой заголовок записывается в виде:

Content-type: тип данных

Тип данных создается из двух составляющих: его типа и формата. То есть, сначала указывается один из следующих типов:

Далее через /(слэш) указывается формат данных.

Примеры заголовков:

Content-type: text/html
Content-type: image/gif
Content-type: video/mpeg

Еще заголовок может включать в себя вспомогательные параметры, Content-length - длина содержимого, Expires - время существования в кэше и т.д.

Каждый параметр указывается в отдельной строке и заканчивается переносом строки \n.

Пример:

Content-type: image/gif
Content-length: 1052

После того как заголовок сформирован и отправлен надо отправить пустую строку, для того, чтобы отделить его от основных данных.

...
printf("Content-type: text/html\n");
printf("\n");
printf("<html><h1>Simple CGI for example !!!</h1></html>");
...

Если отделяющая пустая строка (я ее сделал отдельной строкой в коде) не будет отправлена, то сервер выдаст ошибку 500 Internal Server Error : Bad header.

После отправки заголовка скрипт может приступать к выводу данных, но естественно только в указанном им формате.

Шаг 6 - Переменные среды о сервере.

Как я уже говорил, сервер и шлюз общаются между собой через стандартные потоки ввода/вывода и переменные среды. Незная названий этих переменных среды сложно что-либо получить от сервера :-). Давайте рассмотрим какие же переменные среды устанавливает сервер в момент запуска шлюза.

Переменные среды о сервере

Эти переменные сервер устанавливает для того, чтобы шлюз мог узнать с каким сервером он работает. Сюда входят данные о портах сервера, его версии, типе интерфеса CGI и т.д. В каждой версии сервера часто прибавляются новые переменные, но следующие переменные должен устанавливать любой сервер любой версии.

GATEWAY_INTERFACE

Указывает версию интерфейса CGI, который поддерживает сервер. Например:

CGI/1.1

SERVER_NAME

Содержит IP адрес сервера или его доменное имя. Например:

www.mjk.msk.ru

SERVER_PORT

Номер порта, по которому сервер получает http запросы. Стандартный порт для этого 80.

SERVER_PROTOCOL

Версия протокола Http, который использует сервер для обработки запросов. Например:

 HTTP/1.1

SERVER_SOFTWARE

Название и версия программы сервера. Например:

Apache/1.3.3 (Unix) (Red Hat/Linux)

Эти переменные обеспечивают все необходимые данные о сервере, на котором запускается скрипт. Если Ваш сервер сконфигурирован для работы с одним хостом, то скорее информация эта вам не понадобится. Сейчас же большинство серверов позволяют создавать так называемые "виртуальные" хосты. Т.е. это один компьютер, который поддерживает много IP адресов и различает запросы от клиентов по требуемому хосту, на которые он соответственно выдает странички с сайтов. Тут уже могут понадобиться данные о портах сервера (т.к. многие хосты просто "сидят" на других портах, например 8080, 8081 и т.д.) и его IP адрес с именем.

Шаг 7 - Переменные среды о запросе.

При помощи переменных данного типа шлюз узнает полную информацию о запросе к нему. Т.е. каким методом будут передаваться данные, их тип, длину и т.д.

AUTH_TYPE

Тип авторизации используемой сервером. Например:

Basic

Подробнее об авторизации в сервере Apache читайте в "Шаг 18 - Авторизация посетителей"(Html&Web) .

CONTENT_FILE

Путь к файлу с полученными данными. Используется только в серверах под Windows. Например:

c:\website\cgi-temp\103421.dat

CONTENT_LENGTH

Длинна переданной информации в байтах. То бишь сколько надо считать байтов из stdin. Например:

10353

CONTENT_TYPE

Тип содержимого посланного серверу клиентом. Например:

text/html

OUTPUT_FILE

Файл для вывода данных, используется только серверами под Windows. Аналогично CONTENT_FILE.

PATH_INFO

и PATH_TRANSLATED

В современных веб-серверах появилась возможность после имени скрипта указывать еще какой-то определенный путь. Для чего он нужен скрипту я пока не очень понимаю. Но видимо некоторым он сможет пригодиться. Эти переменные работают следующим образом. Предположим существует скрипт с именем 1.cgi в каталоге сервера /cgi-bin, тогда при вызове скрипта в таком виде:

  http://.../cgi-bin/1.cgi/dir1/dir2

данные переменные установятся следующим образом:

PATH_INFO=/dir1/dir2
PATH_TRANSLATED=/home/httpd/html/dir1/dir2

Помоему видно, что эти переменные будут указывать на папку относительно корневой директории сервера. При этом PATH_TRANSLATED будет содержать абсолютный путь до этого каталога на диске сервера. В данном случае корневым каталогом сервера считается /home/httpd/html/, и еще замечу, что это путь в Unix системах.

Под dos/win системами переменная PATH_INFO не изменится, а PATH_TRANSLATED будет содержать d:\apache\htdocs\dir1\dir2 (в данном случае корнем сервера является директория d:\apache\htdocs\).

QUERY_STRING

Содержит данные переданные через URL. Такие данные указываются после имени шлюза и знака ?. Пример:

http://.../cgi-bin/1.cgi?d=123&name=kostia

тогда переменная QUERY_STRING будет содержать

 d=123&name=kostia

и еще незабывайте, что данные передаваемые таким образом кодируются методом URL.

REMOTE_ADDR

Содержит IP адрес пользователя пославшего запрос шлюзу. Если Вы обращаетесь к любому шлюзу в интернете, то данная переменная будет содержать ваш IP адрес. Пример:

192.168.1.36

REMOTE_HOST

Содержит ваше доменное имя, при условии, что вы прописаны на каком-либо DNS сервере. Например, если ваш Dial-UP провайдер регистрирует все свои динамические IP адреса на DNS сервере, то при обращении к шлюзу, эта переменная может содержать примерно следующее:

d6032.dialup.cornell.edu

или

dial57127.mtu-net.ru

или

ppp-130-66.dialup.metrocom.ru

(брал прямо из логов сервера :-)

REQUEST_METHOD

Мы раньше говорили об этой переменной. Она содержит метод передачи данных шлюзу: GET или POST.

REQUEST_LINE

Содержит строку из запроса протокола HTTP. Например:

GET /cgi-bin/1.cgi HTTP/1.0

SCRIPT_NAME

Содержит имя вызванного скрипта. Например: 1.cgi. Все эти переменные, надеюсь, обеспечат Вам все самые необходимые данные о запросе к шлюзу.

Шаг 8 - Переменные среды о клиенте.

Осталось рассмотреть еще несколько переменных, которые несут в себе информацию о клиенте пославшем запрос. Их не много, всего три:

HTTP_ACCEPT

Эта переменная пречисляет все типы данных, которые может получать и обрабатывать клиент. Часто содержит просто */*, т.е. клиент может получать все подряд. Пример:

*/*,image/gif,image/x-xbitmap  

HTTP_REFERER

Содержит URL страницы, с которой был произведен запрос, т.е. которая содержит ссылку на шлюз. Пример:

http://www.mjk.msk.ru/~dron/index.html

HTTP_USER_AGENT

Содержит в себе название и версию браузера, которым пользуется клиент для запроса. Полезно для того, чтобы игнорировать браузеры ненавистных вам фирм :-))). Пример:

MyBrowser/1.0 (MSIE 4.0 Compatible, Win98)

В большинстве случаев эти переменные не несут в себе полезной для скрипта информации, если только Вы не будете передавать все данные в формате MS Word , и вам не потребуется знать сможет ли клиент их обработать :-). С моей точки зрения полезной можной назвать только HTTP_REFERER, которая может использоваться для защиты шлюза, например, запуска только с определенной страницы. Большинство счетчиков пользуется этой переменной для того, чтобы не учитывать хиты с других хостов, на которые этот счетчик не зарегистрирован.

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

HTTP_ACCEPT_ENCODING

Указывает набор кодировок, которые может получать клиент. Например:

koi8-r, gzip, deflate

HTTP_ACCEPT_LANGUAGE

Содержит в себе список языков в кодах ISO, которые может принимать клиент. Например:

ru, en, fr

HTTP_IF_MODIFIED_SINCE

Содержит в себе дату, новее которой должны быть получаемые данные.

HTTP_FROM

Содержит список почтовых адресов клиента. Вобщем-то весь этот список никем не ограничивается и любой браузер может сообщить кроме этих переменных еще добрый десяток. Правда уважающие себя разработчики не будут этого делать. А зачем?

Шаг 9 - Обработка простой формы.

Понятие формы языка html описано в шаге 19 раздела Html&Web. Давайте сделаем простую форму.

<form action="http://localhost/cgi-bin/primer.cgi" method=GET>
Введите свое имя пользователя:
<input type=text maxlength=150 name=user>
<p><input type=submit value=Send>
</form>

Мы уже обсуждали методы передачи данных в шлюз раньше. Какие тут могут быть советы? Лично мне кажется передавать такие маленькие формы лучше всего посредством метода GET. С чем это связано? Во-первых получить данные из переменной окружения намного легче, чем считать их из потока. Чтобы считать данные из потока надо точно знать их размер, позаботиться о выделении памяти и многом другом. Тут же обо всем позаботится сервер и встроенные средства программирования вашего языка. Во-вторых пользователь сможет обратиться к вашему скрипту непосредственно из адресной строки браузера. Например, многие программы для поиска информации в интернете используют различные поисковые сервера. Для того чтобы сделать запрос к одному из них требуется всего лишь вызвать браузер и сообщить ему URL. В Windows это делается просто в командной строке (а значит и просто сделать программе):

start http://www.abc.com/cgi-bin/search.cgi?word=hello&language=ru

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

С методом POST в этом отношении сложнее, для этого программе нужно уметь работать по протоколу HTTP и связываться с серверами в интернете. Размер и сложность такой программы будет на порядок выше. Поэтому, если Ваш ресурс может быть полезен и при этом передаваемые ему данные не будут превышать 32 Кб (это ограничение на длинну URL, если я все правильно помню :-), то лучше метода GET не найти.

Давайте напишем программу для обработки этой формы.

#include <stdio.h>
#include <stdlib.h>

//Здесь надо вставить процедуру получения
//параметра по его имени... Она была описана
//раньше.

char *getparam(...)
{
};

int main()
{
  char *user=NULL;
  char *content=NULL;
  char *request_method=getenv("REQUEST_METHOD");
  if (strcmp(request_method,"GET")!=0)
	{
		printf("Content-type: text/html\n\n");
		printf("Unknown REQUEST_METHOD. Use only GET !\n");
		return -1;
	};
  
  content=getenv("QUERY_STRING");
  user=getparam(content,"user=");
  printf("Content-type: text/html\n\n");
  printf("User name=\"%s\"\n",user);
};

После того как вся программа будет собрана и откомпилированна, расположите ее в директории cgi-bin вашего вебсервера. Теперь можно смело пробовать форму написанную выше. Я для теста использовал браузер напрямую... Как я уже говорил, достаточно набрать

http://localhost/cgi-bin/primer.cgi?user=hello

И Вы увидите тот же результат, что и при использовании формы. Кстати вот он:

User name="hello" 

Обратите внимание на то, что я уже в первой программе начинаю предусматривать возможные ошибки, и поэтому неизвестные программе методы она не будет выполнять. Вообще Вы должны приучиться к этому сразу, т.е. отлавливать возможные ошибки уже на этапе создания шлюза, иначе без этого он может стать большой дырой в системе безопастности вашего сервера. Я по ходу шагов еще тысячу раз упомяну Вам об этом, но только потому, что это действительно очень важно.

Шаг 10 - Как запускать CGI.

Если у Вас от чего-то все работает, то очень СТРАННО. Как правило новичку, чтобы запустить скрипт приходится попотеть. Какие же основные проблемы возникают при запуске скрипта?

Настройки сервера

Первой, но не главной причиной может послужить неправильная настройка сервера. Скорее всего попросту он не имеет права запускать скрипты из этой директории. Я всегда рассматриваю только Web-сервер Apache, поэтому приведу настройки для него. Кстати сегодня в ComputerWorld(8.02.2000) опубликовали результаты исследований RUNet'a. Оказалось, что Apache установлен на 78% всех серверов, веб-сервера от Microsoft - 19%, доля остальных серверов составляет по 1%. Так что я опять говорю, лучше Apache Вы не найдете !!!

Ладно отвлеклись от темы... Все настройки Apache для каждой директории задаются с помощью файла .htaccess. Если такового в вашей директории не имеется, то создавайте его. В него запишите следующее:

Options ExecCGI

Или даже посоветую обратиться к документации, но думаю она не понадобится. Теперь любые скрипты в этом каталоге будут загружаться без проблем.

Неверные атрибуты на файле скрипта

Если на вашем сервере установлена система подобная Windows, то эта проблема вас не касается, так как все программы *.exe "эта" система загружает без вопросов.

В случае если Ваша система Unix, то вам повезло меньше, особенно если Вы до этого видели только Виндовоз.

В кратце поясню... Во всех системах Unix для каждого файла устанавливаются атрибуты файлов. Этих атрибутов (как правило) девять. Даю список таковых.

Owner Read
Owner Write
Owner Execute

Group Read
Group Write
Group Execute

Other Read
Other Write
Other Execute

Если кратко, то в Unix системах создаются пользователи и разделяются на группы. Вы вот при входе в систему набираете свой логин и пароль, т.е. Вы являетесь пользователем. Также по FTP и т.д. Все кто имеет доступ к системе является ее пользователем.

Атрибуты типа Owner задают параметры для Вас, т.е. для владельца файлов. Атрибуты Group определяют уровень доступа для вашей группы, т.е. если Вы принадлежите к группе Webmasters, то при установке атрибута Group Write любой другой пользователь, который принадлежит к группе Webmasters сможет записывать в этот файл информацию. Думаю для чего Other понятно, это значит всем остальным.

При записывании файла через FTP атрибуты файла устанавливаются по умолчанию

rw.r..r..

Т.е. Вы можете писать и читать, а остальные могут только читать. Как видите ни один атрибут не указывает на то, что файл загружаемый. Вы должны добиться такого:

rwxr.xr.x

Т.е. установить атрибут Execute во всех группах.

Как это сделать это другой вопрос, давайте рассмотрим с вами работу с некоторыми FTP клиентами. Сразу скажу, что не использую никакие Виндовые программы, т.е. графические "проги" рассчитанные на любителей делать все одним кликом мыши... Это ламерство господа.

Я пользуюсь нашим, т.е. российским файловым менеджером FAR, если у вас его нет, то Вы много потеряли... И я вам сочувствую.

Так вот в нем надо нажать Ctrl-A на том файле, который Вы закачали на сервер (только делаете это не на локальном диске, а на FTP, а то увидите вместо атрибутов Unix атрибуты Досовской файловой системы) Делаете следующую картинку:

  R  W  X   R  W  X   R  W  X
 [x][x][x] [x][ ][x] [x][ ][x]

И нажимаете Okey. Теперь все классно.

Если у Вас нет FAR, то у Вас ОБЯЗАНА быть программа в системе, которая занимается сервисом FTP. В большинстве систем (и в Винде) такая программа называется ftp. Запустите ее. Наверняка она обладает только командной строкой, так что потейте... :-) Я рассмотрю программу ftp.exe, которая входит в виндовс.

Первое, что надо сделать открыть Ваш сайт, делается это командой open

ftp> open www.mjk.msk.ru
Связь с mjk.
220 mjk-gw.mjk.msk.ru FTP server (Version wu-2.4.2-academ[BETA-18](1)
Mon Aug 3 19:17:20 EDT 1998) ready.
Пользователь (mjk:(none)): dron
331 Password required for dron.
Пароль:
230-Please read the file README.linux
230-  it was last modified on Sat Feb  5 16:31:50 2000 - 3 days ago
230 User dron logged in.
ftp>

Теперь Вы в системе. Наберите help для получения основных команд. Попробуйте набрать dir. Пример вывода:

200 PORT command successful.
150 Opening ASCII mode data connection for /bin/ls.
total 48
drwx------   3 dron     mjkusers     1024 Feb  6 16:58 .
drwxr-xr-x  75 root     root         2048 Feb  1 00:03 ..
-rw-r--r--   1 dron     mjkusers     1155 Jun 24  1999 .Xdefaults
-rw-------   1 dron     mjkusers       24 Jan  8 11:35 .bash_history
-rw-r--r--   1 dron     mjkusers       24 Jun 24  1999 .bash_logout
-rw-r--r--   1 dron     mjkusers      230 Jun 24  1999 .bash_profile
-rw-r--r--   1 dron     mjkusers      124 Jun 24  1999 .bashrc
-rw-r--r--   3 dron     mjkusers     1324 Jan  8 11:32 123.cgi
-rw-r--r--   1 dron     mjkusers    37165 Feb  5 16:31 README.linux
226 Transfer complete.
691 байт получено за 0.33 с (2.09 КБ/с)

Мне например надо теперь установить атрибут загрузки на файл 123.cgi, как видите у него такого атрибута нет. Такую возможность ftp.exe не предоставляет, зато он может посылать команды непосредственно FTP - серверу, т.е. что нам и требуется. Если вы вызывали помощь, то знаете, что такую функцию выполняет команда quote. Чтобы Вам особо не разбираться просто приведу команду.

quote SITE CHMOD 755 123.cgi

Теперь на файле 123.cgi будут установлены необходимые атрибуты. Если Ваш файл располагается в другой директории, то пользуйтесь командой cd (change directory).

Неправильный атрибут на каталоге скрипта

Многие скрипты не только выводят какую-то информацию, но и еще записывают что-то в определенные папки или файлы. Тут надо предусмотреть правильный доступ к этим ресурсам. Любой Web-сервер работает не от вашего имени, а от другого, поэтому запуская скрипт он не предоставляет ему Ваши возможности. Вы должны поставить атрибуты на директорию, в которую записан скрипт, для полного доступа, т.е. для записи всем кому угодно. Для установки таких атрибутов в FAR'е поставьте все крестики. В ftp.exe команда такая

quote SITE CHMOD 777 <директория>

Хочу Вас также предостеречь, делая полный доступ на директорию помните, что любой "злоумышленник" может вам подпортить жизнь, стереть Ваш скрипт и например записать свой, или записывать неправильные данные в ваши файлы. Короче он может делать, что угодно. Особенно опасно делать полный доступ к директории в которой лежит страничка, потому как рано или поздно вы ее там не обнаружите :-(.

Поэтому СОВЕТ. Создавайте специально для скриптов отдельные каталоги и используйте их для записи данных. А лучше всего делать доступ только отдельному файлу (в этом случае маска доступа не 777, а 666 !!!) Причем думаю не плохо было бы позаботиться и о шифровании, т.к. любой скрипт может записывать конфиденциальную информацию, такую как номера кридитных карт, почтовые адреса, имена и фамилии. Любой "спаммер" или "хакер" скажет вам большое спасибо за такой подарок, хотя скорее всего "спасибо" вам скажут "дяди в погонах".

Вобщем-то все, надеюсь у Вас все заработает :-)