為libevent添加websocket支持(上)


在跨平台網絡基礎庫中,libevent與asio近年來使用比較廣泛。asio對boost的依賴太大,個人認為發展前途堪憂,尤其asio對http沒有很好的支持也是缺點之一。

libevent對http有天生支持,含有服務與客戶兩個部分,是做web服務的好特性。

libevent隨對http支持很優秀,但並不支持html5標准的websocket,這有些與時代脫軌。如果你熟悉websocket協議,像自己擴展libevent,很遺憾,libevent的http部分並不支持邏輯層擴展。所以我想,還是通過源碼級擴展比較好。注:git上有代碼級擴展,但不是在http功能上的擴展。

正文:

libevent的http支持核心代碼都在http.c中,包含了幾個相關頭文件,包括http.h、http-internal.h、http_struct.h、http_compat.h。

libevent的主要容器是列表,由一系列宏進行操作。包括http request,callback函數,http connection,輸入頭信息,輸出頭信息均被列表容器管理。回調函數的搜索匹配,evkeyxxx相關的頭信息搜索,均需要在列表中遍歷,會有些許性能損耗。

libevent有兩個方法設置http事件回調函數:evhttp_set_cb,evhttp_set_gencb.我本計划用開關的方式來決定是否開啟websocket的連接升級(Connection: Upgrade)功能,后來覺得與libevent的原始架構有些不一致,最終決定用類似設置回調函數的方法設置哪些路徑接收WebSocket升級:evhttp_set_ws,evhttp_del_ws。

處理頭信息:

evhttp_read_header是接管websocket升級的好地方,我在EVHTTP_REQUEST case的地方添加處理代碼,根據WebSocket標准文檔,先在header中尋找升級Key(Sec-WebSocket-Key),進行Hash(SHA1->BASE64)返回給客戶端即可完成升級。至於hash代碼,在windows下可方便的用加解密相關函數(Crypt開頭)解決,在Linux就要用openssl了。

libevent默認在讀完header后會關閉bufferevent的讀取事件,這會影響之后我們websocket的通訊,為此我寫了一個新的寫緩沖函數,不停止讀取事件:evhttp_write_buffer_nostop_read。只需要復制evhttp_write_buffer函數,刪除設置緩沖cb的代碼即可。

libevent http connection有個state枚舉,用來只是當前讀取狀態,我為此枚舉添加了一個狀態:EVCON_READING_WSDATA,在提升Websocket完成后,將state設置為EVCON_READING_WSDATA,並且為evhttp_read_cb添加一個對應case,處理websocket的數據。

 

代碼

注:代碼按照libevent源碼風格進行編寫,除了大括號后置,基本就是本人的風格了。

先做到協議提升與協議解析,下次再討論發送的問題以及數據類型的問題。

websocket客戶端key處理代碼:

Linux需要這些頭文件<openssl/sha.h>,<openssl/bio.h>,<openssl/evp.h>,<string.h>,<openssl/buffer.h>,前提你應該有openssl的devel版被安裝。

Linux總歸會麻煩一些,忍咯!Windows需要引用crypt32.lib,Linu需要引用libcrypto.lib(gcc:lcrypto)。

Windows已測試,Linux只進行了片段代碼測試。

 1 const char*
 2 ws_hash(const char* client_key) {
 3     static char result[50];
 4     if (strlen(client_key) > 37)
 5         return NULL;
 6 
 7     const char* uuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
 8     char src[100];
 9     strcpy(src, client_key);
10     strcat(src, uuid);
11     size_t src_len = strlen(src);
12 
13 #ifdef _WIN64 or _WIN32
14     HCRYPTPROV hCryptProv;
15     if (CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, 0)){
16         HCRYPTHASH hHash;
17         if (CryptCreateHash(hCryptProv, CALG_SHA1, 0, 0, &hHash)){
18             if (CryptHashData(hHash, (BYTE*)src, src_len, 0)){
19                 BYTE hash_result[50];
20                 DWORD out_len = sizeof(hash_result);
21                 if (CryptGetHashParam(hHash, HP_HASHVAL, hash_result, &out_len, 0)){
22                     DWORD crypt_out_len = 100;
23                     CryptBinaryToStringA(hash_result, out_len, CRYPT_STRING_BASE64, result, &crypt_out_len);
24                     result[crypt_out_len - 2] = 0;
25                     return result;
26                 }
27             }
28         }
29     }
30 #else
31     unsigned char* value = SHA1((unsigned char*)src, src_len, out);
32     BIO *bm = NULL, *bio = NULL;
33     bio = BIO_new(BIO_f_base64());
34     if (bio) {
35         bm = BIO_new(BIO_s_mem());
36         if (bm) {
37             BIO_push(bio, bm);
38             BIO_write(bio, value, strlen((const char*)value));
39             BIO_flush(bio);
40             BUF_MEM *buf;
41             BIO_get_mem_ptr(bio, &buf);
42             strcpy(result, buf->data);
43             BIO_free_all(bio);
44         }
45     }
46 #endif
47     return NULL;
48 }

 

websocket提升代碼:

主要處理頭信息並寫回客戶端與狀態。

 1     case EVHTTP_REQUEST: {
 2         /* handle the websocket upgrade key */
 3         const char* seckey = evhttp_find_header(req->input_headers, "Sec-WebSocket-Key");
 4         if (seckey) {
 5             struct evhttp_wsup* wsup;
 6             char* translated;
 7             /* Test for different URLs */
 8             const char* path = evhttp_uri_get_path(req->uri_elems);
 9             size_t offset = strlen(path);
10             if ((translated = mm_malloc(offset + 1)) == NULL)
11                 return;
12             evhttp_decode_uri_internal(path, offset, translated,
13                 0 /* decode_plus */);
14             TAILQ_FOREACH(wsup, &evcon->http_server->websocket_upgrades, next) {
15                 if (!stricmp(wsup->what, translated)) {
16                     evhttp_add_header(req->output_headers, "Connection", "Upgrade");
17                     evhttp_add_header(req->output_headers, "Upgrade", "WebSocket");
18                     evhttp_add_header(req->output_headers, "Sec-WebSocket-Accept", ws_hash(seckey));
19                     req->websocket = 1;
20                     evhttp_response_code(req, 101, "SwitchProtocol");
21                     evhttp_make_header(req->evcon, req);
22                     evhttp_write_buffer_nostop_read(req->evcon, NULL, NULL);
23                     evcon->state = EVCON_READING_WSDATA;
24                     bufferevent_enable(evcon->bufev, EV_READ);
25                     break;
26                 }
27             }
28             mm_free(translated);
29             return;
30         }

 

處理websocket協議的代碼:

 1 int
 2 process_buffer(unsigned char* buff, size_t data_len)
 3 {
 4     if (data_len < 2)
 5         return 0;
 6     switch (buff[0] & 0xF){
 7     case 8:
 8         return 1;
 9     case 9:
10     case 10:{
11         auto len = buff[1] & 0x7F;
12         auto mask = (buff[1] & 0x80) > 0;
13         if (len > 0)
14             return -1;
15 
16         if (mask)
17             return 6;
18         else
19             return 2;
20         break;
21     }
22     case 1:{
23         auto len = buff[1] & 0x7F;
24         auto mask = (buff[1] & 0x80) > 0;
25         int head_len = 0;
26         if (len == 127)
27             head_len = mask ? 14 : 10;
28         else if (len == 126)
29             head_len = mask ? 8 : 4;
30         else
31             head_len = mask ? 6 : 2;
32         if (data_len < head_len)
33             return 0;
34 
35         int tail_len = 0;
36         if (len == 127)
37             tail_len = (int)ntohll((unsigned long long)(buff + 2));
38         else if (len == 126)
39             tail_len = ntohs((u_short)(buff + 2));
40         else
41             tail_len = len;
42 
43         if (data_len < head_len + tail_len)
44             return 0;
45 
46         if (mask)
47             for (int i = head_len, j = 0; j < tail_len; i++, j++)
48                 buff[i] = buff[i] ^ buff[head_len - 4 + j % 4];
49 
50         char* utf8_text = buff + head_len;
51 
52         return head_len + tail_len;
53     }
54     }
55     return 0;
56 }
57 
58 static void
59 evhttp_read_wsdata(struct evhttp_connection *evcon, struct evhttp_request *req)
60 {
61     struct evbuffer *buf = bufferevent_get_input(evcon->bufev);
62 
63     size_t buflen = evbuffer_get_length(buf);
64     if (buflen == 0)
65         return;
66 
67     size_t drain_len = 0;
68     unsigned char* data = evbuffer_pullup(buf, buflen);
69     while (buflen>0) {
70         int result = process_buffer(data, buflen);
71         if (result < 0) {
72             evhttp_connection_free(evcon);
73             return;
74         }
75         else if (result > 0) {
76             if (result > buflen) {
77                 evhttp_connection_free(evcon);
78                 return;
79             }
80             drain_len += result;
81             data += result;
82             buflen -= result;
83         }
84         else
85             break;
86     }
87     if(drain_len>0)evbuffer_drain(buf, drain_len);
88 
89     /* Read more! */
90     bufferevent_enable(evcon->bufev, EV_READ);
91 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM