void bb_zmq_receiver(struct ev_loop *loop, struct ev_io *w, int revents) { uint32_t zmq_events = 0; size_t opt_len = sizeof(uint32_t); for(;;) { int ret = zmq_getsockopt(blastbeat.router, ZMQ_EVENTS, &zmq_events, &opt_len); if (ret < 0) { perror("zmq_getsockopt()"); break; } if (zmq_events & ZMQ_POLLIN) { uint64_t more = 0; size_t more_size = sizeof(more); int headers = 0; int i; zmq_msg_t msg[4]; for(i=0;i<4;i++) { zmq_msg_init(&msg[i]); zmq_recv(blastbeat.router, &msg[i], ZMQ_NOBLOCK); if (zmq_getsockopt(blastbeat.router, ZMQ_RCVMORE, &more, &more_size)) { perror("zmq_getsockopt()"); break; } if (!more && i < 3) { break; } } // invalid message if (i != 4) goto next; // manage "pong" messages // message with uuid ? if (zmq_msg_size(&msg[1]) != BB_UUID_LEN) goto next; // dead/invalid session ? struct bb_session *bbs = bb_sht_get(zmq_msg_data(&msg[1])); if (!bbs) goto next; struct bb_session_request *bbsr = bbs->requests_tail; // no request running ? if (!bbsr) goto next; if (!strncmp(zmq_msg_data(&msg[2]), "body", zmq_msg_size(&msg[2]))) { if (bb_wq_push_copy(bbs,zmq_msg_data(&msg[3]), zmq_msg_size(&msg[3]), 1)) { bb_session_close(bbs); goto next; } bbsr->written_bytes += zmq_msg_size(&msg[2]); // if Content-Length is specified, check it... if (bbsr->content_length != ULLONG_MAX && bbsr->written_bytes >= bbsr->content_length && bbsr->close) { if (bb_wq_push_close(bbs)) bb_session_close(bbs); } goto next; } if (!strncmp(zmq_msg_data(&msg[2]), "websocket", zmq_msg_size(&msg[2]))) { if (bb_websocket_reply(bbsr, zmq_msg_data(&msg[3]), zmq_msg_size(&msg[3]))) bb_session_close(bbs); goto next; } if (!strncmp(zmq_msg_data(&msg[2]), "chunk", zmq_msg_size(&msg[2]))) { if (bb_manage_chunk(bbsr, zmq_msg_data(&msg[3]), zmq_msg_size(&msg[3]))) bb_session_close(bbs); goto next; } if (!strncmp(zmq_msg_data(&msg[2]), "headers", zmq_msg_size(&msg[2]))) { http_parser parser; http_parser_init(&parser, HTTP_RESPONSE); parser.data = bbsr; int res = http_parser_execute(&parser, &bb_http_response_parser_settings, zmq_msg_data(&msg[3]), zmq_msg_size(&msg[3])); // invalid headers ? if (res != zmq_msg_size(&msg[3])) { bb_session_close(bbs); goto next; } if (bb_wq_push_copy(bbs, zmq_msg_data(&msg[3]), zmq_msg_size(&msg[3]), 1)) bb_session_close(bbs); goto next; } if (!strncmp(zmq_msg_data(&msg[2]), "retry", zmq_msg_size(&msg[2]))) { if (bbs->hops >= blastbeat.max_hops) { bb_session_close(bbs); goto next; } bbs->dealer = bb_get_dealer(bbs->acceptor, bbs->dealer->vhost->name, bbs->dealer->vhost->len); if (!bbs->dealer) { bb_session_close(bbs); goto next; } bb_zmq_send_msg(bbs->dealer->identity, bbs->dealer->len, (char *) &bbs->uuid_part1, BB_UUID_LEN, "uwsgi", 5, bbsr->uwsgi_buf, bbsr->uwsgi_pos); bbs->hops++; goto next; } if (!strncmp(zmq_msg_data(&msg[2]), "end", zmq_msg_size(&msg[2]))) { if (bb_wq_push_close(bbs)) { bb_session_close(bbs); } goto next; } next: zmq_msg_close(&msg[0]); zmq_msg_close(&msg[1]); zmq_msg_close(&msg[2]); zmq_msg_close(&msg[3]); continue; } break; } }
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; }