CGI и FastCGI плюс Java
Вдохновленный множественными статьями о микросервисах, пришла в голову идея, реализовать свой микро веб-сервис, но не стандартным путем.
Что такое CGI и FastCGI ?
CGI (от англ. Common Gateway Interface — «общий интерфейс шлюза») — стандарт интерфейса, используемого для связи внешней программы с веб-сервером. Программу, которая работает по такому интерфейсу совместно с веб-сервером, принято называть шлюзом, хотя многие предпочитают названия «скрипт» (сценарий) или «CGI-программа».
Сам интерфейс разработан таким образом, чтобы можно было использовать любой язык программирования, который может работать со стандартными устройствами ввода-вывода. Такими возможностями обладают даже скрипты для встроенных командных интерпретаторов операционных систем, поэтому в простых случаях могут использоваться даже командные скрипты.
Например bash скрипт :
#!/bin/bash
echo "Content-Type: text/plain;charset=us-ascii\n\n";
echo "Hello, world!\n\n";
Все скрипты, как правило, помещают в каталог cgi (или cgi-bin) сервера, но это необязательно: скрипт может располагаться где угодно, но при этом большинство веб-серверов требуют специальной настройки. В веб-сервере Apache, например, такая настройка может производиться при помощи общего файла настроек httpd.conf или с помощью файла .htaccess в том каталоге, где содержится этот скрипт.
То есть, создав файл с раширением .cgi (например hello.cgi), наполнив его строками приведенными выше и поместив его в каталог cgi(или cgi-bin) и запустив веб-сервер потдерживаюший cgi с правильно-выставленными путями к исходным файлам *.cji, мы можем открыть веб-бразер и обратиться по адресу http://localhost/cgi-bin/hello.cgi. И результат будет : Hello, world!
Стандарный этапы выполнения cgi скрипта :
- на сервер приходит HTTP запрос
- сервер парсит этот запрос
- создает изолированный набор переменных окружения (REQUEST_METHOD, CONTENT_LENGTH, QUERY_STRING и т.д.) и запускает cgi скрипт. (Как вы догадались, эти перменные мы и будет считывать нашим скриптом, это входящие данные, например данные запроса - QUERY_STRING)
- после завершения скрипта, веб-сервер формирует ответ на основании полученного вывода в STDOUT и отправляет клиенту.
Плюсы :
- как было сказано, подойдет любой язык(среда) который способен считывать и отдавать данные из обычного потоков ввода и вывода STDIN/STDOUT
Минусы :
- каждый новый запрос формирует отдельный экзепляр скрипта (отдельный процесс), что сказывается на быстродействии
FastCGI - дальнейшее развитие технологии CGI. По сравнению с CGI является более производительным и безопасным.
Отличия от CGI :
- Недостаток CGI-программ в том, что они должны быть перезапущены веб-сервером при каждом запросе, что приводит к понижению производительности. FastCGI, вместо того чтобы создавать новые процессы для каждого нового запроса, использует постоянно запущенные процессы для обработки множества запросов. Это позволяет экономить время.
- В то время как CGI-программы взаимодействуют с сервером через STDIN и STDOUT запущенного CGI-процесса, FastCGI-процессы используют Unix Domain Sockets или TCP/IP для связи с сервером. Это даёт следующее преимущество над обычными CGI-программами: FastCGI-программы могут быть запущены не только на этом же сервере, но и где угодно в сети. Также возможна обработка запросов несколькими FastCGI-процессами, работающими параллельно.
FastCGI может быть использован в любом языке, поддерживающем сокеты. Есть большое разнообразие библиотек для разных языков программирования.
CGI + Java
Начнем с простого. Как было описано в первых этапах, нам необходим механизм считывания переменных окружения, для того что бы знать входящие параметры HTTP запроса. Мы помним что веб-сервер создает эти переменные в изолированном виде для каждого процесса, и что простого вызова System.getProperty(“cgi.query_string”) будет недостаточно. Мы должны передать эти переменные java программе, а если быть точным, то JVM процессу. Для этого создадим скромный bash скрипт, который будет запускать java и передавать нужные параметры :
#!/bin/sh
java \
-Dcgi.content_type=$CONTENT_TYPE \
-Dcgi.content_length=$CONTENT_LENGTH \
-Dcgi.request_method=$REQUEST_METHOD \
-Dcgi.query_string=$QUERY_STRING \
-Dcgi.server_name=$SERVER_NAME \
-Dcgi.server_port=$SERVER_PORT \
-Dcgi.script_name=$SCRIPT_NAME \
-Dcgi.path_info=$PATH_INFO \
Hello
После, соответственно написать саму java программу (hello.java), скомпилировать и положить рядом. Для удобства я использовал библиотеку cgi_lib, методы которой инкапсулируют логику чтения и генерации ответа.
import java.util.*;
import java.io.*;
public class Hello {
public static void main( String args[]){
//
// Here is a minimalistic CGI program that uses cgi_lib
//
// Print the required CGI header.
//
System.out.println(cgi_lib.Header());
//
// Parse the form data into a Hashtable.
//
Hashtable form_data = cgi_lib.ReadParse(System.in);
//
// Create the Top of the returned HTML page
//
String name = (String)form_data.get("name");
System.out.println(cgi_lib.HtmlTop("Hello There " + name + "!"));
System.out.println("<h1 align=center>Hello There " + name +
"!</h1>");
System.out.println("Here are the name/value pairs from the form:");
//
// Print the name/value pairs sent from the browser.
//
System.out.println(cgi_lib.Variables(form_data));
//
// Print the Environment variables sent in from the Unix script.
//
System.out.println("Here are the CGI environment variables/value pairs" +
"passed in from the UNIX script:");
System.out.println(cgi_lib.Environment());
//
// Create the Bottom of the returned HTML page to close it cleanly.
//
System.out.println(cgi_lib.HtmlBot());
}
}
На самом деле методы класса cgi_lib используют функции System.getProperty() для чтения входящих данных, а поток вывода System.out.println() для формирования ответа.
Вот и все. Осталось проверить результат по адресу http://localhost/cgi-bin/Hello.cgi.
FastCGI + Java
С FastCGI придется немножко повозиться, но результат эта реализация даст лучше, потому что во первых, процесс запускается только единожды, во вторых, можно настроить лоад балансер, в третьих само приложение может находиться по совершенно другому ip адресу.
- Выкачать исходники этой библиотеки
- Распаковать и перейти в директорию java, где мы и будем создавать веб-приложение
- Создать Класс TinyFCGI.java
import com.fastcgi.FCGIInterface;
import java.io.*;
import static java.lang.System.out;
class TinyFCGI {
public static void main (String args[]) {
int count = 0;
FCGIInterface fcgiinterface = new FCGIInterface();
while(fcgiinterface.FCGIaccept() >= 0) {
count++;
out.println("Content-type: text/html\n\n");
out.println("<html>");
out.println(
"<head><TITLE>FastCGI-Hello Java stdio</TITLE></head>");
out.println("<body>");
out.println("<H3>FastCGI-HelloJava stdio</H3>");
out.println("request number " + count +
" running on host "
+ System.getProperty("SERVER_NAME"));
out.println("</body>");
out.println("</html>");
}
}
}
- скомпилировать все java файлы находящиеся в директории java
- запустить приложение java -DFCGI_PORT=9884 TinyFCGI (помним что FastCGI-процессы используют Unix Domain Sockets или TCP/IP для связи с сервером.)
- теперь осталось настроить Apache установив моудль mod_proxy_fcgi
- сделать доступным sudo a2enmod proxy_fcgi
- создать файл конфигурации /etc/apache2/conf-enabled/your_site.conf
<Location /your_site>
ProxyPass fcgi://localhost:9884/
</Location>
- и перезапустить сервер sudo apache2ctl restart
Посмотреть что же у нас получилось можно по адресу - http://localhost/your_site.
Вывод
Конечно *CGI интерфейс не заменит вам полноценного JavaEE сервера или сервлет контейнера, но я бы сказал, что это простой и альтернативный путь к созданию веб-приложения “на коленке”(микро-сервис) и используя не только Java, но и тот язык, который вы довольно хорошо знаете.