Содержание


У истоков Apache. Часть 2

Библиотека libwww

Comments

Серия контента:

Этот контент является частью # из серии # статей: У истоков Apache. Часть 2

Следите за выходом новых статей этой серии.

Этот контент является частью серии:У истоков Apache. Часть 2

Следите за выходом новых статей этой серии.

Libwww - это открытая библиотека, включающая в себя API для работы с вебом.

Портирована под unix, windows, mac. На ее основе можно писать разнообразные клиентские приложения: браузеры, роботы и т. д. Библиотека включает в себя полный набор стандартов HTTP/1.1(кеширование, аутентификацию и т.д). На ее базе также написано различное программное обеспечение, в том числе CERN httpd, Cygwin, Lynx, Mosaic и другие.

1. Как инсталлировать libwww под unix

Исходники libwww можно найти по адресу: http://www.w3.org/Library/Distribution.html.

Процедура инсталляции стандартная:

		 ./configure 
		make
		make install

По умолчанию после инсталляции библиотека окажется в каталоге /usr/local/lib.

При конфигурации можно этот путь поменять на '--prefix=PATH'. По умолчанию динамическая и статическая линковки включены. Также в библиотеку включены опции:

	--with-md5[=PATH] -  поддержка HTTP digest authentication
	--with-mysql[=PATH]
	--with-regex[=PATH]
	--with-ssl[=path]
	--with-zlib[=PATH]

2. Архитектура libwww

Как уже было сказано в предыдущей статье, библиотека libwww имеет модульную архитектуру и состоит из 5 основных частей:

  1. Базовые модули: здесь реализована кросс-платформенная переносимость.
  2. Ядро: базовые функции.
  3. Потоковые модули: обработка потоков.
  4. Модули доступа: работа с интернет-протоколами.
  5. Модули приложений.
Архитектура libwww
Архитектура libwww

Ядро можно разделить на 3 слоя:

  1. Request обьект.
  2. Net обьект.
  3. Channel обьект.

Само приложение при работе с libwww использует напрямую последние 3 группы модулей.

В основе библиотеки лежит парадигма "запрос/ответ" (request/responce). Клиентское приложение делает запрос на сервер по определенному URL, который обрабатывается сервером, в файловой системе находится документ, соответствующий этому URL, и отдается клиенту. Библиотека рассчитана на одновременную обработку множества запросов от многих клиентов.

Поток (stream) - объект, который имеет дело с последовательностью символов, проходящих через стандартный вход/выход. Libwww использует потоки для транспортировки данных между 3 основными каналами: приложениями, сетью и файловой системой. Поток делает библиотеку событийной (event) в том смысле, что когда данные ложатся в поток, это фиксируется как событие под названием "поток готов". Есть один общий родитель, от которого унаследованы все остальные потоки - Generic Stream Class.

В нем есть набор методов, которые унаследуют все остальные потоки:

Есть один общий родитель, от которого унаследованы все остальные потоки - Generic Stream Class.
Есть один общий родитель, от которого унаследованы все остальные потоки - Generic Stream Class.

От этого родителя унаследован поток под названием Structured Stream Class, который переводит поток в набор тегов формата HTML 3.

Потоки в библиотеке libwww разделены на группы:

  1. Протокольные потоки.
  2. Конвертеры.
  3. Представления - запись данных в локальный файл.
  4. Потоки ввода/вывода - запись данных в сокет или в объект типа ANSI C FILE.
  5. Базовые потоки - реализуют технологию вложенных каскадных потоков.

Запрос, или request, - это операция, применимая к URL. Как клиентский, так и серверный объект Request происходят от одного класса. Пример запроса для клиента - это клиентский GET-запрос по определенному URL. Пример запроса для сервера - это загрузка локального файла по этому URL. Класс Request имеет большой набор методов – например, PUT, POST, DELETE и т.д. Время запроса ограничено временем его обслуживания. Libwww автоматически не удаляет объекты запросов, это делается на стороне приложения путем вызова callback-функций. Libwww может обрабатывать одновременно условно неограниченное число запросов.

Фильтр - объект, вызываемый непосредственно перед запросом. Например, аутентификационный фильтр проверяет, имеет ли клиент нужные права, после чего модифицирует информацию в хидере запроса либо отклоняет запрос.

Libwww использует модель не-блокирующих сокетов. Для управления событиями используется событийный класс Event. Его интерфейс включает регистрацию дескриптора сокета и текущее состояние операции. Когда менеджер событий получает информацию о том, что сокет прочитал порцию данных, он передает управление libwww. Libwww занимается созданием и удалением своих внутренних тредов, т.е. использует как бы многопоточную - Pseudo Threads - объектную модель. В основе этой модели лежат callback-функции.

В приложении должен быть как минимум один обработчик событий и один обработчик завершения. Библиотечная функция select() на самом деле блокирует клиентское приложение при отсутствии событий. Специальный Timeout Handler сделан для того, чтобы остановить эту ветку в приложении.

Библиотека хранит текущее состояние запроса. На следующей картинке описана реализация HTTP 1.0 модуля.

Реализация HTTP 1.0 модуля
Реализация HTTP 1.0 модуля

BEGIN State - начальное состояние модуля, когда ожидается запрос от клиента.

NEED_CONNECTION State - HTTP модуль готов установить коннект с удаленным хостом. Библиотека кеширует все произведенные коннекты и сортирует их в порядке времени, затраченном на само соединение. В последующем при повторном обращении с того же хоста из кеша выбираются соединения с наименьшим временем и инициализируются в первую очередь.

NEED_REQUEST State - после установки коннекта клиент посылает серверу объект HTTP Request, в который входит набор заголовков - HTTP Header, а также данные. Заголовок имеет формат:

		<METHOD> <URI> <HTTP-VERSION> CRLF

SENT_REQUEST State - событие наступает, когда в ответ на запрос сервер вернет ответ, либо истечет тайм-аут, либо случится ошибка. Отличие протокола HTTP 0.9 от версии HTTP 1.0 в том, что 0.9 не включает HTTP заголовок в ответ.

NEED_ACCESS_AUTHORIZATION State - требуется идентификация пользователя по паролю.

REDIRECTION State - перенаправление на другой сервер.

NO_DATA State - данные отсутствуют.

NEED_BODY State - в ответе сервера присутствуют данные.

GOT_DATA State - данные получены, запрос можно закрывать.

ERROR или FAILURE State - в этом случае соединение закрывается.

3. Первое приложение

После того, как вы установили libwww, на ее основе можно строить веб-приложения. Библиотека разделена на несколько пакетов, интерфейс к которым описан в соответствующем заголовке www*.h.

Таблица пакетов и интерфейсов:

ПакетБиблиотекаЗаголовок
Basic Utility libwwwutil.a WWWUtil.h
Core libwwwcore.a WWWCore.h
Initialization libwwwinit.a WWWInit.h
Application libwwwapp.a WWWApp.h
Transport libwwwtrans.a WWWTrans.h
MIME libwwwmime.a WWWMIME.h
Protocol libwwwfile.a
libwwwftp.a
libwwwhttp.a
WWWFile.h
WWWFTP.h
WWWHTTP.h
Caching libwwwcache.a WWWCache.h
Stream libwwwstream.a WWWStream.h
HTML / XML libwwwhtml.a WWWHTML.h
Database Access libwwwsql.a WWWSQL.h

Для компиляции вашего приложения компилятору нужно передавать несколько параметров.

После инсталляции можно проверить эти опции из командной строки:

>> libwww-config --cflags 	- вывод флагов
>> libwww-config --libs	- вывод флагов для линковки

Скомпилировать вашу программу можно прямо из командной строки.

Например, текст исходника libapp_1.c, в которой делается простая инициализация libwww:

#include "WWWLib.h"

int main()
{
    HTLibInit("TestApp", "1.0");
    HTLibTerminate();
    return 0;
}

Компиляция этого примера из командной строки:

>> gcc -o app1 `libwww-config --cflags` libapp_1.c `libwww-config --libs`

4. Инициализация libwww

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

#include "WWWLib.h"
#include "WWWHTTP.h"
#include "WWWInit.h"

int main()
{
    HTList * converters = HTList_new();		/* список конвертеров */
    HTList * encodings = HTList_new();		/* список енкодеров */

    HTEventInit(); /* инициализируем библиотечный event loop */

    HTLibInit("TestApp", "1.0"); /* инициализация ядра libwww*/

    HTSetTraceMessageMask("sop"); /* включаем трассировку */

    HTTransportInit(); /* регистрируем транспортные протоколы*/

    HTProtocolInit(); /* регистрируем протоколы */

    HTNetInit(); /* регистрация фильтров BEFORE и AFTER*/

    HTConverterInit(converters); /* регистрация конвертеров */
    HTFormat_setConversion(converters);

    HTTransferEncoderInit(encodings);  /* регистрация енкодеров/декодеров */
    HTFormat_setTransferCoding(encodings);

    HTFileInit(); /* регистрируем файловую систему */

    HTMIMEInit(); /* регистрация парсеров MIME-заголовков */

    HTIconInit(NULL);
    HTAlertInit();

    HTLibTerminate(); /* закрываем библиотеку */
    return 0;
}

5. Профайл

Ваше приложение может выполнять различные функции. В зависимости от функционала все приложения можно разделить на несколько основных категорий:

  1. Обычный клиент, выполняющий одновременно множество запросов с помощью не-блокирующих сокетов.
  2. HTML-клиент.
  3. Обычный клиент, но без кеширования.
  4. HTML-клиент без кеширования.
  5. Клиент на базе блокирующих сокетов.
  6. Робот.

Для инициализации типа клиента используется профайловый модуль - он же плагин, который инициализирует соответствующий тип такого клиента:

  • extern void HTProfile_newClient ( const char * AppName, const char * AppVersion);
  • extern void HTProfile_newHTMLClient (const char * AppName, const char * AppVersion);
  • и т. д.

Удаляется профиль командой:

	extern void HTProfile_delete (void);

6. Callback функции

Это еще один метод расширения функционала ядра. Такие функции, как правило, активируются в двух случаях:

  1. Перед запросом.
  2. После завершения запроса.

В библиотеке есть стандартный набор callback-функций для часто повторяющихся событий: проверка кеширования, прокси-запрос, логирование, проверка URL и т.д.

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

7. Пример с загрузкой удаленного URL

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

	loadtofile http://www.w3.org -o w3chome.html

Текст примера:

#include "WWWLib.h"			      /* Global Library Include file */
#include "WWWMIME.h"				    /* MIME parser/generator */
#include "WWWNews.h"				       /* News access module */
#include "WWWHTTP.h"				       /* HTTP access module */
#include "WWWFTP.h"
#include "WWWFile.h"
#include "WWWGophe.h"
#include "WWWInit.h"

#define APP_NAME		"GETTOOL"
#define APP_VERSION		"1.0"
#define DEFAULT_OUTPUT_FILE     "get.out"

PRIVATE int printer (const char * fmt, va_list pArgs)
{
    return (vfprintf(stdout, fmt, pArgs));
}

PRIVATE int tracer (const char * fmt, va_list pArgs)
{
    return (vfprintf(stderr, fmt, pArgs));
}

/*
**  Здесь завершается цикл - event loop - по загрузке урла.
*/
int terminate_handler (HTRequest * request, HTResponse * response, void * 
param, int status)
{
    HTRequest_delete(request); /* удаляем запрос */

    HTProfile_delete(); /* удаляем профайл */

    exit(status ? status : 0);
}

int main (int argc, char ** argv)
{
    int		        arg = 0;
    char *              outputfile = NULL;
    char *              getme = NULL;
    HTRequest *         request = NULL;

    HTProfile_newNoCacheClient(APP_NAME, APP_VERSION); /* Создание клиентского профайла */

    HTPrint_setCallback(printer); /* вывод на экран */
    HTTrace_setCallback(tracer);

    HTNet_addAfter(terminate_handler, NULL, NULL, HT_ALL, HT_FILTER_LAST); 
/* прерывающий фильтр для всего приложения */

    HTHost_setEventTimeout(10000); /* тайм-аут для response */

    /* Парсим командную строку */
    for (arg=1; arg<argc; arg++) {
        if (!strcmp(argv[arg], "-o")) { 
            outputfile = (arg+1 < argc && *argv[arg+1] != '-') ?
                argv[++arg] : DEFAULT_OUTPUT_FILE;
            
        } else {
            getme = argv[arg];
        }
    }

    if (!outputfile) outputfile = DEFAULT_OUTPUT_FILE;

    if (getme && *getme) {
        request = HTRequest_new();

        /* Запускаем загрузку */
        if (HTLoadToFile(getme, request, outputfile) != YES) {
	    HTPrint("Can't open output file\n");
	    HTProfile_delete();
	    return 0;
	}

        /*  event loop... */
        HTEventList_loop(request);

    } else {
	HTPrint("Type the URI of document you want to load and the name of the
	 local file.\n");
	HTPrint("\t%s <address> -o <localfile>\n", argv[0]);
	HTPrint("For example, %s http://www.w3.org -o w3chome.html\n", argv[0]);

        /* Delete our profile if no load */
        HTProfile_delete();
    }

    return 0;
}

8. Приложение-сервер

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

Все рассмотренные здесь примеры можно найти в самом дистрибутиве libwww в подкаталоге Library/Examples. Код данного примера лежит в исходнике под названием listen.c. Программа открывает слушающий raw-socket и в случае соединения посылает ответ:

	/* регистрация TCP */	
	HTTransport_add("tcp", HT_TP_SINGLE, HTReader_new, HTWriter_new); 

	HTProtocol_add("noop", "tcp", ms->port, NO, NULL, HTLoadSocket);

	ms->request = HTRequest_new(); /* инициализация обьекта Request */
	HTRequest_setOutputFormat(ms->request, DEFAULT_FORMAT);
HTRequest_setOutputStream(ms->request,  HTFWriter_new(ms->request, OUTPUT, YES));

	/* запуск прослушивания */
	HTPrint("Listening on port %d\n", ms->port);
	if ((status = HTServeAbsolute("noop://localhost", ms->request)) == NO) 
	{
	    HTPrint("Can't listen on port %d\n", ms->port);
	    Cleanup(ms, -1);
	}

	/* event loop... */
	if (status == YES) HTEventList_newLoop();

9. Альтернативы libwww

В библиотеке есть ряд недостатков, которые остались с момента прекращения поддержки.

Нет единого API, которое разбито на 2 уровня: средний и высокий. Вместо thread safe в библиотеке имеет место псевдопоточность. На данный момент есть несколько альтернатив, среди которых можно отметить:

  1. libcurl - является современным аналогом libwww, развивается и поддерживается сообществом. В сравнении с libwww библиотека libcurl работает быстрее, является thread-safe, поддерживает больше протоколов. В свою очередь libwww предлагает кеширование и парсинг HTML, чего нет в libcurl.
  2. Библиотека Netscape Network Library (netlib) является сердцевиной популярнейшего браузера, написана на С.
  3. Другим аналогом может выступать libghttp - GNOME http client, написан на С.
  4. http-tiny - компактная библиотека для работы из командной строки с http-запросами, умеет работать с прокси.
  5. wget - всем известное приложение, а не библиотека, его код может послужить основой для ваших собственных веб-приложений.

Заключение

Библиотека libwww является ядром для CERN httpd, в ней сосредоточены наиболее важные API. Эта библиотека имеет модульную структуру, которая позже была скопирована в других веб-серверах, в частности, в Apache. Были рассмотрены возможности библиотеки в плане написания различных веб-приложений. На ее основе можно писать как клиентские, так и серверные приложения.


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source, Linux
ArticleID=619917
ArticleTitle=У истоков Apache. Часть 2: Библиотека libwww
publish-date=01272011