Browse Source

Upgrade: websocket

ithewei 4 years ago
parent
commit
1179b248f4

+ 17 - 17
http/Http2Parser.cpp

@@ -52,11 +52,11 @@ Http2Parser::Http2Parser(http_session_type type) {
     }
     if (type == HTTP_CLIENT) {
         nghttp2_session_client_new(&session, cbs, this);
-        state = HSS_SEND_MAGIC;
+        state = H2_SEND_MAGIC;
     }
     else if (type == HTTP_SERVER) {
         nghttp2_session_server_new(&session, cbs, this);
-        state = HSS_WANT_RECV;
+        state = H2_WANT_RECV;
     }
     //nghttp2_session_set_user_data(session, this);
     submited = NULL;
@@ -68,10 +68,10 @@ Http2Parser::Http2Parser(http_session_type type) {
         {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}
     };
     nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, settings, ARRAY_SIZE(settings));
-    state = HSS_SEND_SETTINGS;
+    state = H2_SEND_SETTINGS;
 
     //nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL);
-    //state = HSS_SEND_PING;
+    //state = H2_SEND_PING;
 }
 
 Http2Parser::~Http2Parser() {
@@ -89,11 +89,11 @@ int Http2Parser::GetSendData(char** data, size_t* len) {
 
     if (submited == NULL) return 0;
     // HTTP2_DATA
-    if (state == HSS_SEND_HEADERS) {
+    if (state == H2_SEND_HEADERS) {
         void* content = submited->Content();
         int content_length = submited->ContentLength();
         // HTTP2 DATA framehd
-        state = HSS_SEND_DATA_FRAME_HD;
+        state = H2_SEND_DATA_FRAME_HD;
         http2_frame_hd  framehd;
         framehd.length = content_length;
         framehd.type = HTTP2_DATA;
@@ -137,7 +137,7 @@ int Http2Parser::GetSendData(char** data, size_t* len) {
         }
         http2_frame_hd_pack(&framehd, frame_hdbuf);
     }
-    else if (state == HSS_SEND_DATA_FRAME_HD) {
+    else if (state == H2_SEND_DATA_FRAME_HD) {
         // HTTP2 DATA
         void* content = submited->Content();
         int content_length = submited->ContentLength();
@@ -147,14 +147,14 @@ int Http2Parser::GetSendData(char** data, size_t* len) {
         }
         else {
             printd("HTTP2 SEND_DATA... content_length=%d\n", content_length);
-            state = HSS_SEND_DATA;
+            state = H2_SEND_DATA;
             *data = (char*)content;
             *len = content_length;
         }
     }
-    else if (state == HSS_SEND_DATA) {
+    else if (state == H2_SEND_DATA) {
 send_done:
-        state = HSS_SEND_DONE;
+        state = H2_SEND_DONE;
         if (submited->ContentType() == APPLICATION_GRPC) {
             if (type == HTTP_SERVER && stream_closed) {
                 // grpc HEADERS grpc-status
@@ -173,7 +173,7 @@ send_done:
 
 int Http2Parser::FeedRecvData(const char* data, size_t len) {
     printd("nghttp2_session_mem_recv %d\n", len);
-    state = HSS_WANT_RECV;
+    state = H2_WANT_RECV;
     size_t ret = nghttp2_session_mem_recv(session, (const uint8_t*)data, len);
     if (ret != len) {
         error = ret;
@@ -232,7 +232,7 @@ int Http2Parser::SubmitRequest(HttpRequest* req) {
     // nghttp2_data_provider data_prd;
     // data_prd.read_callback = data_source_read_callback;
     //stream_id = nghttp2_submit_request(session, NULL, &nvs[0], nvs.size(), &data_prd, NULL);
-    state = HSS_SEND_HEADERS;
+    state = H2_SEND_HEADERS;
     return 0;
 }
 
@@ -284,7 +284,7 @@ int Http2Parser::SubmitResponse(HttpResponse* res) {
     // avoid DATA_SOURCE_COPY, we do not use nghttp2_submit_data
     // data_prd.read_callback = data_source_read_callback;
     //nghttp2_submit_response(session, stream_id, &nvs[0], nvs.size(), &data_prd);
-    state = HSS_SEND_HEADERS;
+    state = H2_SEND_HEADERS;
     return 0;
 }
 
@@ -380,16 +380,16 @@ int on_frame_recv_callback(nghttp2_session *session,
     Http2Parser* hp = (Http2Parser*)userdata;
     switch (frame->hd.type) {
     case NGHTTP2_DATA:
-        hp->state = HSS_RECV_DATA;
+        hp->state = H2_RECV_DATA;
         break;
     case NGHTTP2_HEADERS:
-        hp->state = HSS_RECV_HEADERS;
+        hp->state = H2_RECV_HEADERS;
         break;
     case NGHTTP2_SETTINGS:
-        hp->state = HSS_RECV_SETTINGS;
+        hp->state = H2_RECV_SETTINGS;
         break;
     case NGHTTP2_PING:
-        hp->state = HSS_RECV_PING;
+        hp->state = H2_RECV_PING;
         break;
     case NGHTTP2_RST_STREAM:
     case NGHTTP2_WINDOW_UPDATE:

+ 17 - 15
http/Http2Parser.h

@@ -9,19 +9,21 @@
 #include "nghttp2/nghttp2.h"
 
 enum http2_session_state {
-    HSS_SEND_MAGIC,
-    HSS_SEND_SETTINGS,
-    HSS_SEND_PING,
-    HSS_SEND_HEADERS,
-    HSS_SEND_DATA_FRAME_HD,
-    HSS_SEND_DATA,
-    HSS_SEND_DONE,
-
-    HSS_WANT_RECV,
-    HSS_RECV_SETTINGS,
-    HSS_RECV_PING,
-    HSS_RECV_HEADERS,
-    HSS_RECV_DATA,
+    H2_SEND_MAGIC,
+    H2_SEND_SETTINGS,
+    H2_SEND_PING,
+    H2_SEND_HEADERS,
+    H2_SEND_DATA_FRAME_HD,
+    H2_SEND_DATA,
+    H2_SEND_DONE,
+
+    H2_WANT_SEND,
+    H2_WANT_RECV,
+
+    H2_RECV_SETTINGS,
+    H2_RECV_PING,
+    H2_RECV_HEADERS,
+    H2_RECV_DATA,
 };
 
 class Http2Parser : public HttpParser {
@@ -50,11 +52,11 @@ public:
     }
 
     virtual bool WantRecv() {
-        return state == HSS_WANT_RECV;
+        return state == H2_WANT_RECV;
     }
 
     virtual bool WantSend() {
-        return state != HSS_WANT_RECV;
+        return state <= H2_WANT_SEND;
     }
 
     virtual bool IsComplete() {

+ 3 - 3
http/HttpParser.h

@@ -20,16 +20,16 @@ public:
     virtual int GetState() = 0;
 
     // Http1Parser: GetState() != HP_MESSAGE_COMPLETE
-    // Http2Parser: GetState() == HSS_WANT_RECV
+    // Http2Parser: GetState() == H2_WANT_RECV
     virtual bool WantRecv() = 0;
 
     // Http1Parser: GetState() == HP_MESSAGE_COMPLETE
-    // Http2Parser: GetState() == HSS_WANT_SEND
+    // Http2Parser: GetState() == H2_WANT_SEND
     virtual bool WantSend() = 0;
 
     // IsComplete: Is recved HttpRequest or HttpResponse complete?
     // Http1Parser: GetState() == HP_MESSAGE_COMPLETE
-    // Http2Parser: (state == HSS_RECV_HEADERS || state == HSS_RECV_DATA) && stream_closed
+    // Http2Parser: (state == H2_RECV_HEADERS || state == H2_RECV_DATA) && stream_closed
     virtual bool IsComplete() = 0;
 
     // client

+ 1 - 1
http/server/HttpHandler.cpp

@@ -3,7 +3,7 @@
 #include "hbase.h"
 #include "http_page.h"
 
-int HttpHandler::HandleRequest() {
+int HttpHandler::HandleHttpRequest() {
     // preprocessor -> api -> web -> postprocessor
 
     int ret = 0;

+ 12 - 1
http/server/HttpHandler.h

@@ -7,6 +7,13 @@
 
 class HttpHandler {
 public:
+    enum ProtoType {
+        UNKNOWN,
+        HTTP_V1,
+        HTTP_V2,
+        WEBSOCKET,
+    } proto;
+
     // peeraddr
     char                    ip[64];
     int                     port;
@@ -20,6 +27,7 @@ public:
     HttpParserPtr           parser;
 
     HttpHandler() {
+        proto = UNKNOWN;
         service = NULL;
         files = NULL;
         fc = NULL;
@@ -30,7 +38,10 @@ public:
 
     // @workflow: preprocessor -> api -> web -> postprocessor
     // @result: HttpRequest -> HttpResponse/file_cache_t
-    int HandleRequest();
+    int HandleHttpRequest();
+
+    // TODO
+    // int HandleWebsocketMessage(void* buf, int len);
 
     void Reset() {
         req.Reset();

+ 76 - 66
http/server/HttpServer.cpp

@@ -4,11 +4,11 @@
 #include "hmain.h"
 #include "hloop.h"
 
-#include "FileCache.h"
-#include "HttpHandler.h"
-
+#include "httpdef.h"
 #include "http2def.h"
-#include "Http2Parser.h"
+#include "wsdef.h"
+
+#include "HttpHandler.h"
 
 #define MIN_HTTP_REQUEST        "GET / HTTP/1.1\r\n\r\n"
 #define MIN_HTTP_REQUEST_LEN    14 // exclude CRLF
@@ -27,9 +27,16 @@ static void on_recv(hio_t* io, void* _buf, int readbytes) {
     // printf("on_recv fd=%d readbytes=%d\n", hio_fd(io), readbytes);
     const char* buf = (const char*)_buf;
     HttpHandler* handler = (HttpHandler*)hevent_userdata(io);
+
+    if (handler->proto == HttpHandler::WEBSOCKET) {
+        // TODO: HandleWebsocketMessage
+        return;
+    }
+
     // HTTP1 / HTTP2 -> HttpParser -> InitRequest
     // recv -> FeedRecvData -> !WantRecv -> HttpRequest ->
     // HandleRequest -> HttpResponse -> SubmitResponse -> while (GetSendData) -> send
+
     if (handler->parser == NULL) {
         // check request-line
         if (readbytes < MIN_HTTP_REQUEST_LEN) {
@@ -45,8 +52,10 @@ static void on_recv(hio_t* io, void* _buf, int readbytes) {
             }
         }
         http_version version = HTTP_V1;
+        handler->proto = HttpHandler::HTTP_V1;
         if (strncmp((char*)buf, HTTP2_MAGIC, MIN(readbytes, HTTP2_MAGIC_LEN)) == 0) {
             version = HTTP_V2;
+            handler->proto = HttpHandler::HTTP_V2;
             handler->req.http_major = 2;
             handler->req.http_minor = 0;
         }
@@ -74,88 +83,91 @@ static void on_recv(hio_t* io, void* _buf, int readbytes) {
         return;
     }
 
-#ifdef WITH_NGHTTP2
-    if (parser->version == HTTP_V2) {
-        // HTTP2 extra processing steps
-        Http2Parser* h2p = (Http2Parser*)parser;
-        if (h2p->state == HSS_RECV_PING) {
-            char* data = NULL;
-            size_t len = 0;
-            while (parser->GetSendData(&data, &len)) {
-                hio_write(io, data, len);
-            }
-            return;
-        }
-        else if ((h2p->state == HSS_RECV_HEADERS && req->method != HTTP_POST) || h2p->state == HSS_RECV_DATA) {
-            goto handle_request;
-        }
-        else {
-            // ignore other http2 frame
-            return;
-        }
-    }
-
-    // Upgrade: h2
-    {
-        auto iter_upgrade = req->headers.find("upgrade");
-        if (iter_upgrade != req->headers.end()) {
-            hlogi("[%s:%d] Upgrade: %s", handler->ip, handler->port, iter_upgrade->second.c_str());
-            // h2/h2c
-            if (strnicmp(iter_upgrade->second.c_str(), "h2", 2) == 0) {
-                hio_write(io, HTTP2_UPGRADE_RESPONSE, strlen(HTTP2_UPGRADE_RESPONSE));
-                parser = HttpParser::New(HTTP_SERVER, HTTP_V2);
-                if (parser == NULL) {
-                    hloge("[%s:%d] unsupported HTTP2", handler->ip, handler->port);
-                    hio_close(io);
-                    return;
-                }
-                handler->parser.reset(parser);
-                HttpRequest http1_req = *req;
-                parser->InitRequest(req);
-                *req = http1_req;
-                req->http_major = 2;
-                req->http_minor = 0;
-                // HTTP2_Settings: ignore
-                // parser->FeedRecvData(HTTP2_Settings, );
-            }
-            else {
-                hio_close(io);
-                return;
-            }
-        }
-    }
-#endif
-
-handle_request:
-    handler->HandleRequest();
-    // prepare headers body
     // Server:
     static char s_Server[64] = {'\0'};
     if (s_Server[0] == '\0') {
         snprintf(s_Server, sizeof(s_Server), "httpd/%s", hv_compile_version());
     }
     res->headers["Server"] = s_Server;
+
     // Connection:
     bool keepalive = true;
     auto iter_keepalive = req->headers.find("connection");
     if (iter_keepalive != req->headers.end()) {
-        if (stricmp(iter_keepalive->second.c_str(), "keep-alive") == 0) {
+        const char* keepalive_value = iter_keepalive->second.c_str();
+        if (stricmp(keepalive_value, "keep-alive") == 0) {
             keepalive = true;
         }
-        else if (stricmp(iter_keepalive->second.c_str(), "close") == 0) {
+        else if (stricmp(keepalive_value, "close") == 0) {
             keepalive = false;
         }
+        else if (stricmp(keepalive_value, "upgrade") == 0) {
+            keepalive = true;
+        }
     }
     else if (req->http_major == 1 && req->http_minor == 0) {
         keepalive = false;
     }
     if (keepalive) {
         res->headers["Connection"] = "keep-alive";
-    }
-    else {
+    } else {
         res->headers["Connection"] = "close";
     }
 
+    // Upgrade:
+    bool upgrade = false;
+    auto iter_upgrade = req->headers.find("upgrade");
+    if (iter_upgrade != req->headers.end()) {
+        upgrade = true;
+        const char* upgrade_proto = iter_upgrade->second.c_str();
+        hlogi("[%s:%d] Upgrade: %s", handler->ip, handler->port, upgrade_proto);
+        // websocket
+        if (stricmp(upgrade_proto, "websocket") == 0) {
+            /*
+            HTTP/1.1 101 Switching Protocols
+            Connection: Upgrade
+            Upgrade: websocket
+            Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
+            */
+            res->status_code = HTTP_STATUS_SWITCHING_PROTOCOLS;
+            res->headers["Connection"] = "Upgrade";
+            res->headers["Upgrade"] = "websocket";
+            auto iter_key = req->headers.find(SEC_WEBSOCKET_KEY);
+            if (iter_key != req->headers.end()) {
+                char ws_accept[32] = {0};
+                ws_encode_key(iter_key->second.c_str(), ws_accept);
+                res->headers[SEC_WEBSOCKET_ACCEPT] = ws_accept;
+            }
+            handler->proto = HttpHandler::WEBSOCKET;
+        }
+        // h2/h2c
+        else if (strnicmp(upgrade_proto, "h2", 2) == 0) {
+            /*
+            HTTP/1.1 101 Switching Protocols
+            Connection: Upgrade
+            Upgrade: h2c
+            */
+            hio_write(io, HTTP2_UPGRADE_RESPONSE, strlen(HTTP2_UPGRADE_RESPONSE));
+            parser = HttpParser::New(HTTP_SERVER, HTTP_V2);
+            if (parser == NULL) {
+                hloge("[%s:%d] unsupported HTTP2", handler->ip, handler->port);
+                hio_close(io);
+                return;
+            }
+            handler->parser.reset(parser);
+            parser->InitRequest(req);
+        }
+        else {
+            hio_close(io);
+            return;
+        }
+    }
+
+    if (parser->IsComplete() && !upgrade) {
+        handler->HandleHttpRequest();
+        parser->SubmitResponse(res);
+    }
+
     if (req->http_major == 1) {
         std::string header = res->Dump(true, false);
         hbuf_t sendbuf;
@@ -184,7 +196,6 @@ handle_request:
         }
     }
     else if (req->http_major == 2) {
-        parser->SubmitResponse(res);
         char* data = NULL;
         size_t len = 0;
         while (parser->GetSendData(&data, &len)) {
@@ -202,8 +213,7 @@ handle_request:
     if (keepalive) {
         handler->Reset();
         parser->InitRequest(req);
-    }
-    else {
+    } else {
         hio_close(io);
     }
 }

+ 249 - 0
http/websocket_parser.c

@@ -0,0 +1,249 @@
+#include "websocket_parser.h"
+#include <assert.h>
+#include <string.h>
+
+#ifdef assert
+# define assertFalse(msg) assert(0 && msg)
+#else
+# define assertFalse(msg)
+#endif
+
+#define SET_STATE(V) parser->state = V
+#define HAS_DATA() (p < end )
+#define CC (*p)
+#define GET_NPARSED() ( (p == end) ? len : (p - data) )
+
+#define NOTIFY_CB(FOR)                                                 \
+do {                                                                   \
+  if (settings->on_##FOR) {                                            \
+    if (settings->on_##FOR(parser) != 0) {                             \
+      return GET_NPARSED();                                            \
+    }                                                                  \
+  }                                                                    \
+} while (0)
+
+#define EMIT_DATA_CB(FOR, ptr, len)                                    \
+do {                                                                   \
+  if (settings->on_##FOR) {                                            \
+    if (settings->on_##FOR(parser, ptr, len) != 0) {                   \
+      return GET_NPARSED();                                            \
+    }                                                                  \
+  }                                                                    \
+} while (0)
+
+enum state {
+    s_start,
+    s_head,
+    s_length,
+    s_mask,
+    s_body,
+};
+
+void websocket_parser_init(websocket_parser * parser) {
+    void *data = parser->data; /* preserve application data */
+    memset(parser, 0, sizeof(*parser));
+    parser->data = data;
+    parser->state = s_start;
+}
+
+void websocket_parser_settings_init(websocket_parser_settings *settings) {
+    memset(settings, 0, sizeof(*settings));
+}
+
+size_t websocket_parser_execute(websocket_parser *parser, const websocket_parser_settings *settings, const char *data, size_t len) {
+    const char * p;
+    const char * end = data + len;
+    size_t frame_offset = 0;
+
+    for(p = data; p != end; p++) {
+        switch(parser->state) {
+            case s_start:
+                parser->offset      = 0;
+                parser->length      = 0;
+                parser->mask_offset = 0;
+                parser->flags       = (websocket_flags) (CC & WS_OP_MASK);
+                if(CC & (1<<7)) {
+                    parser->flags |= WS_FIN;
+                }
+                SET_STATE(s_head);
+
+                frame_offset++;
+                break;
+            case s_head:
+                parser->length  = (size_t)CC & 0x7F;
+                if(CC & 0x80) {
+                    parser->flags |= WS_HAS_MASK;
+                }
+                if(parser->length >= 126) {
+                    if(parser->length == 127) {
+                        parser->require = 8;
+                    } else {
+                        parser->require = 2;
+                    }
+                    parser->length = 0;
+                    SET_STATE(s_length);
+                } else if (parser->flags & WS_HAS_MASK) {
+                    SET_STATE(s_mask);
+                    parser->require = 4;
+                } else if (parser->length) {
+                    SET_STATE(s_body);
+                    parser->require = parser->length;
+                    NOTIFY_CB(frame_header);
+                } else {
+                    SET_STATE(s_start);
+                    NOTIFY_CB(frame_header);
+                    NOTIFY_CB(frame_end);
+                }
+
+                frame_offset++;
+                break;
+            case s_length:
+                while(HAS_DATA() && parser->require) {
+                    parser->length <<= 8;
+                    parser->length |= (unsigned char)CC;
+                    parser->require--;
+                    frame_offset++;
+                    p++;
+                }
+                p--;
+                if(!parser->require) {
+                    if (parser->flags & WS_HAS_MASK) {
+                        SET_STATE(s_mask);
+                        parser->require = 4;
+                    } else if (parser->length) {
+                        SET_STATE(s_body);
+                        parser->require = parser->length;
+                        NOTIFY_CB(frame_header);
+                    } else {
+                        SET_STATE(s_start);
+                        NOTIFY_CB(frame_header);
+                        NOTIFY_CB(frame_end);
+                    }
+                }
+                break;
+            case s_mask:
+                while(HAS_DATA() && parser->require) {
+                    parser->mask[4 - parser->require--] = CC;
+                    frame_offset++;
+                    p++;
+                }
+                p--;
+                if(!parser->require) {
+                    if(parser->length) {
+                        SET_STATE(s_body);
+                        parser->require = parser->length;
+                        NOTIFY_CB(frame_header);
+                    } else {
+                        SET_STATE(s_start);
+                        NOTIFY_CB(frame_header);
+                        NOTIFY_CB(frame_end);
+                    }
+                }
+                break;
+            case s_body:
+                if(parser->require) {
+                    if(p + parser->require <= end) {
+                        EMIT_DATA_CB(frame_body, p, parser->require);
+                        p += parser->require;
+                        parser->require = 0;
+                        frame_offset = p - data;
+                    } else {
+                        EMIT_DATA_CB(frame_body, p, end - p);
+                        parser->require -= end - p;
+                        p = end;
+                        parser->offset += p - data - frame_offset;
+                        frame_offset = 0;
+                    }
+
+                    p--;
+                }
+                if(!parser->require) {
+                    NOTIFY_CB(frame_end);
+                    SET_STATE(s_start);
+                }
+                break;
+            default:
+                assertFalse("Unreachable case");
+        }
+    }
+
+    return GET_NPARSED();
+}
+
+void websocket_parser_decode(char * dst, const char * src, size_t len, websocket_parser * parser) {
+    size_t i = 0;
+    for(; i < len; i++) {
+        dst[i] = src[i] ^ parser->mask[(i + parser->mask_offset) % 4];
+    }
+
+    parser->mask_offset = (uint8_t) ((i + parser->mask_offset) % 4);
+}
+
+uint8_t websocket_decode(char * dst, const char * src, size_t len, const char mask[4], uint8_t mask_offset) {
+    size_t i = 0;
+    for(; i < len; i++) {
+        dst[i] = src[i] ^ mask[(i + mask_offset) % 4];
+    }
+
+    return (uint8_t) ((i + mask_offset) % 4);
+}
+
+size_t websocket_calc_frame_size(websocket_flags flags, size_t data_len) {
+    size_t size = data_len + 2; // body + 2 bytes of head
+    if(data_len >= 126) {
+        if(data_len > 0xFFFF) {
+            size += 8;
+        } else {
+            size += 2;
+        }
+    }
+    if(flags & WS_HAS_MASK) {
+        size += 4;
+    }
+
+    return size;
+}
+
+size_t websocket_build_frame(char * frame, websocket_flags flags, const char mask[4], const char * data, size_t data_len) {
+    size_t body_offset = 0;
+    frame[0] = 0;
+    frame[1] = 0;
+    if(flags & WS_FIN) {
+        frame[0] = (char) (1 << 7);
+    }
+    frame[0] |= flags & WS_OP_MASK;
+    if(flags & WS_HAS_MASK) {
+        frame[1] = (char) (1 << 7);
+    }
+    if(data_len < 126) {
+        frame[1] |= data_len;
+        body_offset = 2;
+    } else if(data_len <= 0xFFFF) {
+        frame[1] |= 126;
+        frame[2] = (char) (data_len >> 8);
+        frame[3] = (char) (data_len & 0xFF);
+        body_offset = 4;
+    } else {
+        frame[1] |= 127;
+        frame[2] = (char) ((data_len >> 56) & 0xFF);
+        frame[3] = (char) ((data_len >> 48) & 0xFF);
+        frame[4] = (char) ((data_len >> 40) & 0xFF);
+        frame[5] = (char) ((data_len >> 32) & 0xFF);
+        frame[6] = (char) ((data_len >> 24) & 0xFF);
+        frame[7] = (char) ((data_len >> 16) & 0xFF);
+        frame[8] = (char) ((data_len >>  8) & 0xFF);
+        frame[9] = (char) ((data_len)       & 0xFF);
+        body_offset = 10;
+    }
+    if(flags & WS_HAS_MASK) {
+        if(mask != NULL) {
+            memcpy(&frame[body_offset], mask, 4);
+        }
+        websocket_decode(&frame[body_offset + 4], data, data_len, &frame[body_offset], 0);
+        body_offset += 4;
+    } else {
+        memcpy(&frame[body_offset], data, data_len);
+    }
+
+    return body_offset + data_len;
+}

+ 99 - 0
http/websocket_parser.h

@@ -0,0 +1,99 @@
+#ifndef WEBSOCKET_PARSER_H
+#define WEBSOCKET_PARSER_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#include <sys/types.h>
+#if defined(_WIN32) && !defined(__MINGW32__) && \
+  (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
+#include <BaseTsd.h>
+#include <stddef.h>
+typedef __int8 int8_t;
+typedef unsigned __int8 uint8_t;
+typedef __int16 int16_t;
+typedef unsigned __int16 uint16_t;
+typedef __int32 int32_t;
+typedef unsigned __int32 uint32_t;
+typedef __int64 int64_t;
+typedef unsigned __int64 uint64_t;
+#else
+#include <stdint.h>
+#endif
+
+#define WEBSOCKET_UUID   "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+typedef struct websocket_parser websocket_parser;
+typedef struct websocket_parser_settings websocket_parser_settings;
+
+typedef enum websocket_flags {
+    // opcodes
+    WS_OP_CONTINUE = 0x0,
+    WS_OP_TEXT     = 0x1,
+    WS_OP_BINARY   = 0x2,
+    WS_OP_CLOSE    = 0x8,
+    WS_OP_PING     = 0x9,
+    WS_OP_PONG     = 0xA,
+
+    // marks
+    WS_FINAL_FRAME = 0x10,
+    WS_HAS_MASK    = 0x20,
+} websocket_flags;
+
+#define WS_OP_MASK 0xF
+#define WS_FIN     WS_FINAL_FRAME
+
+typedef int (*websocket_data_cb) (websocket_parser*, const char * at, size_t length);
+typedef int (*websocket_cb) (websocket_parser*);
+
+struct websocket_parser {
+    uint32_t        state;
+    websocket_flags flags;
+
+    char            mask[4];
+    uint8_t         mask_offset;
+
+    size_t   length;
+    size_t   require;
+    size_t   offset;
+
+    void * data;
+};
+
+struct websocket_parser_settings {
+    websocket_cb      on_frame_header;
+    websocket_data_cb on_frame_body;
+    websocket_cb      on_frame_end;
+};
+
+void websocket_parser_init(websocket_parser *parser);
+void websocket_parser_settings_init(websocket_parser_settings *settings);
+size_t websocket_parser_execute(
+    websocket_parser * parser,
+    const websocket_parser_settings *settings,
+    const char * data,
+    size_t len
+);
+
+// Apply XOR mask (see https://tools.ietf.org/html/rfc6455#section-5.3) and store mask's offset
+void websocket_parser_decode(char * dst, const char * src, size_t len, websocket_parser * parser);
+
+// Apply XOR mask (see https://tools.ietf.org/html/rfc6455#section-5.3) and return mask's offset
+uint8_t websocket_decode(char * dst, const char * src, size_t len, const char mask[4], uint8_t mask_offset);
+#define websocket_encode(dst, src, len, mask, mask_offset) websocket_decode(dst, src, len, mask, mask_offset)
+
+// Calculate frame size using flags and data length
+size_t websocket_calc_frame_size(websocket_flags flags, size_t data_len);
+
+// Create string representation of frame
+size_t websocket_build_frame(char * frame, websocket_flags flags, const char mask[4], const char * data, size_t data_len);
+
+#define websocket_parser_get_opcode(p) (p->flags & WS_OP_MASK)
+#define websocket_parser_has_mask(p) (p->flags & WS_HAS_MASK)
+#define websocket_parser_has_final(p) (p->flags & WS_FIN)
+
+#ifdef __cplusplus
+}
+#endif
+#endif //WEBSOCKET_PARSER_H

+ 46 - 0
http/wsdef.c

@@ -0,0 +1,46 @@
+#include "wsdef.h"
+
+#include <string.h>
+
+#include "sha1.h"
+#include "base64.h"
+
+#include "websocket_parser.h"
+
+// base64_encode( SHA1(key + magic) )
+void ws_encode_key(const char* key, char accept[]) {
+    char magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+    unsigned char digest[20] = {0};
+    SHA1_CTX ctx;
+    SHA1Init(&ctx);
+    SHA1Update(&ctx, (unsigned char*)key, strlen(key));
+    SHA1Update(&ctx, (unsigned char*)magic, strlen(magic));
+    SHA1Final(digest, &ctx);
+    base64_encode(digest, 20, accept);
+}
+
+// fix-header[2] + var-length[2/8] + mask[4] + data[data_len]
+int ws_calc_frame_size(int data_len, bool has_mask) {
+    int size = data_len + 2;
+    if (data_len >=126) {
+        if (data_len > 0xFFFF) {
+            size += 8;
+        } else {
+            size += 2;
+        }
+    }
+    if (has_mask) size += 4;
+    return size;
+}
+
+int ws_build_frame(
+    char* out,
+    const char* data, int data_len,
+    const char mask[4], bool has_mask,
+    enum ws_opcode opcode,
+    bool fin) {
+    int flags = opcode;
+    if (fin) flags |= WS_FIN;
+    if (has_mask) flags |=  WS_HAS_MASK;
+    return websocket_build_frame(out, (websocket_flags)flags, mask, data, data_len);
+}

+ 66 - 0
http/wsdef.h

@@ -0,0 +1,66 @@
+#ifndef HV_WS_DEF_H_
+#define HV_WS_DEF_H_
+
+#include "hexport.h"
+
+#include <stdbool.h>
+#include <stdlib.h> // import rand
+
+#define SEC_WEBSOCKET_VERSION   "Sec-WebSocket-Version"
+#define SEC_WEBSOCKET_KEY       "Sec-WebSocket-Key"
+#define SEC_WEBSOCKET_ACCEPT    "Sec-WebSocket-Accept"
+
+enum ws_opcode {
+    WS_OPCODE_CONTINUE = 0x0,
+    WS_OPCODE_TEXT     = 0x1,
+    WS_OPCODE_BINARY   = 0x2,
+    WS_OPCODE_CLOSE    = 0x8,
+    WS_OPCODE_PING     = 0x9,
+    WS_OPCODE_PONG     = 0xA,
+};
+
+BEGIN_EXTERN_C
+
+// Sec-WebSocket-Key => Sec-WebSocket-Accept
+HV_EXPORT void ws_encode_key(const char* key, char accept[]);
+
+// fix-header[2] + var-length[2/8] + mask[4] + data[data_len]
+HV_EXPORT int ws_calc_frame_size(int data_len, bool has_mask DEFAULT(false));
+
+HV_EXPORT int ws_build_frame(
+    char* out,
+    const char* data,
+    int data_len,
+    const char mask[4],
+    bool has_mask DEFAULT(false),
+    enum ws_opcode opcode DEFAULT(WS_OPCODE_TEXT),
+    bool fin DEFAULT(true));
+
+static inline int ws_client_build_frame(
+    char* out,
+    const char* data,
+    int data_len,
+    /* const char mask[4] */
+    /* bool has_mask = true */
+    enum ws_opcode opcode DEFAULT(WS_OPCODE_TEXT),
+    bool fin DEFAULT(true)) {
+    char mask[4];
+    *(int*)mask = rand();
+    return ws_build_frame(out, data, data_len, mask, true, opcode, fin);
+}
+
+static inline int ws_server_build_frame(
+    char* out,
+    const char* data,
+    int data_len,
+    /* const char mask[4] */
+    /* bool has_mask = false */
+    enum ws_opcode opcode DEFAULT(WS_OPCODE_TEXT),
+    bool fin DEFAULT(true)) {
+    char mask[4] = {0};
+    return ws_build_frame(out, data, data_len, mask, false, opcode, fin);
+}
+
+END_EXTERN_C
+
+#endif // HV_WS_DEF_H_