int bb_socketio_send(struct bb_session *bbs, char *buf, size_t len) { char *cl = bb_alloc(MAX_CONTENT_LENGTH); if (!cl) { bb_error("unable to allocate memory for socket.io message: malloc()"); return -1; } int chunk_len = snprintf(cl, MAX_CONTENT_LENGTH, "%llu\r\n\r\n", (unsigned long long) len); if (bb_wq_push(bbs, (char *)message_headers, strlen(message_headers), 0)) return -1; if (bb_wq_push(bbs, (char *)cl, chunk_len, BB_WQ_FREE)) return -1; if (bb_wq_push(bbs, (char *)buf, len, BB_WQ_FREE)) return -1; return 0; }
int bb_manage_chunk(struct bb_session_request *bbsr, char *buf, size_t len) { struct bb_session *bbs = bbsr->bbs; char *chunk = malloc(MAX_CHUNK_STORAGE); if (!chunk) { bb_error("unable to allocate memory for chunked response: malloc()"); bb_session_close(bbs); return -1; } int chunk_len = snprintf(chunk, MAX_CHUNK_STORAGE, "%X\r\n", (unsigned int) len); if (bb_wq_push(bbs, chunk, chunk_len, 1)) goto end; if (bb_wq_push_copy(bbs, buf, len, 1)) goto end; if (bb_wq_push(bbs, "\r\n", 2, 0)) goto end; if (len == 0 && bbsr->close) { if (bb_wq_push_close(bbs)) goto end; } end: bb_session_close(bbs); return -1; }
static int socketio_poller(struct bb_session *bbs) { struct bb_socketio_message *bbsm = bbs->sio_queue; if (bbsm) { if (bb_socketio_send(bbs, bbsm->buf, bbsm->len)) { fprintf(stderr,"unable to deliver message\n"); return 0; } bbs->sio_queue = bbsm->next; bb_free(bbsm, sizeof(struct bb_socketio_message)); bbs->sio_poller = 0; return 1; } if (bb_wq_push(bbs, (char *)empty_queue, strlen(empty_queue), 0)) return 0; if (bb_wq_push_close(bbs)) return 0; bb_session_reset_timer(bbs, 60, NULL); bbs->sio_poller = 0; // leave the session opened return 1; }
int bb_send_websocket_handshake(struct bb_session_request *bbsr) { char sha[20]; struct bb_http_header *swk = bb_http_req_header(bbsr, "sec-websocket-key", 17); if (!swk) return -1; struct bb_http_header *origin = bb_http_req_header(bbsr, "origin", 6); sha1(swk->value, swk->vallen, sha); size_t b_len = 20; char *b64 = base64(sha, &b_len); bbsr->http_major = '0' + bbsr->parser.http_major; bbsr->http_minor = '0' + bbsr->parser.http_minor; if (bb_wq_push(bbsr->bbs->connection, "HTTP/", 5, 0)) return -1; if (bb_wq_push(bbsr->bbs->connection, &bbsr->http_major, 1, 0)) return -1; if (bb_wq_push(bbsr->bbs->connection, ".", 1, 0)) return -1; if (bb_wq_push(bbsr->bbs->connection, &bbsr->http_minor, 1, 0)) return -1; if (bb_wq_push(bbsr->bbs->connection, " 101 WebSocket Protocol Handshake\r\nUpgrade: WebSocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ", 98, 0)) return -1; if (bb_wq_push(bbsr->bbs->connection, b64, b_len, 1)) return -1; if (bb_wq_push(bbsr->bbs->connection, "\r\n\r\n", 4, 0)) return -1; return 0; }
int bb_manage_socketio(struct bb_session *bbs, char *method, size_t method_len, char *url, size_t url_len) { char *query_string = memchr(url, '?', url_len); size_t query_string_len = 0; if (query_string) { query_string_len = url_len - (query_string-url); url_len = query_string-url; } //fprintf(stderr,"SOCKET.IO %p %.*s %.*s\n", bbs, method_len, method, url_len, url); // handshake // /socket.io/1/ if (url_len == 13) { char handshake[36+3+3+1+21]; uuid_t *session_uuid = (uuid_t *) &bbs->uuid_part1; uuid_unparse(*session_uuid, handshake); if (bbs->stream_id > 0) { // only websockets are supported under SPDY char *supported = "websocket"; memcpy(handshake+36, ":30:60:", 7); memcpy(handshake+36+7, supported, 9); if (bb_spdy_raw_send_headers(bbs, 7, (struct bb_http_header *) handshake_spdy_headers, "200", "HTTP/1.1", 0)) return -1; if (bb_spdy_send_body(bbs, handshake, 52)) return -1; if (bb_spdy_send_end(bbs)) return -1; } else { char *supported = "websocket,xhr-polling"; memcpy(handshake+36, ":30:60:", 7); memcpy(handshake+36+7, supported, 21); if (bb_wq_push(bbs, (char *)handshake_headers, strlen(handshake_headers), 0)) return -1; if (bb_wq_push_copy(bbs, handshake, 64, BB_WQ_FREE)) return -1; // close the connection too... (simplify resource management) if (bb_wq_push_close(bbs)) return -1; } // mark the session as persistent bbs->persistent = 1; // 30 seconds from now to finalize the handshake bb_session_reset_timer(bbs, 30, NULL); // now send the uwsgi pocket, to allow the dealer to close the session return 0; } // now we try to get the transport and session off_t i; char *transport = NULL; size_t transport_len = 0; for(i=13; i<url_len; i++) { if (url[i] == '/') { transport = url+13; transport_len = i-13; break; } } if (!transport) return -1; char *session_id = NULL; size_t session_id_len = 0; for(i=13+transport_len+1; i<url_len; i++) { if (url[i] == '/') { session_id = transport+transport_len+1; session_id_len = i-(13+transport_len+1); break; } } if (!session_id) { session_id = transport+transport_len+1; session_id_len = url_len - (13+transport_len+1); } if (session_id_len == 0 || session_id_len > 36) return -1; uuid_t sio_uuid; char tmp_uuid[37]; memcpy(tmp_uuid, session_id, session_id_len); tmp_uuid[36] = 0; if (uuid_parse(tmp_uuid, sio_uuid)) return -1; struct bb_session *persistent_bbs = bb_sht_get((char *)sio_uuid); if (!persistent_bbs) return -1; // destroy the session !!! // check for non-persisten-sessions or non-vhost matching too if (transport_len == 0 || !bb_strcmp(query_string, query_string_len, "?disconnect", 12) || !persistent_bbs->persistent || persistent_bbs->vhost != bbs->vhost) { persistent_bbs->persistent = 0; // DANGER !!! avoid destroying the current session too early if (persistent_bbs != bbs) { bb_session_close(persistent_bbs); } return -1; } // already the right session if (persistent_bbs == bbs) goto ready; // do not report 'end' message on end of session bbs->stealth = 1; ready: if (!bb_strcmp(transport, transport_len, "websocket", 9)) { // map the connection to allow sending from the persistent session // to the current connection struct bb_connection *bbc = bbs->connection; persistent_bbs->connection = bbc; bbs->connection->func = bb_websocket_func; // set the persistent session bbs->sio_session = persistent_bbs; bb_send_websocket_handshake(bbs); bb_websocket_reply(bbs, "1::", 3); persistent_bbs->sio_connected = 1; persistent_bbs->sio_realtime = 1; bb_session_reset_timer(persistent_bbs, 20, socketio_heartbeat); bbs->request.no_uwsgi = 1; return 0; } if (!bb_strcmp(transport, transport_len, "xhr-polling", 11)) { // sending messages does not require remapping the session if (!bb_strcmp(method, method_len, "POST",4)) { if (!persistent_bbs->sio_connected) return -1; if (bb_wq_push(bbs, (char *)post_headers, strlen(post_headers), 0)) return -1; //if (bb_wq_push_close(bbs)) return -1; if (bbs->request.parser.content_length != ULLONG_MAX && bbs->request.parser.content_length > 0) { bbs->sio_session = persistent_bbs; //bbs->sio_bbs = persistent_bbs; bbs->request.no_uwsgi = 1; // set socket.io hooks bbs->recv_body = bb_socketio_recv_body; bbs->recv_complete = bb_socketio_recv_complete; // do not report session death bbs->stealth = 1; return 0; } return -1; } // ok we are ready if (!bb_strcmp(method, method_len, "GET", 3)) { // already handshaked, this is a poll if (persistent_bbs->sio_connected) { // check if a poller is already running if (persistent_bbs->sio_poller) return -1; // ...and map the connection struct bb_connection *bbc = bbs->connection; persistent_bbs->connection = bbc; // do not forward the request to the dealer bbs->request.no_uwsgi = 1; struct bb_socketio_message *bbsm = persistent_bbs->sio_queue; if (bbsm) { if (bb_socketio_send(persistent_bbs, bbsm->buf, bbsm->len)) { fprintf(stderr,"unable to deliver message\n"); } persistent_bbs->sio_queue = bbsm->next; bb_free(bbsm, sizeof(struct bb_socketio_message)); return 0; } // start the poller persistent_bbs->sio_poller = 1; bb_session_reset_timer(persistent_bbs, 30.0, socketio_poller); return 0; } else { if (bb_wq_push(bbs, (char *)connected_headers, strlen(connected_headers), 0)) return -1; if (bb_wq_push_close(bbs)) return -1; persistent_bbs->sio_connected = 1; // start the sio_timer bb_session_reset_timer(persistent_bbs, 60.0, NULL); return 0; } } } return -1; }