(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

WebService c поддержкой gzip на C

Источник: habrahabr

В посте речь пойдет о моем опыте встраивания XML-RPC интерфейса в утилиту, написанную на C. Интерфейс должен предоставлять доступ к статитстике и результатам работы утилиты. Одно из требований к интерфейсу - поддержка ответов в формате gzip, в целях экономии трафика. Мне очень хотелось обойтись малой кровью и вот что из этого получилось.

Прежде всего тесты

 Начнем с тестов. XML-RPC клиент на python умещается в 4 строчки. Кстати, он как раз понимает ответы в gzip формате.

import xmlrpclib
if __name__ == '__main__':
    proxy = xmlrpclib.ServerProxy("http://localhost:8080/", verbose=True)
    print proxy.sayHello()

 Отлично! Теперь мы знаем какие 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>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define PORT    8080
#define MAXCONN 5

#define BUF_SZ  1024

#define ZLIB_PREFIX_SZ  2
#define ZLIB_SUFFIX_SZ  4

#define GZIP_PREFIX_SZ  10
#define GZIP_SUFFIX_SZ  8

// Returns listen socket handle
int create_srvsock(int port, int maxconn);
// Returns response to be sent back
int get_response(int clisock, char *response);
// Writes given data range to socket
void write_range(int sock, const char *begin, const char *end);
// Write int value to socket
void write_int(int sock, int value);
// Prints error message and exit
void error(const char *msg);

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,};
    char response[BUF_SZ] = {0,};
    char compressed[BUF_SZ] = {0,};

    int srvsock = create_srvsock(PORT, MAXCONN);

    fprintf(stderr, "Server is started on port %d\r\n", PORT);

    while (true) {
        struct sockaddr_in addr = {0,};
        socklen_t addrlen = sizeof(addr);

        // 1. Accepting connection
        int clisock = accept(srvsock, (struct sockaddr *)&addr, &addrlen);

        // 2. Retreiving response
        int responselen = get_response(clisock, response);

        // 3. Compressing response
        long unsigned int compressedlen = BUF_SZ;
        if (compress((unsigned char *)compressed, &compressedlen
            , (const unsigned char *)response, responselen) != Z_OK)
            error("Can not compress");

        // substract zlib prefix and suffix: http://www.ietf.org/rfc/rfc1950.txt
        compressedlen -= ZLIB_PREFIX_SZ + ZLIB_SUFFIX_SZ;

        // 4. Writing HTTP headers
        int contentlen = GZIP_PREFIX_SZ + compressedlen + GZIP_SUFFIX_SZ;
        int httpheaderslen = sprintf(httpheaders,
"HTTP/1.1 200 OK\r\n"\
"Content-Type: text/xml\r\n"\
"Content-Encoding: gzip\r\n"\
"Content-Length: %d\r\n\r\n", contentlen);

        write_range(clisock, httpheaders, httpheaders + httpheaderslen);

        // 5. Writing gzip headers: http://www.gzip.org/zlib/rfc-gzip.html
        const char gzipheader[] = {
            0x1f, 0x8b      // gzip magic number
            , 8             // compress method "defalte"
            , 1             // text data
            , 0, 0, 0, 0    // timestamp is not set
            , 2             // maximum compression flag
            , 255           // unknown OS
        };

        write_range(clisock, gzipheader, gzipheader + sizeof(gzipheader));

        // 6. Write compressed data
        write_range(clisock, compressed + ZLIB_PREFIX_SZ
            , compressed + ZLIB_PREFIX_SZ + compressedlen);

        // 7. Append crc32
        write_int(clisock, (int)crc32(0, (unsigned char *)response, responselen));

        // 8. Append initial size
        write_int(clisock, responselen);
    }

    return EXIT_SUCCESS;
}

// Returns listen socket handle
int create_srvsock(int port, int maxconn) {
    int sock = 0;
    struct sockaddr_in addr = {0,};

    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        error("Can not open socket");
    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
        error("Can not bind socket");
    if (listen(sock, maxconn) < 0)
        error("Can not listen socket");

    return sock;
}
// Returns response to be sent back
int get_response(int clisock, char *response) {
    return sprintf(response, "<?xml version=\"1.0\"?>\r\n"\
"<methodResponse>\r\n"\
"    <params><param><value>Hello there!</value></param></params>\r\n"\
"</methodResponse>");
}
// Writes given data range to socket
void write_range(int sock, const char* begin, const char *end) {
    for (const char *it = begin; it != end;) {
        int written = write(sock, it, end - it);
        if (written < 0)
            error("Can not write to socket");
        it += written;
    }
}
// Write int value to socket
void write_int(int sock, int value) {
    const char *data = (const char *)&value;
    write_range(sock, data, data + sizeof(int));
}
// Prints error message and exit
void error(const char *msg) {
    perror(msg);
    exit(EXIT_FAILURE);
}

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 23.11.2011 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Delphi Professional Named User
Enterprise Connectors (1 Year term)
Stimulsoft Reports.Ultimate Single License Includes one year subscription
Enterprise Connectors (1 Year term)
erwin Data Modeler Workgroup Edition r9.7 - Product plus 1 Year Enterprise Maintenance Commercial
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
eManual - электронные книги и техническая документация
Утиль - лучший бесплатный софт для Windows
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100