|
|
|||||||||||||||||||||||||||||
|
WebService c поддержкой gzip на CИсточник: habrahabr
В посте речь пойдет о моем опыте встраивания XML-RPC интерфейса в утилиту, написанную на C. Интерфейс должен предоставлять доступ к статитстике и результатам работы утилиты. Одно из требований к интерфейсу - поддержка ответов в формате gzip, в целях экономии трафика. Мне очень хотелось обойтись малой кровью и вот что из этого получилось. Прежде всего тесты Начнем с тестов. XML-RPC клиент на python умещается в 4 строчки. Кстати, он как раз понимает ответы в gzip формате. import xmlrpclib Отлично! Теперь мы знаем какие HTTP заголовки получает клиент. И если формат некорректен - получаем исключение с подробным стеком вызовов. В случае ошибки, все это поможет нам пролить свет на причину ее возникновения. Zlib В вики написано, что формат gzip основан на алгоритме сжатия deflate, который реализован в библиотеке zlib. В этой библиотеке есть отличный метод compress. int compress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen); Обрадовавшись находке, я сразу решил попробовать этот метод и набросал простой костяк приложения, но этого оказалось не достаточно. Клиент отказывался понимать содержимое ответов сервера и вываливался с исключением. Пришлось изучить формат gzip продробнее. Gzip Тут все довольно просто.
Сжатые данные обрамляются десятью байтами заголовка специального формата и восьмью байтами суффикса, содержащего контрольную сумму исходных данных и их длину. Заголовок начинается с магических констант ID1 = 31 (0x1f, \037), ID2 = 139 (0x8b, \213), говорящих о начале данных в формате gzip. Далее идет метод сжатия CM (Compress Method), в случае deflate СM=8. Заним следуют флаги, в нашем случае FLG=1, что означает текстовые данные. Потом идут 4 байта даты последнего изменение исходных данных, в нашем случае MTIME=0. Затем идут дополнительные флаги XFL=2 (высокая степень сжатия). Имя операционной системы позволим себе оставить неопределенным OS=255. Для вычисления контрольной суммы воспользуемся функцией из той же zlib uLong crc32 (uLong crc, const Bytef *buf, uInt len); Но и этого оказывается мало. Нашего клиента все еще не устраивают ответы сервера. И снова zlib Посмотрим, в каком формате возвращает нам данные zlib. Оказалось, zlib добавляет специальные 2-байтный префикс и 4-байтный суффикс к сжатым данным (подробнее). Избавимся от них и добавим заголовок и суффикс формата gzip. И, о чудо! Клиент наконец-то нас понял! На заметку: в библиотеке Qt есть метод qCompress(), который возвращает данные сжатые библиотекой zlib, но еще и с 4-байтным префиксом длины сжатых данных. Итог Чтобы сформировать данные в формате gzip, сжимаем исходные данные функцией compress, в полученном массиве первые 2 байта заменяем 10-байтным заголовком gzip, вместо последних 4 байт ставим контрольную сумму и длину исходных данных. Пример рабочего XML-RPC сервера, возвращающего данные в gzip формате приведен ниже. #include <zlib.h> #define PORT 8080 #define BUF_SZ 1024 #define ZLIB_PREFIX_SZ 2 #define GZIP_PREFIX_SZ 10 // Returns listen socket handle int main(int argc, const char *argv[]) { fprintf(stderr, "HTTP Server with gzip encoding support using zlib (%s)\r\n", ZLIB_VERSION); char httpheaders[BUF_SZ] = {0,}; int srvsock = create_srvsock(PORT, MAXCONN); fprintf(stderr, "Server is started on port %d\r\n", PORT); while (true) { // 1. Accepting connection // 2. Retreiving response // 3. Compressing response // substract zlib prefix and suffix: http://www.ietf.org/rfc/rfc1950.txt // 4. Writing HTTP headers write_range(clisock, httpheaders, httpheaders + httpheaderslen); // 5. Writing gzip headers: http://www.gzip.org/zlib/rfc-gzip.html write_range(clisock, gzipheader, gzipheader + sizeof(gzipheader)); // 6. Write compressed data // 7. Append crc32 // 8. Append initial size return EXIT_SUCCESS; // Returns listen socket handle addr.sin_family = AF_INET; if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) return sock; Ссылки по теме
|
|