CでgzipをサポートするWebService

この投稿では、Cで書かれたユーティリティにXML-RPCインターフェイスを埋め込むという私の経験に焦点を当てます。インターフェイスは、ユーティリティの統計と結果へのアクセスを提供する必要があります。 インターフェイスの要件の1つは、トラフィックを節約するためのgzip応答のサポートです。 私は本当に小さな血でうまくやりたかったのですが、これがその結果です。



すべてのテストの最初



テストから始めましょう。 Python XML-RPCクライアントは4行に収まります。 ところで、彼はgzip形式の答えを理解しているだけです。



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





いいね! これで、クライアントが受信するHTTPヘッダーがわかりました。 また、形式が正しくない場合、詳細なコールスタックで例外が発生します。 エラーが発生した場合、これはすべて、発生の原因を明らかにするのに役立ちます。



Zlib



wikiによると、gzip形式はzlibライブラリに実装されているdeflate圧縮アルゴリズムに基づいています。 このライブラリには優れた圧縮方法があります。



 int compress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen);
      
      





発見に喜び、私はすぐにこの方法を試すことにし、簡単なアプリケーションフレームワークをスケッチしましたが、これでは十分ではありませんでした。 クライアントはサーバーの応答の内容を理解することを拒否し、例外で抜け落ちました。 私はgzip形式をさらに詳しく調べる必要がありました。



Gzip



ここではすべてが非常に簡単です







圧縮データは、10バイトの特別な形式のヘッダーと、ソースデータとその長さのチェックサムを含む8バイトのサフィックスによってフレーム化されます。



ヘッダーはマジック定数ID1 = 31(0x1f、\ 037)、 ID2 = 139(0x8b、\ 213)で始まり、gzip形式のデータの始まりについて話します。 次は圧縮方法( CM )です。deflateCM = 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バイトのサフィックスを追加することがわかりました( more )。 それらを取り除き、gzipヘッダーとサフィックスを追加します。



そして、lo! クライアントはついに私たちを理解しました!



注:Qtライブラリには、zlibライブラリによって圧縮されたデータを返すqCompress()メソッドがありますが、圧縮データの長さを4バイトのプレフィックスで返します。



まとめ



gzip形式でデータを生成するには、 compress関数を使用してソースデータを圧縮します。結果の配列では、元のデータのチェックサムと長さを配置する最後の4バイトではなく、最初の2バイトを10バイトのgzipヘッダーに置き換えます。



gzip形式でデータを返す動作するXML-RPCサーバーの例を以下に示します。



 #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); }
      
      






All Articles