xref: /unit/src/nxt_h1proto_websocket.c (revision 1956:f4c32c2d595d)
11131Smax.romanov@nginx.com 
21131Smax.romanov@nginx.com /*
31131Smax.romanov@nginx.com  * Copyright (C) NGINX, Inc.
41131Smax.romanov@nginx.com  */
51131Smax.romanov@nginx.com 
61131Smax.romanov@nginx.com #include <nxt_main.h>
71131Smax.romanov@nginx.com #include <nxt_router.h>
81131Smax.romanov@nginx.com #include <nxt_http.h>
91131Smax.romanov@nginx.com #include <nxt_h1proto.h>
101131Smax.romanov@nginx.com #include <nxt_websocket.h>
111131Smax.romanov@nginx.com #include <nxt_websocket_header.h>
121131Smax.romanov@nginx.com 
131131Smax.romanov@nginx.com typedef struct {
141131Smax.romanov@nginx.com     uint16_t   code;
151131Smax.romanov@nginx.com     uint8_t    args;
161131Smax.romanov@nginx.com     nxt_str_t  desc;
171131Smax.romanov@nginx.com } nxt_ws_error_t;
181131Smax.romanov@nginx.com 
191131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_keepalive(nxt_task_t *task, void *obj, void *data);
201131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_frame_header_read(nxt_task_t *task, void *obj,
211131Smax.romanov@nginx.com     void *data);
221131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_keepalive_disable(nxt_task_t *task,
231131Smax.romanov@nginx.com     nxt_h1proto_t *h1p);
241131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_keepalive_enable(nxt_task_t *task,
251131Smax.romanov@nginx.com     nxt_h1proto_t *h1p);
261131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_frame_process(nxt_task_t *task, nxt_conn_t *c,
271131Smax.romanov@nginx.com     nxt_h1proto_t *h1p, nxt_websocket_header_t *wsh);
281131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_error(nxt_task_t *task, void *obj, void *data);
291268Sigor@sysoev.ru static ssize_t nxt_h1p_ws_io_read_handler(nxt_task_t *task, nxt_conn_t *c);
301131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_timeout(nxt_task_t *task, void *obj, void *data);
311131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_frame_payload_read(nxt_task_t *task, void *obj,
321131Smax.romanov@nginx.com     void *data);
331131Smax.romanov@nginx.com static void hxt_h1p_send_ws_error(nxt_task_t *task, nxt_http_request_t *r,
341131Smax.romanov@nginx.com     const nxt_ws_error_t *err, ...);
351131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_error_sent(nxt_task_t *task, void *obj, void *data);
361131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_pong(nxt_task_t *task, void *obj, void *data);
371131Smax.romanov@nginx.com 
381131Smax.romanov@nginx.com static const nxt_conn_state_t  nxt_h1p_read_ws_frame_header_state;
391131Smax.romanov@nginx.com static const nxt_conn_state_t  nxt_h1p_read_ws_frame_payload_state;
401131Smax.romanov@nginx.com 
411131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_out_of_memory = {
421131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_INTERNAL_SERVER_ERROR,
431131Smax.romanov@nginx.com     0, nxt_string("Out of memory") };
441131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_too_big = {
451131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_MESSAGE_TOO_BIG,
461131Smax.romanov@nginx.com     1, nxt_string("Message too big: %uL bytes") };
471131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_invalid_close_code = {
481131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
491131Smax.romanov@nginx.com     1, nxt_string("Close code %ud is not valid") };
501131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_going_away = {
511131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_GOING_AWAY,
521131Smax.romanov@nginx.com     0, nxt_string("Remote peer is going away") };
531131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_not_masked = {
541131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
551131Smax.romanov@nginx.com     0, nxt_string("Not masked client frame") };
561131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_ctrl_fragmented = {
571131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
581131Smax.romanov@nginx.com     0, nxt_string("Fragmented control frame") };
591131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_ctrl_too_big = {
601131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
611131Smax.romanov@nginx.com     1, nxt_string("Control frame too big: %uL bytes") };
621131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_invalid_close_len = {
631131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
641131Smax.romanov@nginx.com     0, nxt_string("Close frame payload length cannot be 1") };
651131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_invalid_opcode = {
661131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
671131Smax.romanov@nginx.com     1, nxt_string("Unrecognized opcode %ud") };
681131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_cont_expected = {
691131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
701131Smax.romanov@nginx.com     1, nxt_string("Continuation expected, but %ud opcode received") };
711131Smax.romanov@nginx.com 
721131Smax.romanov@nginx.com void
nxt_h1p_websocket_first_frame_start(nxt_task_t * task,nxt_http_request_t * r,nxt_buf_t * ws_frame)731131Smax.romanov@nginx.com nxt_h1p_websocket_first_frame_start(nxt_task_t *task, nxt_http_request_t *r,
741131Smax.romanov@nginx.com     nxt_buf_t *ws_frame)
751131Smax.romanov@nginx.com {
76*1956Smax.romanov@nginx.com     nxt_conn_t            *c;
77*1956Smax.romanov@nginx.com     nxt_timer_t           *timer;
78*1956Smax.romanov@nginx.com     nxt_h1proto_t         *h1p;
79*1956Smax.romanov@nginx.com     nxt_websocket_conf_t  *websocket_conf;
801131Smax.romanov@nginx.com 
811131Smax.romanov@nginx.com     nxt_debug(task, "h1p ws first frame start");
821131Smax.romanov@nginx.com 
831131Smax.romanov@nginx.com     h1p = r->proto.h1;
841131Smax.romanov@nginx.com     c = h1p->conn;
851131Smax.romanov@nginx.com 
861131Smax.romanov@nginx.com     if (!c->tcp_nodelay) {
871131Smax.romanov@nginx.com         nxt_conn_tcp_nodelay_on(task, c);
881131Smax.romanov@nginx.com     }
891131Smax.romanov@nginx.com 
90*1956Smax.romanov@nginx.com     websocket_conf = &r->conf->socket_conf->websocket_conf;
911131Smax.romanov@nginx.com 
92*1956Smax.romanov@nginx.com     if (nxt_slow_path(websocket_conf->keepalive_interval != 0)) {
931131Smax.romanov@nginx.com         h1p->websocket_timer = nxt_mp_zget(c->mem_pool,
941131Smax.romanov@nginx.com                                            sizeof(nxt_h1p_websocket_timer_t));
951131Smax.romanov@nginx.com         if (nxt_slow_path(h1p->websocket_timer == NULL)) {
961131Smax.romanov@nginx.com             hxt_h1p_send_ws_error(task, r, &nxt_ws_err_out_of_memory);
971131Smax.romanov@nginx.com             return;
981131Smax.romanov@nginx.com         }
991131Smax.romanov@nginx.com 
1001131Smax.romanov@nginx.com         h1p->websocket_timer->keepalive_interval =
101*1956Smax.romanov@nginx.com             websocket_conf->keepalive_interval;
1021131Smax.romanov@nginx.com         h1p->websocket_timer->h1p = h1p;
1031131Smax.romanov@nginx.com 
1041131Smax.romanov@nginx.com         timer = &h1p->websocket_timer->timer;
1051131Smax.romanov@nginx.com         timer->task = &c->task;
1061131Smax.romanov@nginx.com         timer->work_queue = &task->thread->engine->fast_work_queue;
1071131Smax.romanov@nginx.com         timer->log = &c->log;
1081131Smax.romanov@nginx.com         timer->bias = NXT_TIMER_DEFAULT_BIAS;
1091131Smax.romanov@nginx.com         timer->handler = nxt_h1p_conn_ws_keepalive;
1101131Smax.romanov@nginx.com     }
1111131Smax.romanov@nginx.com 
1121131Smax.romanov@nginx.com     nxt_h1p_websocket_frame_start(task, r, ws_frame);
1131131Smax.romanov@nginx.com }
1141131Smax.romanov@nginx.com 
1151131Smax.romanov@nginx.com 
1161131Smax.romanov@nginx.com void
nxt_h1p_websocket_frame_start(nxt_task_t * task,nxt_http_request_t * r,nxt_buf_t * ws_frame)1171131Smax.romanov@nginx.com nxt_h1p_websocket_frame_start(nxt_task_t *task, nxt_http_request_t *r,
1181131Smax.romanov@nginx.com     nxt_buf_t *ws_frame)
1191131Smax.romanov@nginx.com {
1201131Smax.romanov@nginx.com     size_t         size;
1211131Smax.romanov@nginx.com     nxt_buf_t      *in;
1221131Smax.romanov@nginx.com     nxt_conn_t     *c;
1231131Smax.romanov@nginx.com     nxt_h1proto_t  *h1p;
1241131Smax.romanov@nginx.com 
1251131Smax.romanov@nginx.com     nxt_debug(task, "h1p ws frame start");
1261131Smax.romanov@nginx.com 
1271131Smax.romanov@nginx.com     h1p = r->proto.h1;
1281131Smax.romanov@nginx.com 
1291131Smax.romanov@nginx.com     if (nxt_slow_path(h1p->websocket_closed)) {
1301131Smax.romanov@nginx.com         return;
1311131Smax.romanov@nginx.com     }
1321131Smax.romanov@nginx.com 
1331131Smax.romanov@nginx.com     c = h1p->conn;
1341131Smax.romanov@nginx.com     c->read = ws_frame;
1351131Smax.romanov@nginx.com 
1361417Smax.romanov@nginx.com     nxt_h1p_complete_buffers(task, h1p, 0);
1371131Smax.romanov@nginx.com 
1381131Smax.romanov@nginx.com     in = c->read;
1391131Smax.romanov@nginx.com     c->read_state = &nxt_h1p_read_ws_frame_header_state;
1401131Smax.romanov@nginx.com 
1411131Smax.romanov@nginx.com     if (in == NULL) {
1421131Smax.romanov@nginx.com         nxt_conn_read(task->thread->engine, c);
1431131Smax.romanov@nginx.com         nxt_h1p_conn_ws_keepalive_enable(task, h1p);
1441131Smax.romanov@nginx.com 
1451131Smax.romanov@nginx.com     } else {
1461131Smax.romanov@nginx.com         size = nxt_buf_mem_used_size(&in->mem);
1471131Smax.romanov@nginx.com 
1481131Smax.romanov@nginx.com         nxt_debug(task, "h1p read client ws frame");
1491131Smax.romanov@nginx.com 
1501131Smax.romanov@nginx.com         nxt_memmove(in->mem.start, in->mem.pos, size);
1511131Smax.romanov@nginx.com 
1521131Smax.romanov@nginx.com         in->mem.pos = in->mem.start;
1531131Smax.romanov@nginx.com         in->mem.free = in->mem.start + size;
1541131Smax.romanov@nginx.com 
1551131Smax.romanov@nginx.com         nxt_h1p_conn_ws_frame_header_read(task, c, h1p);
1561131Smax.romanov@nginx.com     }
1571131Smax.romanov@nginx.com }
1581131Smax.romanov@nginx.com 
1591131Smax.romanov@nginx.com 
1601131Smax.romanov@nginx.com static void
nxt_h1p_conn_ws_keepalive(nxt_task_t * task,void * obj,void * data)1611131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive(nxt_task_t *task, void *obj, void *data)
1621131Smax.romanov@nginx.com {
1631131Smax.romanov@nginx.com     nxt_buf_t                  *out;
1641131Smax.romanov@nginx.com     nxt_timer_t                *timer;
1651131Smax.romanov@nginx.com     nxt_h1proto_t              *h1p;
1661131Smax.romanov@nginx.com     nxt_http_request_t         *r;
1671131Smax.romanov@nginx.com     nxt_websocket_header_t     *wsh;
1681131Smax.romanov@nginx.com     nxt_h1p_websocket_timer_t  *ws_timer;
1691131Smax.romanov@nginx.com 
1701131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws keepalive");
1711131Smax.romanov@nginx.com 
1721131Smax.romanov@nginx.com     timer = obj;
1731131Smax.romanov@nginx.com     ws_timer = nxt_timer_data(timer, nxt_h1p_websocket_timer_t, timer);
1741131Smax.romanov@nginx.com     h1p = ws_timer->h1p;
1751131Smax.romanov@nginx.com 
1761131Smax.romanov@nginx.com     r = h1p->request;
1771131Smax.romanov@nginx.com     if (nxt_slow_path(r == NULL)) {
1781131Smax.romanov@nginx.com         return;
1791131Smax.romanov@nginx.com     }
1801131Smax.romanov@nginx.com 
1811131Smax.romanov@nginx.com     out = nxt_http_buf_mem(task, r, 2);
1821131Smax.romanov@nginx.com     if (nxt_slow_path(out == NULL)) {
1831131Smax.romanov@nginx.com         nxt_http_request_error_handler(task, r, r->proto.any);
1841131Smax.romanov@nginx.com         return;
1851131Smax.romanov@nginx.com     }
1861131Smax.romanov@nginx.com 
1871131Smax.romanov@nginx.com     out->mem.start[0] = 0;
1881131Smax.romanov@nginx.com     out->mem.start[1] = 0;
1891131Smax.romanov@nginx.com 
1901131Smax.romanov@nginx.com     wsh = (nxt_websocket_header_t *) out->mem.start;
1911131Smax.romanov@nginx.com     out->mem.free = nxt_websocket_frame_init(wsh, 0);
1921131Smax.romanov@nginx.com 
1931131Smax.romanov@nginx.com     wsh->fin = 1;
1941131Smax.romanov@nginx.com     wsh->opcode = NXT_WEBSOCKET_OP_PING;
1951131Smax.romanov@nginx.com 
1961131Smax.romanov@nginx.com     nxt_http_request_send(task, r, out);
1971131Smax.romanov@nginx.com }
1981131Smax.romanov@nginx.com 
1991131Smax.romanov@nginx.com 
2001131Smax.romanov@nginx.com static const nxt_conn_state_t  nxt_h1p_read_ws_frame_header_state
2011131Smax.romanov@nginx.com     nxt_aligned(64) =
2021131Smax.romanov@nginx.com {
2031131Smax.romanov@nginx.com     .ready_handler = nxt_h1p_conn_ws_frame_header_read,
2041131Smax.romanov@nginx.com     .close_handler = nxt_h1p_conn_ws_error,
2051131Smax.romanov@nginx.com     .error_handler = nxt_h1p_conn_ws_error,
2061131Smax.romanov@nginx.com 
2071131Smax.romanov@nginx.com     .io_read_handler = nxt_h1p_ws_io_read_handler,
2081131Smax.romanov@nginx.com 
2091131Smax.romanov@nginx.com     .timer_handler = nxt_h1p_conn_ws_timeout,
2101131Smax.romanov@nginx.com     .timer_value = nxt_h1p_conn_request_timer_value,
2111131Smax.romanov@nginx.com     .timer_data = offsetof(nxt_socket_conf_t, websocket_conf.read_timeout),
2121131Smax.romanov@nginx.com     .timer_autoreset = 1,
2131131Smax.romanov@nginx.com };
2141131Smax.romanov@nginx.com 
2151131Smax.romanov@nginx.com 
2161131Smax.romanov@nginx.com static void
nxt_h1p_conn_ws_frame_header_read(nxt_task_t * task,void * obj,void * data)2171131Smax.romanov@nginx.com nxt_h1p_conn_ws_frame_header_read(nxt_task_t *task, void *obj, void *data)
2181131Smax.romanov@nginx.com {
219*1956Smax.romanov@nginx.com     size_t                  size, hsize, frame_size, max_frame_size;
220*1956Smax.romanov@nginx.com     uint64_t                payload_len;
221*1956Smax.romanov@nginx.com     nxt_conn_t              *c;
222*1956Smax.romanov@nginx.com     nxt_h1proto_t           *h1p;
223*1956Smax.romanov@nginx.com     nxt_http_request_t      *r;
224*1956Smax.romanov@nginx.com     nxt_event_engine_t      *engine;
225*1956Smax.romanov@nginx.com     nxt_websocket_header_t  *wsh;
2261131Smax.romanov@nginx.com 
2271131Smax.romanov@nginx.com     c = obj;
2281131Smax.romanov@nginx.com     h1p = data;
2291131Smax.romanov@nginx.com 
2301131Smax.romanov@nginx.com     nxt_h1p_conn_ws_keepalive_disable(task, h1p);
2311131Smax.romanov@nginx.com 
2321131Smax.romanov@nginx.com     size = nxt_buf_mem_used_size(&c->read->mem);
2331131Smax.romanov@nginx.com 
2341131Smax.romanov@nginx.com     engine = task->thread->engine;
2351131Smax.romanov@nginx.com 
2361131Smax.romanov@nginx.com     if (size < 2) {
2371131Smax.romanov@nginx.com         nxt_debug(task, "h1p conn ws frame header read %z", size);
2381131Smax.romanov@nginx.com 
2391131Smax.romanov@nginx.com         nxt_conn_read(engine, c);
2401131Smax.romanov@nginx.com         nxt_h1p_conn_ws_keepalive_enable(task, h1p);
2411131Smax.romanov@nginx.com 
2421131Smax.romanov@nginx.com         return;
2431131Smax.romanov@nginx.com     }
2441131Smax.romanov@nginx.com 
2451131Smax.romanov@nginx.com     wsh = (nxt_websocket_header_t *) c->read->mem.pos;
2461131Smax.romanov@nginx.com 
2471131Smax.romanov@nginx.com     hsize = nxt_websocket_frame_header_size(wsh);
2481131Smax.romanov@nginx.com 
2491131Smax.romanov@nginx.com     if (size < hsize) {
2501131Smax.romanov@nginx.com         nxt_debug(task, "h1p conn ws frame header read %z < %z", size, hsize);
2511131Smax.romanov@nginx.com 
2521131Smax.romanov@nginx.com         nxt_conn_read(engine, c);
2531131Smax.romanov@nginx.com         nxt_h1p_conn_ws_keepalive_enable(task, h1p);
2541131Smax.romanov@nginx.com 
2551131Smax.romanov@nginx.com         return;
2561131Smax.romanov@nginx.com     }
2571131Smax.romanov@nginx.com 
2581131Smax.romanov@nginx.com     r = h1p->request;
2591131Smax.romanov@nginx.com     if (nxt_slow_path(r == NULL)) {
2601131Smax.romanov@nginx.com         return;
2611131Smax.romanov@nginx.com     }
2621131Smax.romanov@nginx.com 
2631131Smax.romanov@nginx.com     r->ws_frame = c->read;
2641131Smax.romanov@nginx.com 
2651131Smax.romanov@nginx.com     if (nxt_slow_path(wsh->mask == 0)) {
2661131Smax.romanov@nginx.com         hxt_h1p_send_ws_error(task, r, &nxt_ws_err_not_masked);
2671131Smax.romanov@nginx.com         return;
2681131Smax.romanov@nginx.com     }
2691131Smax.romanov@nginx.com 
2701131Smax.romanov@nginx.com     if ((wsh->opcode & NXT_WEBSOCKET_OP_CTRL) != 0) {
2711131Smax.romanov@nginx.com         if (nxt_slow_path(wsh->fin == 0)) {
2721131Smax.romanov@nginx.com             hxt_h1p_send_ws_error(task, r, &nxt_ws_err_ctrl_fragmented);
2731131Smax.romanov@nginx.com             return;
2741131Smax.romanov@nginx.com         }
2751131Smax.romanov@nginx.com 
2761131Smax.romanov@nginx.com         if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_PING
2771131Smax.romanov@nginx.com                           && wsh->opcode != NXT_WEBSOCKET_OP_PONG
2781131Smax.romanov@nginx.com                           && wsh->opcode != NXT_WEBSOCKET_OP_CLOSE))
2791131Smax.romanov@nginx.com         {
2801131Smax.romanov@nginx.com             hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_opcode,
2811131Smax.romanov@nginx.com                                   wsh->opcode);
2821131Smax.romanov@nginx.com             return;
2831131Smax.romanov@nginx.com         }
2841131Smax.romanov@nginx.com 
2851131Smax.romanov@nginx.com         if (nxt_slow_path(wsh->payload_len > 125)) {
2861131Smax.romanov@nginx.com             hxt_h1p_send_ws_error(task, r, &nxt_ws_err_ctrl_too_big,
2871131Smax.romanov@nginx.com                                   nxt_websocket_frame_payload_len(wsh));
2881131Smax.romanov@nginx.com             return;
2891131Smax.romanov@nginx.com         }
2901131Smax.romanov@nginx.com 
2911131Smax.romanov@nginx.com         if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_CLOSE
2921131Smax.romanov@nginx.com                           && wsh->payload_len == 1))
2931131Smax.romanov@nginx.com         {
2941131Smax.romanov@nginx.com             hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_close_len);
2951131Smax.romanov@nginx.com             return;
2961131Smax.romanov@nginx.com         }
2971131Smax.romanov@nginx.com 
2981131Smax.romanov@nginx.com     } else {
2991131Smax.romanov@nginx.com         if (h1p->websocket_cont_expected) {
3001131Smax.romanov@nginx.com             if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_CONT)) {
3011131Smax.romanov@nginx.com                 hxt_h1p_send_ws_error(task, r, &nxt_ws_err_cont_expected,
3021131Smax.romanov@nginx.com                                       wsh->opcode);
3031131Smax.romanov@nginx.com                 return;
3041131Smax.romanov@nginx.com             }
3051131Smax.romanov@nginx.com 
3061131Smax.romanov@nginx.com         } else {
3071131Smax.romanov@nginx.com             if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_BINARY
3081131Smax.romanov@nginx.com                               && wsh->opcode != NXT_WEBSOCKET_OP_TEXT))
3091131Smax.romanov@nginx.com             {
3101131Smax.romanov@nginx.com                 hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_opcode,
3111131Smax.romanov@nginx.com                                       wsh->opcode);
3121131Smax.romanov@nginx.com                 return;
3131131Smax.romanov@nginx.com             }
3141131Smax.romanov@nginx.com         }
3151131Smax.romanov@nginx.com 
3161131Smax.romanov@nginx.com         h1p->websocket_cont_expected = !wsh->fin;
3171131Smax.romanov@nginx.com     }
3181131Smax.romanov@nginx.com 
319*1956Smax.romanov@nginx.com     max_frame_size = r->conf->socket_conf->websocket_conf.max_frame_size;
3201131Smax.romanov@nginx.com 
3211131Smax.romanov@nginx.com     payload_len = nxt_websocket_frame_payload_len(wsh);
3221131Smax.romanov@nginx.com 
3231131Smax.romanov@nginx.com     if (nxt_slow_path(hsize > max_frame_size
3241131Smax.romanov@nginx.com                       || payload_len > (max_frame_size - hsize)))
3251131Smax.romanov@nginx.com     {
3261131Smax.romanov@nginx.com         hxt_h1p_send_ws_error(task, r, &nxt_ws_err_too_big, payload_len);
3271131Smax.romanov@nginx.com         return;
3281131Smax.romanov@nginx.com     }
3291131Smax.romanov@nginx.com 
3301131Smax.romanov@nginx.com     c->read_state = &nxt_h1p_read_ws_frame_payload_state;
3311131Smax.romanov@nginx.com 
3321131Smax.romanov@nginx.com     frame_size = payload_len + hsize;
3331131Smax.romanov@nginx.com 
3341131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws frame header read: %z, %z", size, frame_size);
3351131Smax.romanov@nginx.com 
3361131Smax.romanov@nginx.com     if (frame_size <= size) {
3371131Smax.romanov@nginx.com         nxt_h1p_conn_ws_frame_process(task, c, h1p, wsh);
3381131Smax.romanov@nginx.com 
3391131Smax.romanov@nginx.com         return;
3401131Smax.romanov@nginx.com     }
3411131Smax.romanov@nginx.com 
3421131Smax.romanov@nginx.com     if (frame_size < (size_t) nxt_buf_mem_size(&c->read->mem)) {
3431131Smax.romanov@nginx.com         c->read->mem.end = c->read->mem.start + frame_size;
3441131Smax.romanov@nginx.com 
3451131Smax.romanov@nginx.com     } else {
3461131Smax.romanov@nginx.com         nxt_buf_t *b = nxt_buf_mem_alloc(c->mem_pool, frame_size - size, 0);
3471131Smax.romanov@nginx.com 
3481131Smax.romanov@nginx.com         c->read->next = b;
3491131Smax.romanov@nginx.com         c->read = b;
3501131Smax.romanov@nginx.com     }
3511131Smax.romanov@nginx.com 
3521131Smax.romanov@nginx.com     nxt_conn_read(engine, c);
3531131Smax.romanov@nginx.com     nxt_h1p_conn_ws_keepalive_enable(task, h1p);
3541131Smax.romanov@nginx.com }
3551131Smax.romanov@nginx.com 
3561131Smax.romanov@nginx.com 
3571131Smax.romanov@nginx.com static void
nxt_h1p_conn_ws_keepalive_disable(nxt_task_t * task,nxt_h1proto_t * h1p)3581131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive_disable(nxt_task_t *task, nxt_h1proto_t *h1p)
3591131Smax.romanov@nginx.com {
3601131Smax.romanov@nginx.com     nxt_timer_t  *timer;
3611131Smax.romanov@nginx.com 
3621131Smax.romanov@nginx.com     if (h1p->websocket_timer == NULL) {
3631131Smax.romanov@nginx.com         return;
3641131Smax.romanov@nginx.com     }
3651131Smax.romanov@nginx.com 
3661131Smax.romanov@nginx.com     timer = &h1p->websocket_timer->timer;
3671131Smax.romanov@nginx.com 
3681131Smax.romanov@nginx.com     if (nxt_slow_path(timer->handler != nxt_h1p_conn_ws_keepalive)) {
3691131Smax.romanov@nginx.com         nxt_debug(task, "h1p ws keepalive disable: scheduled ws shutdown");
3701131Smax.romanov@nginx.com         return;
3711131Smax.romanov@nginx.com     }
3721131Smax.romanov@nginx.com 
3731131Smax.romanov@nginx.com     nxt_timer_disable(task->thread->engine, timer);
3741131Smax.romanov@nginx.com }
3751131Smax.romanov@nginx.com 
3761131Smax.romanov@nginx.com 
3771131Smax.romanov@nginx.com static void
nxt_h1p_conn_ws_keepalive_enable(nxt_task_t * task,nxt_h1proto_t * h1p)3781131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive_enable(nxt_task_t *task, nxt_h1proto_t *h1p)
3791131Smax.romanov@nginx.com {
3801131Smax.romanov@nginx.com     nxt_timer_t  *timer;
3811131Smax.romanov@nginx.com 
3821131Smax.romanov@nginx.com     if (h1p->websocket_timer == NULL) {
3831131Smax.romanov@nginx.com         return;
3841131Smax.romanov@nginx.com     }
3851131Smax.romanov@nginx.com 
3861131Smax.romanov@nginx.com     timer = &h1p->websocket_timer->timer;
3871131Smax.romanov@nginx.com 
3881131Smax.romanov@nginx.com     if (nxt_slow_path(timer->handler != nxt_h1p_conn_ws_keepalive)) {
3891131Smax.romanov@nginx.com         nxt_debug(task, "h1p ws keepalive enable: scheduled ws shutdown");
3901131Smax.romanov@nginx.com         return;
3911131Smax.romanov@nginx.com     }
3921131Smax.romanov@nginx.com 
3931131Smax.romanov@nginx.com     nxt_timer_add(task->thread->engine, timer,
3941131Smax.romanov@nginx.com                   h1p->websocket_timer->keepalive_interval);
3951131Smax.romanov@nginx.com }
3961131Smax.romanov@nginx.com 
3971131Smax.romanov@nginx.com 
3981131Smax.romanov@nginx.com static void
nxt_h1p_conn_ws_frame_process(nxt_task_t * task,nxt_conn_t * c,nxt_h1proto_t * h1p,nxt_websocket_header_t * wsh)3991131Smax.romanov@nginx.com nxt_h1p_conn_ws_frame_process(nxt_task_t *task, nxt_conn_t *c,
4001131Smax.romanov@nginx.com     nxt_h1proto_t *h1p, nxt_websocket_header_t *wsh)
4011131Smax.romanov@nginx.com {
4021131Smax.romanov@nginx.com     size_t              hsize;
4031131Smax.romanov@nginx.com     uint8_t             *p, *mask;
4041131Smax.romanov@nginx.com     uint16_t            code;
4051131Smax.romanov@nginx.com     nxt_http_request_t  *r;
4061131Smax.romanov@nginx.com 
4071131Smax.romanov@nginx.com     r = h1p->request;
4081131Smax.romanov@nginx.com 
4091131Smax.romanov@nginx.com     c->read = NULL;
4101131Smax.romanov@nginx.com 
4111131Smax.romanov@nginx.com     if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_PING)) {
4121155Smax.romanov@nginx.com         nxt_h1p_conn_ws_pong(task, r, NULL);
4131131Smax.romanov@nginx.com         return;
4141131Smax.romanov@nginx.com     }
4151131Smax.romanov@nginx.com 
4161131Smax.romanov@nginx.com     if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_CLOSE)) {
4171131Smax.romanov@nginx.com         if (wsh->payload_len >= 2) {
4181131Smax.romanov@nginx.com             hsize = nxt_websocket_frame_header_size(wsh);
4191131Smax.romanov@nginx.com             mask = nxt_pointer_to(wsh, hsize - 4);
4201131Smax.romanov@nginx.com             p = nxt_pointer_to(wsh, hsize);
4211131Smax.romanov@nginx.com 
4221131Smax.romanov@nginx.com             code = ((p[0] ^ mask[0]) << 8) + (p[1] ^ mask[1]);
4231131Smax.romanov@nginx.com 
4241131Smax.romanov@nginx.com             if (nxt_slow_path(code < 1000 || code >= 5000
4251131Smax.romanov@nginx.com                               || (code > 1003 && code < 1007)
4261131Smax.romanov@nginx.com                               || (code > 1014 && code < 3000)))
4271131Smax.romanov@nginx.com             {
4281131Smax.romanov@nginx.com                 hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_close_code,
4291131Smax.romanov@nginx.com                                       code);
4301131Smax.romanov@nginx.com                 return;
4311131Smax.romanov@nginx.com             }
4321131Smax.romanov@nginx.com         }
4331131Smax.romanov@nginx.com 
4341131Smax.romanov@nginx.com         h1p->websocket_closed = 1;
4351131Smax.romanov@nginx.com     }
4361131Smax.romanov@nginx.com 
4371155Smax.romanov@nginx.com     r->state->ready_handler(task, r, NULL);
4381131Smax.romanov@nginx.com }
4391131Smax.romanov@nginx.com 
4401131Smax.romanov@nginx.com 
4411131Smax.romanov@nginx.com static void
nxt_h1p_conn_ws_error(nxt_task_t * task,void * obj,void * data)4421131Smax.romanov@nginx.com nxt_h1p_conn_ws_error(nxt_task_t *task, void *obj, void *data)
4431131Smax.romanov@nginx.com {
4441131Smax.romanov@nginx.com     nxt_h1proto_t       *h1p;
4451131Smax.romanov@nginx.com     nxt_http_request_t  *r;
4461131Smax.romanov@nginx.com 
4471131Smax.romanov@nginx.com     h1p = data;
4481131Smax.romanov@nginx.com 
4491131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws error");
4501131Smax.romanov@nginx.com 
4511131Smax.romanov@nginx.com     r = h1p->request;
4521131Smax.romanov@nginx.com 
4531131Smax.romanov@nginx.com     h1p->keepalive = 0;
4541131Smax.romanov@nginx.com 
4551131Smax.romanov@nginx.com     if (nxt_fast_path(r != NULL)) {
4561131Smax.romanov@nginx.com         r->state->error_handler(task, r, h1p);
4571131Smax.romanov@nginx.com     }
4581131Smax.romanov@nginx.com }
4591131Smax.romanov@nginx.com 
4601131Smax.romanov@nginx.com 
4611131Smax.romanov@nginx.com static ssize_t
nxt_h1p_ws_io_read_handler(nxt_task_t * task,nxt_conn_t * c)4621268Sigor@sysoev.ru nxt_h1p_ws_io_read_handler(nxt_task_t *task, nxt_conn_t *c)
4631131Smax.romanov@nginx.com {
4641131Smax.romanov@nginx.com     size_t     size;
4651131Smax.romanov@nginx.com     ssize_t    n;
4661131Smax.romanov@nginx.com     nxt_buf_t  *b;
4671131Smax.romanov@nginx.com 
4681131Smax.romanov@nginx.com     b = c->read;
4691131Smax.romanov@nginx.com 
4701131Smax.romanov@nginx.com     if (b == NULL) {
4711131Smax.romanov@nginx.com         /* Enough for control frame. */
4721131Smax.romanov@nginx.com         size = 10 + 125;
4731131Smax.romanov@nginx.com 
4741131Smax.romanov@nginx.com         b = nxt_buf_mem_alloc(c->mem_pool, size, 0);
4751131Smax.romanov@nginx.com         if (nxt_slow_path(b == NULL)) {
4761131Smax.romanov@nginx.com             c->socket.error = NXT_ENOMEM;
4771131Smax.romanov@nginx.com             return NXT_ERROR;
4781131Smax.romanov@nginx.com         }
4791131Smax.romanov@nginx.com     }
4801131Smax.romanov@nginx.com 
4811131Smax.romanov@nginx.com     n = c->io->recvbuf(c, b);
4821131Smax.romanov@nginx.com 
4831131Smax.romanov@nginx.com     if (n > 0) {
4841131Smax.romanov@nginx.com         c->read = b;
4851131Smax.romanov@nginx.com 
4861131Smax.romanov@nginx.com     } else {
4871131Smax.romanov@nginx.com         c->read = NULL;
4881131Smax.romanov@nginx.com         nxt_mp_free(c->mem_pool, b);
4891131Smax.romanov@nginx.com     }
4901131Smax.romanov@nginx.com 
4911131Smax.romanov@nginx.com     return n;
4921131Smax.romanov@nginx.com }
4931131Smax.romanov@nginx.com 
4941131Smax.romanov@nginx.com 
4951131Smax.romanov@nginx.com static void
nxt_h1p_conn_ws_timeout(nxt_task_t * task,void * obj,void * data)4961131Smax.romanov@nginx.com nxt_h1p_conn_ws_timeout(nxt_task_t *task, void *obj, void *data)
4971131Smax.romanov@nginx.com {
4981131Smax.romanov@nginx.com     nxt_conn_t          *c;
4991131Smax.romanov@nginx.com     nxt_timer_t         *timer;
5001131Smax.romanov@nginx.com     nxt_h1proto_t       *h1p;
5011131Smax.romanov@nginx.com     nxt_http_request_t  *r;
5021131Smax.romanov@nginx.com 
5031131Smax.romanov@nginx.com     timer = obj;
5041131Smax.romanov@nginx.com 
5051131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws timeout");
5061131Smax.romanov@nginx.com 
5071131Smax.romanov@nginx.com     c = nxt_read_timer_conn(timer);
5081131Smax.romanov@nginx.com     c->block_read = 1;
5091131Smax.romanov@nginx.com     /*
5101131Smax.romanov@nginx.com      * Disable SO_LINGER off during socket closing
5111131Smax.romanov@nginx.com      * to send "408 Request Timeout" error response.
5121131Smax.romanov@nginx.com      */
5131131Smax.romanov@nginx.com     c->socket.timedout = 0;
5141131Smax.romanov@nginx.com 
5151131Smax.romanov@nginx.com     h1p = c->socket.data;
5161131Smax.romanov@nginx.com     h1p->keepalive = 0;
5171131Smax.romanov@nginx.com 
5181131Smax.romanov@nginx.com     r = h1p->request;
5191131Smax.romanov@nginx.com     if (nxt_slow_path(r == NULL)) {
5201131Smax.romanov@nginx.com         return;
5211131Smax.romanov@nginx.com     }
5221131Smax.romanov@nginx.com 
5231131Smax.romanov@nginx.com     hxt_h1p_send_ws_error(task, r, &nxt_ws_err_going_away);
5241131Smax.romanov@nginx.com }
5251131Smax.romanov@nginx.com 
5261131Smax.romanov@nginx.com 
5271131Smax.romanov@nginx.com static const nxt_conn_state_t  nxt_h1p_read_ws_frame_payload_state
5281131Smax.romanov@nginx.com     nxt_aligned(64) =
5291131Smax.romanov@nginx.com {
5301131Smax.romanov@nginx.com     .ready_handler = nxt_h1p_conn_ws_frame_payload_read,
5311131Smax.romanov@nginx.com     .close_handler = nxt_h1p_conn_ws_error,
5321131Smax.romanov@nginx.com     .error_handler = nxt_h1p_conn_ws_error,
5331131Smax.romanov@nginx.com 
5341131Smax.romanov@nginx.com     .timer_handler = nxt_h1p_conn_ws_timeout,
5351131Smax.romanov@nginx.com     .timer_value = nxt_h1p_conn_request_timer_value,
5361131Smax.romanov@nginx.com     .timer_data = offsetof(nxt_socket_conf_t, websocket_conf.read_timeout),
5371131Smax.romanov@nginx.com     .timer_autoreset = 1,
5381131Smax.romanov@nginx.com };
5391131Smax.romanov@nginx.com 
5401131Smax.romanov@nginx.com 
5411131Smax.romanov@nginx.com static void
nxt_h1p_conn_ws_frame_payload_read(nxt_task_t * task,void * obj,void * data)5421131Smax.romanov@nginx.com nxt_h1p_conn_ws_frame_payload_read(nxt_task_t *task, void *obj, void *data)
5431131Smax.romanov@nginx.com {
5441131Smax.romanov@nginx.com     nxt_conn_t              *c;
5451131Smax.romanov@nginx.com     nxt_h1proto_t           *h1p;
5461131Smax.romanov@nginx.com     nxt_http_request_t      *r;
5471131Smax.romanov@nginx.com     nxt_event_engine_t      *engine;
5481131Smax.romanov@nginx.com     nxt_websocket_header_t  *wsh;
5491131Smax.romanov@nginx.com 
5501131Smax.romanov@nginx.com     c = obj;
5511131Smax.romanov@nginx.com     h1p = data;
5521131Smax.romanov@nginx.com 
5531131Smax.romanov@nginx.com     nxt_h1p_conn_ws_keepalive_disable(task, h1p);
5541131Smax.romanov@nginx.com 
5551131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws frame read");
5561131Smax.romanov@nginx.com 
5571131Smax.romanov@nginx.com     if (nxt_buf_mem_free_size(&c->read->mem) == 0) {
5581131Smax.romanov@nginx.com         r = h1p->request;
5591131Smax.romanov@nginx.com         if (nxt_slow_path(r == NULL)) {
5601131Smax.romanov@nginx.com             return;
5611131Smax.romanov@nginx.com         }
5621131Smax.romanov@nginx.com 
5631131Smax.romanov@nginx.com         wsh = (nxt_websocket_header_t *) r->ws_frame->mem.pos;
5641131Smax.romanov@nginx.com 
5651131Smax.romanov@nginx.com         nxt_h1p_conn_ws_frame_process(task, c, h1p, wsh);
5661131Smax.romanov@nginx.com 
5671131Smax.romanov@nginx.com         return;
5681131Smax.romanov@nginx.com     }
5691131Smax.romanov@nginx.com 
5701131Smax.romanov@nginx.com     engine = task->thread->engine;
5711131Smax.romanov@nginx.com 
5721131Smax.romanov@nginx.com     nxt_conn_read(engine, c);
5731131Smax.romanov@nginx.com     nxt_h1p_conn_ws_keepalive_enable(task, h1p);
5741131Smax.romanov@nginx.com }
5751131Smax.romanov@nginx.com 
5761131Smax.romanov@nginx.com 
5771131Smax.romanov@nginx.com static void
hxt_h1p_send_ws_error(nxt_task_t * task,nxt_http_request_t * r,const nxt_ws_error_t * err,...)5781131Smax.romanov@nginx.com hxt_h1p_send_ws_error(nxt_task_t *task, nxt_http_request_t *r,
5791131Smax.romanov@nginx.com     const nxt_ws_error_t *err, ...)
5801131Smax.romanov@nginx.com {
5811131Smax.romanov@nginx.com     u_char                  *p;
5821131Smax.romanov@nginx.com     va_list                 args;
5831131Smax.romanov@nginx.com     nxt_buf_t               *out;
5841131Smax.romanov@nginx.com     nxt_str_t               desc;
5851131Smax.romanov@nginx.com     nxt_websocket_header_t  *wsh;
5861131Smax.romanov@nginx.com     u_char                  buf[125];
5871131Smax.romanov@nginx.com 
5881131Smax.romanov@nginx.com     if (nxt_slow_path(err->args)) {
5891131Smax.romanov@nginx.com         va_start(args, err);
5901131Smax.romanov@nginx.com         p = nxt_vsprintf(buf, buf + sizeof(buf), (char *) err->desc.start,
5911131Smax.romanov@nginx.com                          args);
5921131Smax.romanov@nginx.com         va_end(args);
5931131Smax.romanov@nginx.com 
5941131Smax.romanov@nginx.com         desc.start = buf;
5951131Smax.romanov@nginx.com         desc.length = p - buf;
5961131Smax.romanov@nginx.com 
5971131Smax.romanov@nginx.com     } else {
5981131Smax.romanov@nginx.com         desc = err->desc;
5991131Smax.romanov@nginx.com     }
6001131Smax.romanov@nginx.com 
6011131Smax.romanov@nginx.com     nxt_log(task, NXT_LOG_INFO, "websocket error %d: %V", err->code, &desc);
6021131Smax.romanov@nginx.com 
6031131Smax.romanov@nginx.com     out = nxt_http_buf_mem(task, r, 2 + sizeof(err->code) + desc.length);
6041131Smax.romanov@nginx.com     if (nxt_slow_path(out == NULL)) {
6051131Smax.romanov@nginx.com         nxt_http_request_error_handler(task, r, r->proto.any);
6061131Smax.romanov@nginx.com         return;
6071131Smax.romanov@nginx.com     }
6081131Smax.romanov@nginx.com 
6091131Smax.romanov@nginx.com     out->mem.start[0] = 0;
6101131Smax.romanov@nginx.com     out->mem.start[1] = 0;
6111131Smax.romanov@nginx.com 
6121131Smax.romanov@nginx.com     wsh = (nxt_websocket_header_t *) out->mem.start;
6131131Smax.romanov@nginx.com     p = nxt_websocket_frame_init(wsh, sizeof(err->code) + desc.length);
6141131Smax.romanov@nginx.com 
6151131Smax.romanov@nginx.com     wsh->fin = 1;
6161131Smax.romanov@nginx.com     wsh->opcode = NXT_WEBSOCKET_OP_CLOSE;
6171131Smax.romanov@nginx.com 
6181131Smax.romanov@nginx.com     *p++ = (err->code >> 8) & 0xFF;
6191131Smax.romanov@nginx.com     *p++ = err->code & 0xFF;
6201131Smax.romanov@nginx.com 
6211131Smax.romanov@nginx.com     out->mem.free = nxt_cpymem(p, desc.start, desc.length);
6221131Smax.romanov@nginx.com     out->next = nxt_http_buf_last(r);
6231131Smax.romanov@nginx.com 
6241131Smax.romanov@nginx.com     if (out->next != NULL) {
6251131Smax.romanov@nginx.com         out->next->completion_handler = nxt_h1p_conn_ws_error_sent;
6261131Smax.romanov@nginx.com     }
6271131Smax.romanov@nginx.com 
6281131Smax.romanov@nginx.com     nxt_http_request_send(task, r, out);
6291131Smax.romanov@nginx.com }
6301131Smax.romanov@nginx.com 
6311131Smax.romanov@nginx.com 
6321131Smax.romanov@nginx.com static void
nxt_h1p_conn_ws_error_sent(nxt_task_t * task,void * obj,void * data)6331131Smax.romanov@nginx.com nxt_h1p_conn_ws_error_sent(nxt_task_t *task, void *obj, void *data)
6341131Smax.romanov@nginx.com {
6351131Smax.romanov@nginx.com     nxt_http_request_t  *r;
6361131Smax.romanov@nginx.com 
6371131Smax.romanov@nginx.com     r = data;
6381131Smax.romanov@nginx.com 
6391131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws error sent");
6401131Smax.romanov@nginx.com 
6411131Smax.romanov@nginx.com     r->state->error_handler(task, r, r->proto.any);
6421131Smax.romanov@nginx.com }
6431131Smax.romanov@nginx.com 
6441131Smax.romanov@nginx.com 
6451131Smax.romanov@nginx.com static void
nxt_h1p_conn_ws_pong(nxt_task_t * task,void * obj,void * data)6461131Smax.romanov@nginx.com nxt_h1p_conn_ws_pong(nxt_task_t *task, void *obj, void *data)
6471131Smax.romanov@nginx.com {
6481131Smax.romanov@nginx.com     uint8_t                 payload_len, i;
6491131Smax.romanov@nginx.com     nxt_buf_t               *b, *out, *next;
6501131Smax.romanov@nginx.com     nxt_http_request_t      *r;
6511131Smax.romanov@nginx.com     nxt_websocket_header_t  *wsh;
6521131Smax.romanov@nginx.com     uint8_t                 mask[4];
6531131Smax.romanov@nginx.com 
6541131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws pong");
6551131Smax.romanov@nginx.com 
6561131Smax.romanov@nginx.com     r = obj;
6571131Smax.romanov@nginx.com     b = r->ws_frame;
6581131Smax.romanov@nginx.com 
6591131Smax.romanov@nginx.com     wsh = (nxt_websocket_header_t *) b->mem.pos;
6601131Smax.romanov@nginx.com     payload_len = wsh->payload_len;
6611131Smax.romanov@nginx.com 
6621131Smax.romanov@nginx.com     b->mem.pos += 2;
6631131Smax.romanov@nginx.com 
6641131Smax.romanov@nginx.com     nxt_memcpy(mask, b->mem.pos, 4);
6651131Smax.romanov@nginx.com 
6661131Smax.romanov@nginx.com     b->mem.pos += 4;
6671131Smax.romanov@nginx.com 
6681131Smax.romanov@nginx.com     out = nxt_http_buf_mem(task, r, 2 + payload_len);
6691131Smax.romanov@nginx.com     if (nxt_slow_path(out == NULL)) {
6701131Smax.romanov@nginx.com         nxt_http_request_error_handler(task, r, r->proto.any);
6711131Smax.romanov@nginx.com         return;
6721131Smax.romanov@nginx.com     }
6731131Smax.romanov@nginx.com 
6741131Smax.romanov@nginx.com     out->mem.start[0] = 0;
6751131Smax.romanov@nginx.com     out->mem.start[1] = 0;
6761131Smax.romanov@nginx.com 
6771131Smax.romanov@nginx.com     wsh = (nxt_websocket_header_t *) out->mem.start;
6781131Smax.romanov@nginx.com     out->mem.free = nxt_websocket_frame_init(wsh, payload_len);
6791131Smax.romanov@nginx.com 
6801131Smax.romanov@nginx.com     wsh->fin = 1;
6811131Smax.romanov@nginx.com     wsh->opcode = NXT_WEBSOCKET_OP_PONG;
6821131Smax.romanov@nginx.com 
6831131Smax.romanov@nginx.com     for (i = 0; i < payload_len; i++) {
6841131Smax.romanov@nginx.com         while (nxt_buf_mem_used_size(&b->mem) == 0) {
6851131Smax.romanov@nginx.com             next = b->next;
6861269Sigor@sysoev.ru             b->next = NULL;
6871131Smax.romanov@nginx.com 
6881131Smax.romanov@nginx.com             nxt_work_queue_add(&task->thread->engine->fast_work_queue,
6891131Smax.romanov@nginx.com                                b->completion_handler, task, b, b->parent);
6901131Smax.romanov@nginx.com 
6911131Smax.romanov@nginx.com             b = next;
6921131Smax.romanov@nginx.com         }
6931131Smax.romanov@nginx.com 
6941131Smax.romanov@nginx.com         *out->mem.free++ = *b->mem.pos++ ^ mask[i % 4];
6951131Smax.romanov@nginx.com     }
6961131Smax.romanov@nginx.com 
6971131Smax.romanov@nginx.com     r->ws_frame = b;
6981131Smax.romanov@nginx.com 
6991131Smax.romanov@nginx.com     nxt_http_request_send(task, r, out);
7001131Smax.romanov@nginx.com 
7011131Smax.romanov@nginx.com     nxt_http_request_ws_frame_start(task, r, r->ws_frame);
7021131Smax.romanov@nginx.com }
703