/* * * websocket_write_back: write the string data to the destination wsi. */ int websocket_write_back(struct lws *wsi_in, char *str, int str_size_in) { // if (str == NULL || wsi_in == NULL){ // return -1; // } int n; int len; unsigned char *out = NULL; if (str_size_in < 1) len = strlen(str); else len = str_size_in; out = (unsigned char *)malloc(sizeof(unsigned char)*(LWS_SEND_BUFFER_PRE_PADDING + len + LWS_SEND_BUFFER_POST_PADDING)); //* setup the buffer*/ memcpy (out + LWS_SEND_BUFFER_PRE_PADDING, str, len ); //* write out*/ n = lws_write(wsi_in, out + LWS_SEND_BUFFER_PRE_PADDING, len, LWS_WRITE_TEXT); int client_sockfd=lws_get_socket_fd(wsi_in); printf("向%d,发数据:%s\n", client_sockfd, str); //printf(KBLU"[websocket_write_back] %s\n"RESET, str); //* free the buffer*/ free(out); return n; }
FWebSocket::FWebSocket(WebSocketInternalContext* InContext, WebSocketInternal* InWsi) : Context(InContext) , Wsi(InWsi) , Protocols(nullptr) , IsServerSide(true) { int sock = lws_get_socket_fd(Wsi); socklen_t len = sizeof RemoteAddr; getpeername(sock, (struct sockaddr*)&RemoteAddr, &len); }
FString FWebSocket::LocalEndPoint(bool bAppendPort) { #if !PLATFORM_HTML5 int sock = lws_get_socket_fd(Wsi); struct sockaddr_in addr; socklen_t len = sizeof addr; getsockname(sock, (struct sockaddr*)&addr, &len); // Windows XP does not have support for inet_ntop #if PLATFORM_WINDOWS && _WIN32_WINNT <= 0x0502 TCHAR Buffer[INET_ADDRSTRLEN]; ::DWORD BufferSize = INET_ADDRSTRLEN; FString remote = ""; if (!bAppendPort) { addr.sin_port = 0; } if (WSAAddressToString((sockaddr*)&addr, sizeof(addr), NULL, Buffer, &BufferSize) == 0) { remote = Buffer; } #else ANSICHAR Buffer[INET_ADDRSTRLEN]; FString remote(ANSI_TO_TCHAR(inet_ntop(AF_INET, &addr.sin_addr, Buffer, INET_ADDRSTRLEN))); if ( bAppendPort ) { remote += FString::Printf(TEXT(":%i"), ntohs(addr.sin_port)); } #endif return remote; #else // NOTE: there's no way to get this info from browsers... // return generic localhost without port # return FString(TEXT("127.0.0.1")); #endif }
int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__http *pss = (struct per_session_data__http *)user; unsigned char buffer[4096 + LWS_PRE]; unsigned long amount, file_len, sent; char leaf_path[1024]; const char *mimetype; char *other_headers; unsigned char *end; struct timeval tv; unsigned char *p; char buf[256]; char b64[64]; int n, m; #ifdef EXTERNAL_POLL struct lws_pollargs *pa = (struct lws_pollargs *)in; #endif switch (reason) { case LWS_CALLBACK_HTTP: if (debug_level & LLL_INFO) { dump_handshake_info(wsi); /* dump the individual URI Arg parameters */ n = 0; while (lws_hdr_copy_fragment(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_URI_ARGS, n) > 0) { lwsl_notice("URI Arg %d: %s\n", ++n, buf); } } { char name[100], rip[50]; lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, sizeof(name), rip, sizeof(rip)); sprintf(buf, "%s (%s)", name, rip); lwsl_notice("HTTP connect from %s\n", buf); } if (len < 1) { lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL); goto try_to_reuse; } /* this example server has no concept of directories */ if (strchr((const char *)in + 1, '/')) { lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL); goto try_to_reuse; } /* if a legal POST URL, let it continue and accept data */ if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) return 0; /* check for the "send a big file by hand" example case */ if (!strcmp((const char *)in, "/leaf.jpg")) { if (strlen(resource_path) > sizeof(leaf_path) - 10) return -1; sprintf(leaf_path, "%s/leaf.jpg", resource_path); /* well, let's demonstrate how to send the hard way */ p = buffer + LWS_PRE; end = p + sizeof(buffer) - LWS_PRE; pss->fd = lws_plat_file_open(wsi, leaf_path, &file_len, LWS_O_RDONLY); if (pss->fd == LWS_INVALID_FILE) { lwsl_err("faild to open file %s\n", leaf_path); return -1; } /* * we will send a big jpeg file, but it could be * anything. Set the Content-Type: appropriately * so the browser knows what to do with it. * * Notice we use the APIs to build the header, which * will do the right thing for HTTP 1/1.1 and HTTP2 * depending on what connection it happens to be working * on */ if (lws_add_http_header_status(wsi, 200, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"image/jpeg", 10, &p, end)) return 1; if (lws_add_http_header_content_length(wsi, file_len, &p, end)) return 1; if (lws_finalize_http_header(wsi, &p, end)) return 1; /* * send the http headers... * this won't block since it's the first payload sent * on the connection since it was established * (too small for partial) * * Notice they are sent using LWS_WRITE_HTTP_HEADERS * which also means you can't send body too in one step, * this is mandated by changes in HTTP2 */ *p = '\0'; lwsl_info("%s\n", buffer + LWS_PRE); n = lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS); if (n < 0) { lws_plat_file_close(wsi, pss->fd); return -1; } /* * book us a LWS_CALLBACK_HTTP_WRITEABLE callback */ lws_callback_on_writable(wsi); break; } /* if not, send a file the easy way */ strcpy(buf, resource_path); if (strcmp(in, "/")) { if (*((const char *)in) != '/') strcat(buf, "/"); strncat(buf, in, sizeof(buf) - strlen(resource_path)); } else /* default file to serve */ strcat(buf, "/test.html"); buf[sizeof(buf) - 1] = '\0'; /* refuse to serve files we don't understand */ mimetype = get_mimetype(buf); if (!mimetype) { lwsl_err("Unknown mimetype for %s\n", buf); lws_return_http_status(wsi, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL); return -1; } /* demonstrates how to set a cookie on / */ other_headers = leaf_path; p = (unsigned char *)leaf_path; if (!strcmp((const char *)in, "/") && !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) { /* this isn't very unguessable but it'll do for us */ gettimeofday(&tv, NULL); n = sprintf(b64, "test=LWS_%u_%u_COOKIE;Max-Age=360000", (unsigned int)tv.tv_sec, (unsigned int)tv.tv_usec); if (lws_add_http_header_by_name(wsi, (unsigned char *)"set-cookie:", (unsigned char *)b64, n, &p, (unsigned char *)leaf_path + sizeof(leaf_path))) return 1; } if (lws_is_ssl(wsi) && lws_add_http_header_by_name(wsi, (unsigned char *) "Strict-Transport-Security:", (unsigned char *) "max-age=15768000 ; " "includeSubDomains", 36, &p, (unsigned char *)leaf_path + sizeof(leaf_path))) return 1; n = (char *)p - leaf_path; n = lws_serve_http_file(wsi, buf, mimetype, other_headers, n); if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi))) return -1; /* error or can't reuse connection: close the socket */ /* * notice that the sending of the file completes asynchronously, * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when * it's done */ break; case LWS_CALLBACK_HTTP_BODY: strncpy(buf, in, 20); buf[20] = '\0'; if (len < 20) buf[len] = '\0'; lwsl_notice("LWS_CALLBACK_HTTP_BODY: %s... len %d\n", (const char *)buf, (int)len); break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); /* the whole of the sent body arrived, close or reuse the connection */ lws_return_http_status(wsi, HTTP_STATUS_OK, NULL); goto try_to_reuse; case LWS_CALLBACK_HTTP_FILE_COMPLETION: goto try_to_reuse; case LWS_CALLBACK_HTTP_WRITEABLE: lwsl_info("LWS_CALLBACK_HTTP_WRITEABLE\n"); if (pss->fd == LWS_INVALID_FILE) goto try_to_reuse; /* * we can send more of whatever it is we were sending */ sent = 0; do { /* we'd like the send this much */ n = sizeof(buffer) - LWS_PRE; /* but if the peer told us he wants less, we can adapt */ m = lws_get_peer_write_allowance(wsi); /* -1 means not using a protocol that has this info */ if (m == 0) /* right now, peer can't handle anything */ goto later; if (m != -1 && m < n) /* he couldn't handle that much */ n = m; n = lws_plat_file_read(wsi, pss->fd, &amount, buffer + LWS_PRE, n); /* problem reading, close conn */ if (n < 0) { lwsl_err("problem reading file\n"); goto bail; } n = (int)amount; /* sent it all, close conn */ if (n == 0) goto penultimate; /* * To support HTTP2, must take care about preamble space * * identification of when we send the last payload frame * is handled by the library itself if you sent a * content-length header */ m = lws_write(wsi, buffer + LWS_PRE, n, LWS_WRITE_HTTP); if (m < 0) { lwsl_err("write failed\n"); /* write failed, close conn */ goto bail; } if (m) /* while still active, extend timeout */ lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 5); sent += m; } while (!lws_send_pipe_choked(wsi) && (sent < 1024 * 1024)); later: lws_callback_on_writable(wsi); break; penultimate: lws_plat_file_close(wsi, pss->fd); pss->fd = LWS_INVALID_FILE; goto try_to_reuse; bail: lws_plat_file_close(wsi, pss->fd); return -1; /* * callback for confirming to continue with client IP appear in * protocol 0 callback since no websocket protocol has been agreed * yet. You can just ignore this if you won't filter on client IP * since the default uhandled callback return is 0 meaning let the * connection continue. */ case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: /* if we returned non-zero from here, we kill the connection */ break; /* * callbacks for managing the external poll() array appear in * protocol 0 callback */ case LWS_CALLBACK_LOCK_POLL: /* * lock mutex to protect pollfd state * called before any other POLL related callback * if protecting wsi lifecycle change, len == 1 */ test_server_lock(len); break; case LWS_CALLBACK_UNLOCK_POLL: /* * unlock mutex to protect pollfd state when * called after any other POLL related callback * if protecting wsi lifecycle change, len == 1 */ test_server_unlock(len); break; #ifdef EXTERNAL_POLL case LWS_CALLBACK_ADD_POLL_FD: if (count_pollfds >= max_poll_elements) { lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n"); return 1; } fd_lookup[pa->fd] = count_pollfds; pollfds[count_pollfds].fd = pa->fd; pollfds[count_pollfds].events = pa->events; pollfds[count_pollfds++].revents = 0; break; case LWS_CALLBACK_DEL_POLL_FD: if (!--count_pollfds) break; m = fd_lookup[pa->fd]; /* have the last guy take up the vacant slot */ pollfds[m] = pollfds[count_pollfds]; fd_lookup[pollfds[count_pollfds].fd] = m; break; case LWS_CALLBACK_CHANGE_MODE_POLL_FD: pollfds[fd_lookup[pa->fd]].events = pa->events; break; #endif case LWS_CALLBACK_GET_THREAD_ID: /* * if you will call "lws_callback_on_writable" * from a different thread, return the caller thread ID * here so lws can use this information to work out if it * should signal the poll() loop to exit and restart early */ /* return pthread_getthreadid_np(); */ break; default: break; } return 0; /* if we're on HTTP1.1 or 2.0, will keep the idle connection alive */ try_to_reuse: if (lws_http_transaction_completed(wsi)) return -1; return 0; }
LWS_VISIBLE int lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct lws_ssl_info *si; #ifdef LWS_WITH_CGI struct lws_cgi_args *args; #endif #if defined(LWS_WITH_CGI) || defined(LWS_WITH_HTTP_PROXY) char buf[8192]; int n; #endif #if defined(LWS_WITH_HTTP_PROXY) unsigned char **p, *end; struct lws *parent; #endif switch (reason) { #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) case LWS_CALLBACK_HTTP: #ifndef LWS_NO_SERVER if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL)) return -1; if (lws_http_transaction_completed(wsi)) #endif return -1; break; #if !defined(LWS_NO_SERVER) case LWS_CALLBACK_HTTP_BODY_COMPLETION: case LWS_CALLBACK_HTTP_FILE_COMPLETION: if (lws_http_transaction_completed(wsi)) return -1; break; #endif case LWS_CALLBACK_HTTP_WRITEABLE: #ifdef LWS_WITH_CGI if (wsi->reason_bf & (LWS_CB_REASON_AUX_BF__CGI_HEADERS | LWS_CB_REASON_AUX_BF__CGI)) { n = lws_cgi_write_split_stdout_headers(wsi); if (n < 0) { lwsl_debug("AUX_BF__CGI forcing close\n"); return -1; } if (!n) lws_rx_flow_control( wsi->http.cgi->stdwsi[LWS_STDOUT], 1); if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_HEADERS) wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__CGI_HEADERS; else wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__CGI; if (wsi->http.cgi && wsi->http.cgi->cgi_transaction_over) return -1; break; } if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_CHUNK_END) { if (!wsi->http2_substream) { memcpy(buf + LWS_PRE, "0\x0d\x0a\x0d\x0a", 5); lwsl_debug("writing chunk term and exiting\n"); n = lws_write(wsi, (unsigned char *)buf + LWS_PRE, 5, LWS_WRITE_HTTP); } else n = lws_write(wsi, (unsigned char *)buf + LWS_PRE, 0, LWS_WRITE_HTTP_FINAL); /* always close after sending it */ return -1; } #endif #if defined(LWS_WITH_HTTP_PROXY) if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY_HEADERS) { wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY_HEADERS; lwsl_debug("%s: %p: issuing proxy headers\n", __func__, wsi); n = lws_write(wsi, wsi->http.pending_return_headers + LWS_PRE, wsi->http.pending_return_headers_len, LWS_WRITE_HTTP_HEADERS); lws_free_set_NULL(wsi->http.pending_return_headers); if (n < 0) { lwsl_err("%s: EST_CLIENT_HTTP: write failed\n", __func__); return -1; } lws_callback_on_writable(wsi); break; } if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY) { char *px = buf + LWS_PRE; int lenx = sizeof(buf) - LWS_PRE - 32; /* * our sink is writeable and our source has something * to read. So read a lump of source material of * suitable size to send or what's available, whichever * is the smaller. */ wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY; if (!lws_get_child(wsi)) break; /* this causes LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ */ if (lws_http_client_read(lws_get_child(wsi), &px, &lenx) < 0) { lwsl_info("%s: LWS_CB_REASON_AUX_BF__PROXY: " "client closed\n", __func__); stream_close(wsi); return -1; } break; } if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY_TRANS_END) { lwsl_info("%s: LWS_CB_REASON_AUX_BF__PROXY_TRANS_END\n", __func__); wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY_TRANS_END; if (stream_close(wsi)) return -1; if (lws_http_transaction_completed(wsi)) return -1; } #endif break; #if defined(LWS_WITH_HTTP_PROXY) case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: assert(lws_get_parent(wsi)); if (!lws_get_parent(wsi)) break; lws_get_parent(wsi)->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY; lws_callback_on_writable(lws_get_parent(wsi)); break; case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: { char *out = buf + LWS_PRE; assert(lws_get_parent(wsi)); if (wsi->http.proxy_parent_chunked) { if (len > sizeof(buf) - LWS_PRE - 16) { lwsl_err("oversize buf %d %d\n", (int)len, (int)sizeof(buf) - LWS_PRE - 16); return -1; } /* * this only needs dealing with on http/1.1 to allow * pipelining */ n = lws_snprintf(out, 14, "%X\x0d\x0a", (int)len); out += n; memcpy(out, in, len); out += len; *out++ = '\x0d'; *out++ = '\x0a'; n = lws_write(lws_get_parent(wsi), (unsigned char *)buf + LWS_PRE, len + n + 2, LWS_WRITE_HTTP); } else n = lws_write(lws_get_parent(wsi), (unsigned char *)in, len, LWS_WRITE_HTTP); if (n < 0) return -1; break; } /* this handles the proxy case... */ case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { unsigned char *start, *p, *end; /* * We want to proxy these headers, but we are being called * at the point the onward client was established, which is * unrelated to the state or writability of our proxy * connection. * * Therefore produce the headers using the onward client ah * while we have it, and stick them on the output buflist to be * written on the proxy connection as soon as convenient. */ parent = lws_get_parent(wsi); if (!parent) return 0; start = p = (unsigned char *)buf + LWS_PRE; end = p + sizeof(buf) - LWS_PRE - 256; if (lws_add_http_header_status(lws_get_parent(wsi), lws_http_client_http_response(wsi), &p, end)) return 1; /* * copy these headers from the client connection to the parent */ proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_CONTENT_LENGTH, &p, end); proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_CONTENT_TYPE, &p, end); proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_ETAG, &p, end); proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, &p, end); proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_CONTENT_ENCODING, &p, end); proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_CACHE_CONTROL, &p, end); if (!parent->http2_substream) if (lws_add_http_header_by_token(parent, WSI_TOKEN_CONNECTION, (unsigned char *)"close", 5, &p, end)) return -1; /* * We proxy using h1 only atm, and strip any chunking so it * can go back out on h2 just fine. * * However if we are actually going out on h1, we need to add * our own chunking since we still don't know the size. */ if (!parent->http2_substream && !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { lwsl_debug("downstream parent chunked\n"); if (lws_add_http_header_by_token(parent, WSI_TOKEN_HTTP_TRANSFER_ENCODING, (unsigned char *)"chunked", 7, &p, end)) return -1; wsi->http.proxy_parent_chunked = 1; } if (lws_finalize_http_header(parent, &p, end)) return 1; parent->http.pending_return_headers_len = lws_ptr_diff(p, start); parent->http.pending_return_headers = lws_malloc(parent->http.pending_return_headers_len + LWS_PRE, "return proxy headers"); if (!parent->http.pending_return_headers) return -1; memcpy(parent->http.pending_return_headers + LWS_PRE, start, parent->http.pending_return_headers_len); parent->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY_HEADERS; lwsl_debug("%s: LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: " "prepared headers\n", __func__); lws_callback_on_writable(parent); break; } case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: lwsl_info("%s: COMPLETED_CLIENT_HTTP: %p (parent %p)\n", __func__, wsi, lws_get_parent(wsi)); if (!lws_get_parent(wsi)) break; lws_get_parent(wsi)->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY_TRANS_END; lws_callback_on_writable(lws_get_parent(wsi)); break; case LWS_CALLBACK_CLOSED_CLIENT_HTTP: if (!lws_get_parent(wsi)) break; lwsl_err("%s: LWS_CALLBACK_CLOSED_CLIENT_HTTP\n", __func__); lws_set_timeout(lws_get_parent(wsi), LWS_TO_KILL_ASYNC, PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE); break; case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: parent = lws_get_parent(wsi); if (!parent) break; p = (unsigned char **)in; end = (*p) + len; /* * copy these headers from the parent request to the client * connection's request */ proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HOST, p, end); proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HTTP_ETAG, p, end); proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HTTP_IF_MODIFIED_SINCE, p, end); proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, p, end); proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HTTP_ACCEPT_ENCODING, p, end); proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HTTP_CACHE_CONTROL, p, end); buf[0] = '\0'; lws_get_peer_simple(parent, buf, sizeof(buf)); if (lws_add_http_header_by_token(wsi, WSI_TOKEN_X_FORWARDED_FOR, (unsigned char *)buf, (int)strlen(buf), p, end)) return -1; break; #endif #ifdef LWS_WITH_CGI /* CGI IO events (POLLIN/OUT) appear here, our default policy is: * * - POST data goes on subprocess stdin * - subprocess stdout goes on http via writeable callback * - subprocess stderr goes to the logs */ case LWS_CALLBACK_CGI: args = (struct lws_cgi_args *)in; switch (args->ch) { /* which of stdin/out/err ? */ case LWS_STDIN: /* TBD stdin rx flow control */ break; case LWS_STDOUT: /* quench POLLIN on STDOUT until MASTER got writeable */ lws_rx_flow_control(args->stdwsi[LWS_STDOUT], 0); wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI; /* when writing to MASTER would not block */ lws_callback_on_writable(wsi); break; case LWS_STDERR: n = lws_get_socket_fd(args->stdwsi[LWS_STDERR]); if (n < 0) break; n = read(n, buf, sizeof(buf) - 2); if (n > 0) { if (buf[n - 1] != '\n') buf[n++] = '\n'; buf[n] = '\0'; lwsl_notice("CGI-stderr: %s\n", buf); } break; } break; case LWS_CALLBACK_CGI_TERMINATED: lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: %d %" PRIu64 "\n", wsi->http.cgi->explicitly_chunked, (uint64_t)wsi->http.cgi->content_length); if (!wsi->http.cgi->explicitly_chunked && !wsi->http.cgi->content_length) { /* send terminating chunk */ lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: ending\n"); wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_CHUNK_END; lws_callback_on_writable(wsi); lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, 3); break; } return -1; case LWS_CALLBACK_CGI_STDIN_DATA: /* POST body for stdin */ args = (struct lws_cgi_args *)in; args->data[args->len] = '\0'; if (!args->stdwsi[LWS_STDIN]) return -1; n = lws_get_socket_fd(args->stdwsi[LWS_STDIN]); if (n < 0) return -1; #if defined(LWS_WITH_ZLIB) if (wsi->http.cgi->gzip_inflate) { /* gzip handling */ if (!wsi->http.cgi->gzip_init) { lwsl_info("inflating gzip\n"); memset(&wsi->http.cgi->inflate, 0, sizeof(wsi->http.cgi->inflate)); if (inflateInit2(&wsi->http.cgi->inflate, 16 + 15) != Z_OK) { lwsl_err("%s: iniflateInit failed\n", __func__); return -1; } wsi->http.cgi->gzip_init = 1; } wsi->http.cgi->inflate.next_in = args->data; wsi->http.cgi->inflate.avail_in = args->len; do { wsi->http.cgi->inflate.next_out = wsi->http.cgi->inflate_buf; wsi->http.cgi->inflate.avail_out = sizeof(wsi->http.cgi->inflate_buf); n = inflate(&wsi->http.cgi->inflate, Z_SYNC_FLUSH); switch (n) { case Z_NEED_DICT: case Z_STREAM_ERROR: case Z_DATA_ERROR: case Z_MEM_ERROR: inflateEnd(&wsi->http.cgi->inflate); wsi->http.cgi->gzip_init = 0; lwsl_err("zlib error inflate %d\n", n); return -1; } if (wsi->http.cgi->inflate.avail_out != sizeof(wsi->http.cgi->inflate_buf)) { int written; written = write(args->stdwsi[LWS_STDIN]->desc.filefd, wsi->http.cgi->inflate_buf, sizeof(wsi->http.cgi->inflate_buf) - wsi->http.cgi->inflate.avail_out); if (written != (int)( sizeof(wsi->http.cgi->inflate_buf) - wsi->http.cgi->inflate.avail_out)) { lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: " "sent %d only %d went", n, args->len); } if (n == Z_STREAM_END) { lwsl_err("gzip inflate end\n"); inflateEnd(&wsi->http.cgi->inflate); wsi->http.cgi->gzip_init = 0; break; } } else break; if (wsi->http.cgi->inflate.avail_out) break; } while (1); return args->len; } #endif /* WITH_ZLIB */ n = write(n, args->data, args->len); // lwsl_hexdump_notice(args->data, args->len); if (n < args->len) lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: " "sent %d only %d went", n, args->len); if (wsi->http.cgi->post_in_expected && args->stdwsi[LWS_STDIN] && args->stdwsi[LWS_STDIN]->desc.filefd > 0) { wsi->http.cgi->post_in_expected -= n; if (!wsi->http.cgi->post_in_expected) { struct lws *siwsi = args->stdwsi[LWS_STDIN]; lwsl_debug("%s: expected POST in end: " "closing stdin wsi %p, fd %d\n", __func__, siwsi, siwsi->desc.sockfd); __remove_wsi_socket_from_fds(siwsi); lwsi_set_state(siwsi, LRS_DEAD_SOCKET); siwsi->socket_is_permanently_unusable = 1; lws_remove_child_from_any_parent(siwsi); if (wsi->context->event_loop_ops-> close_handle_manually) { wsi->context->event_loop_ops-> close_handle_manually(siwsi); siwsi->told_event_loop_closed = 1; } else { compatible_close(siwsi->desc.sockfd); __lws_free_wsi(siwsi); } wsi->http.cgi->pipe_fds[LWS_STDIN][1] = -1; args->stdwsi[LWS_STDIN] = NULL; } } return n; #endif /* WITH_CGI */ #endif /* ROLE_ H1 / H2 */ case LWS_CALLBACK_SSL_INFO: si = in; (void)si; lwsl_notice("LWS_CALLBACK_SSL_INFO: where: 0x%x, ret: 0x%x\n", si->where, si->ret); break; #if LWS_MAX_SMP > 1 case LWS_CALLBACK_GET_THREAD_ID: return (int)(unsigned long long)pthread_self(); #endif default: break; } return 0; }
int janus_websockets_send_message(void *transport, void *request_id, gboolean admin, json_t *message) { if(message == NULL) return -1; if(transport == NULL) { json_decref(message); return -1; } /* Make sure this is not related to a closed /freed WebSocket session */ janus_mutex_lock(&old_wss_mutex); janus_websockets_client *client = (janus_websockets_client *)transport; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(g_list_find(old_wss, client) != NULL || !client->wsi) { #else if(g_list_find(old_wss, client) != NULL || !client->context || !client->wsi) { #endif json_decref(message); message = NULL; transport = NULL; janus_mutex_unlock(&old_wss_mutex); return -1; } janus_mutex_lock(&client->mutex); /* Convert to string and enqueue */ char *payload = json_dumps(message, json_format); g_async_queue_push(client->messages, payload); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(client->wsi); #else libwebsocket_callback_on_writable(client->context, client->wsi); #endif janus_mutex_unlock(&client->mutex); janus_mutex_unlock(&old_wss_mutex); json_decref(message); return 0; } void janus_websockets_session_created(void *transport, guint64 session_id) { /* We don't care */ } void janus_websockets_session_over(void *transport, guint64 session_id, gboolean timeout) { if(transport == NULL || !timeout) return; /* We only care if it's a timeout: if so, close the connection */ janus_websockets_client *client = (janus_websockets_client *)transport; /* Make sure this is not related to a closed WebSocket session */ janus_mutex_lock(&old_wss_mutex); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(g_list_find(old_wss, client) == NULL && client->wsi){ #else if(g_list_find(old_wss, client) == NULL && client->context && client->wsi){ #endif janus_mutex_lock(&client->mutex); client->session_timeout = 1; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(client->wsi); #else libwebsocket_callback_on_writable(client->context, client->wsi); #endif janus_mutex_unlock(&client->mutex); } janus_mutex_unlock(&old_wss_mutex); } /* Thread */ void *janus_websockets_thread(void *data) { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws_context *service = (struct lws_context *)data; #else struct libwebsocket_context *service = (struct libwebsocket_context *)data; #endif if(service == NULL) { JANUS_LOG(LOG_ERR, "Invalid service\n"); return NULL; } const char *type = NULL; if(service == wss) type = "WebSocket (Janus API)"; else if(service == swss) type = "Secure WebSocket (Janus API)"; else if(service == admin_wss) type = "WebSocket (Admin API)"; else if(service == admin_swss) type = "Secure WebSocket (Admin API)"; JANUS_LOG(LOG_INFO, "%s thread started\n", type); while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) { /* libwebsockets is single thread, we cycle through events here */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_service(service, 50); #else libwebsocket_service(service, 50); #endif } /* Get rid of the WebSockets server */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_cancel_service(service); #else libwebsocket_cancel_service(service); #endif /* Done */ JANUS_LOG(LOG_INFO, "%s thread ended\n", type); return NULL; } /* WebSockets */ static int janus_websockets_callback_http( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { /* This endpoint cannot be used for HTTP */ switch(reason) { case LWS_CALLBACK_HTTP: JANUS_LOG(LOG_VERB, "Rejecting incoming HTTP request on WebSockets endpoint\n"); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_return_http_status(wsi, 403, NULL); #else libwebsockets_return_http_status(this, wsi, 403, NULL); #endif /* Close and free connection */ return -1; case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: if (!in) { JANUS_LOG(LOG_VERB, "Rejecting incoming HTTP request on WebSockets endpoint: no sub-protocol specified\n"); return -1; } break; default: break; } return 0; } static int janus_websockets_callback_https( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { /* We just forward the event to the HTTP handler */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_callback_http(wsi, reason, user, in, len); #else return janus_websockets_callback_http(this, wsi, reason, user, in, len); #endif } /* This callback handles Janus API requests */ static int janus_websockets_common_callback( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len, gboolean admin) { const char *log_prefix = admin ? "AdminWSS" : "WSS"; janus_websockets_client *ws_client = (janus_websockets_client *)user; switch(reason) { case LWS_CALLBACK_ESTABLISHED: { /* Is there any filtering we should apply? */ char name[256], ip[256]; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, 256, ip, 256); #else libwebsockets_get_peer_addresses(this, wsi, libwebsocket_get_socket_fd(wsi), name, 256, ip, 256); #endif JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection opened from %s by %s\n", log_prefix, wsi, ip, name); if(!janus_websockets_is_allowed(ip, admin)) { JANUS_LOG(LOG_ERR, "[%s-%p] IP %s is unauthorized to connect to the WebSockets %s API interface\n", log_prefix, wsi, ip, admin ? "Admin" : "Janus"); /* Close the connection */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(wsi); #else libwebsocket_callback_on_writable(this, wsi); #endif return -1; } JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection accepted\n", log_prefix, wsi); if(ws_client == NULL) { JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } /* Clean the old sessions list, in case this pointer was used before */ janus_mutex_lock(&old_wss_mutex); if(g_list_find(old_wss, ws_client) != NULL) old_wss = g_list_remove(old_wss, ws_client); janus_mutex_unlock(&old_wss_mutex); /* Prepare the session */ #ifndef HAVE_LIBWEBSOCKETS_NEWAPI ws_client->context = this; #endif ws_client->wsi = wsi; ws_client->messages = g_async_queue_new(); ws_client->buffer = NULL; ws_client->buflen = 0; ws_client->bufpending = 0; ws_client->bufoffset = 0; ws_client->session_timeout = 0; ws_client->destroy = 0; janus_mutex_init(&ws_client->mutex); /* Let us know when the WebSocket channel becomes writeable */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(wsi); #else libwebsocket_callback_on_writable(this, wsi); #endif JANUS_LOG(LOG_VERB, "[%s-%p] -- Ready to be used!\n", log_prefix, wsi); /* Notify handlers about this new transport */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "event", json_string("connected")); json_object_set_new(info, "admin_api", admin ? json_true() : json_false()); json_object_set_new(info, "ip", json_string(ip)); gateway->notify_event(&janus_websockets_transport, ws_client, info); } return 0; } case LWS_CALLBACK_RECEIVE: { JANUS_LOG(LOG_HUGE, "[%s-%p] Got %zu bytes:\n", log_prefix, wsi, len); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(ws_client == NULL || ws_client->wsi == NULL) { #else if(ws_client == NULL || ws_client->context == NULL || ws_client->wsi == NULL) { #endif JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } /* Is this a new message, or part of a fragmented one? */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI const size_t remaining = lws_remaining_packet_payload(wsi); #else const size_t remaining = libwebsockets_remaining_packet_payload(wsi); #endif if(ws_client->incoming == NULL) { JANUS_LOG(LOG_HUGE, "[%s-%p] First fragment: %zu bytes, %zu remaining\n", log_prefix, wsi, len, remaining); ws_client->incoming = g_malloc0(len+1); memcpy(ws_client->incoming, in, len); ws_client->incoming[len] = '\0'; JANUS_LOG(LOG_HUGE, "%s\n", ws_client->incoming); } else { size_t offset = strlen(ws_client->incoming); JANUS_LOG(LOG_HUGE, "[%s-%p] Appending fragment: offset %zu, %zu bytes, %zu remaining\n", log_prefix, wsi, offset, len, remaining); ws_client->incoming = g_realloc(ws_client->incoming, offset+len+1); memcpy(ws_client->incoming+offset, in, len); ws_client->incoming[offset+len] = '\0'; JANUS_LOG(LOG_HUGE, "%s\n", ws_client->incoming+offset); } #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(remaining > 0 || !lws_is_final_fragment(wsi)) { #else if(remaining > 0 || !libwebsocket_is_final_fragment(wsi)) { #endif /* Still waiting for some more fragments */ JANUS_LOG(LOG_HUGE, "[%s-%p] Waiting for more fragments\n", log_prefix, wsi); return 0; } JANUS_LOG(LOG_HUGE, "[%s-%p] Done, parsing message: %zu bytes\n", log_prefix, wsi, strlen(ws_client->incoming)); /* If we got here, the message is complete: parse the JSON payload */ json_error_t error; json_t *root = json_loads(ws_client->incoming, 0, &error); g_free(ws_client->incoming); ws_client->incoming = NULL; /* Notify the core, passing both the object and, since it may be needed, the error */ gateway->incoming_request(&janus_websockets_transport, ws_client, NULL, admin, root, &error); return 0; } case LWS_CALLBACK_SERVER_WRITEABLE: { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(ws_client == NULL || ws_client->wsi == NULL) { #else if(ws_client == NULL || ws_client->context == NULL || ws_client->wsi == NULL) { #endif JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } if(!ws_client->destroy && !g_atomic_int_get(&stopping)) { janus_mutex_lock(&ws_client->mutex); /* Check if we have a pending/partial write to complete first */ if(ws_client->buffer && ws_client->bufpending > 0 && ws_client->bufoffset > 0 && !ws_client->destroy && !g_atomic_int_get(&stopping)) { JANUS_LOG(LOG_HUGE, "[%s-%p] Completing pending WebSocket write (still need to write last %d bytes)...\n", log_prefix, wsi, ws_client->bufpending); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI int sent = lws_write(wsi, ws_client->buffer + ws_client->bufoffset, ws_client->bufpending, LWS_WRITE_TEXT); #else int sent = libwebsocket_write(wsi, ws_client->buffer + ws_client->bufoffset, ws_client->bufpending, LWS_WRITE_TEXT); #endif JANUS_LOG(LOG_HUGE, "[%s-%p] -- Sent %d/%d bytes\n", log_prefix, wsi, sent, ws_client->bufpending); if(sent > -1 && sent < ws_client->bufpending) { /* We still couldn't send everything that was left, we'll try and complete this in the next round */ ws_client->bufpending -= sent; ws_client->bufoffset += sent; } else { /* Clear the pending/partial write queue */ ws_client->bufpending = 0; ws_client->bufoffset = 0; } /* Done for this round, check the next response/notification later */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(wsi); #else libwebsocket_callback_on_writable(this, wsi); #endif janus_mutex_unlock(&ws_client->mutex); return 0; } /* Shoot all the pending messages */ char *response = g_async_queue_try_pop(ws_client->messages); if(response && !ws_client->destroy && !g_atomic_int_get(&stopping)) { /* Gotcha! */ int buflen = LWS_SEND_BUFFER_PRE_PADDING + strlen(response) + LWS_SEND_BUFFER_POST_PADDING; if(ws_client->buffer == NULL) { /* Let's allocate a shared buffer */ JANUS_LOG(LOG_HUGE, "[%s-%p] Allocating %d bytes (response is %zu bytes)\n", log_prefix, wsi, buflen, strlen(response)); ws_client->buflen = buflen; ws_client->buffer = g_malloc0(buflen); } else if(buflen > ws_client->buflen) { /* We need a larger shared buffer */ JANUS_LOG(LOG_HUGE, "[%s-%p] Re-allocating to %d bytes (was %d, response is %zu bytes)\n", log_prefix, wsi, buflen, ws_client->buflen, strlen(response)); ws_client->buflen = buflen; ws_client->buffer = g_realloc(ws_client->buffer, buflen); } memcpy(ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, response, strlen(response)); JANUS_LOG(LOG_HUGE, "[%s-%p] Sending WebSocket message (%zu bytes)...\n", log_prefix, wsi, strlen(response)); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI int sent = lws_write(wsi, ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, strlen(response), LWS_WRITE_TEXT); #else int sent = libwebsocket_write(wsi, ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, strlen(response), LWS_WRITE_TEXT); #endif JANUS_LOG(LOG_HUGE, "[%s-%p] -- Sent %d/%zu bytes\n", log_prefix, wsi, sent, strlen(response)); if(sent > -1 && sent < (int)strlen(response)) { /* We couldn't send everything in a single write, we'll complete this in the next round */ ws_client->bufpending = strlen(response) - sent; ws_client->bufoffset = LWS_SEND_BUFFER_PRE_PADDING + sent; JANUS_LOG(LOG_HUGE, "[%s-%p] -- Couldn't write all bytes (%d missing), setting offset %d\n", log_prefix, wsi, ws_client->bufpending, ws_client->bufoffset); } /* We can get rid of the message */ free(response); /* Done for this round, check the next response/notification later */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(wsi); #else libwebsocket_callback_on_writable(this, wsi); #endif janus_mutex_unlock(&ws_client->mutex); return 0; } janus_mutex_unlock(&ws_client->mutex); } return 0; } case LWS_CALLBACK_CLOSED: { JANUS_LOG(LOG_VERB, "[%s-%p] WS connection down, closing\n", log_prefix, wsi); janus_websockets_destroy_client(ws_client, wsi, log_prefix); JANUS_LOG(LOG_VERB, "[%s-%p] -- closed\n", log_prefix, wsi); return 0; } case LWS_CALLBACK_WSI_DESTROY: { JANUS_LOG(LOG_VERB, "[%s-%p] WS connection down, destroying\n", log_prefix, wsi); janus_websockets_destroy_client(ws_client, wsi, log_prefix); JANUS_LOG(LOG_VERB, "[%s-%p] -- destroyed\n", log_prefix, wsi); return 0; } default: if(wsi != NULL) { JANUS_LOG(LOG_HUGE, "[%s-%p] %d (%s)\n", log_prefix, wsi, reason, janus_websockets_reason_string(reason)); } else { JANUS_LOG(LOG_HUGE, "[%s] %d (%s)\n", log_prefix, reason, janus_websockets_reason_string(reason)); } break; } return 0; } /* This callback handles Janus API requests */ static int janus_websockets_callback( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_common_callback(wsi, reason, user, in, len, FALSE); #else return janus_websockets_common_callback(this, wsi, reason, user, in, len, FALSE); #endif } static int janus_websockets_callback_secure( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { /* We just forward the event to the Janus API handler */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_callback(wsi, reason, user, in, len); #else return janus_websockets_callback(this, wsi, reason, user, in, len); #endif } /* This callback handles Admin API requests */ static int janus_websockets_admin_callback( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_common_callback(wsi, reason, user, in, len, TRUE); #else return janus_websockets_common_callback(this, wsi, reason, user, in, len, TRUE); #endif } static int janus_websockets_admin_callback_secure( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { /* We just forward the event to the Admin API handler */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_admin_callback(wsi, reason, user, in, len); #else return janus_websockets_admin_callback(this, wsi, reason, user, in, len); #endif }
/* This callback handles Janus API requests */ static int janus_websockets_common_callback( struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len, gboolean admin) { const char *log_prefix = admin ? "AdminWSS" : "WSS"; janus_websockets_client *ws_client = (janus_websockets_client *)user; switch(reason) { case LWS_CALLBACK_ESTABLISHED: { /* Is there any filtering we should apply? */ char ip[256]; #ifdef HAVE_LIBWEBSOCKETS_PEER_SIMPLE lws_get_peer_simple(wsi, ip, 256); JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection opened from %s\n", log_prefix, wsi, ip); #else char name[256]; lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, 256, ip, 256); JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection opened from %s by %s\n", log_prefix, wsi, ip, name); #endif if(!janus_websockets_is_allowed(ip, admin)) { JANUS_LOG(LOG_ERR, "[%s-%p] IP %s is unauthorized to connect to the WebSockets %s API interface\n", log_prefix, wsi, ip, admin ? "Admin" : "Janus"); /* Close the connection */ lws_callback_on_writable(wsi); return -1; } JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection accepted\n", log_prefix, wsi); if(ws_client == NULL) { JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } /* Prepare the session */ ws_client->wsi = wsi; ws_client->messages = g_async_queue_new(); ws_client->buffer = NULL; ws_client->buflen = 0; ws_client->bufpending = 0; ws_client->bufoffset = 0; g_atomic_int_set(&ws_client->destroyed, 0); ws_client->ts = janus_transport_session_create(ws_client, NULL); /* Let us know when the WebSocket channel becomes writeable */ lws_callback_on_writable(wsi); JANUS_LOG(LOG_VERB, "[%s-%p] -- Ready to be used!\n", log_prefix, wsi); /* Notify handlers about this new transport */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "event", json_string("connected")); json_object_set_new(info, "admin_api", admin ? json_true() : json_false()); json_object_set_new(info, "ip", json_string(ip)); gateway->notify_event(&janus_websockets_transport, ws_client->ts, info); } return 0; } case LWS_CALLBACK_RECEIVE: { JANUS_LOG(LOG_HUGE, "[%s-%p] Got %zu bytes:\n", log_prefix, wsi, len); if(ws_client == NULL || ws_client->wsi == NULL) { JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } if(g_atomic_int_get(&ws_client->destroyed)) return 0; /* Is this a new message, or part of a fragmented one? */ const size_t remaining = lws_remaining_packet_payload(wsi); if(ws_client->incoming == NULL) { JANUS_LOG(LOG_HUGE, "[%s-%p] First fragment: %zu bytes, %zu remaining\n", log_prefix, wsi, len, remaining); ws_client->incoming = g_malloc(len+1); memcpy(ws_client->incoming, in, len); ws_client->incoming[len] = '\0'; JANUS_LOG(LOG_HUGE, "%s\n", ws_client->incoming); } else { size_t offset = strlen(ws_client->incoming); JANUS_LOG(LOG_HUGE, "[%s-%p] Appending fragment: offset %zu, %zu bytes, %zu remaining\n", log_prefix, wsi, offset, len, remaining); ws_client->incoming = g_realloc(ws_client->incoming, offset+len+1); memcpy(ws_client->incoming+offset, in, len); ws_client->incoming[offset+len] = '\0'; JANUS_LOG(LOG_HUGE, "%s\n", ws_client->incoming+offset); } if(remaining > 0 || !lws_is_final_fragment(wsi)) { /* Still waiting for some more fragments */ JANUS_LOG(LOG_HUGE, "[%s-%p] Waiting for more fragments\n", log_prefix, wsi); return 0; } JANUS_LOG(LOG_HUGE, "[%s-%p] Done, parsing message: %zu bytes\n", log_prefix, wsi, strlen(ws_client->incoming)); /* If we got here, the message is complete: parse the JSON payload */ json_error_t error; json_t *root = json_loads(ws_client->incoming, 0, &error); g_free(ws_client->incoming); ws_client->incoming = NULL; /* Notify the core, passing both the object and, since it may be needed, the error */ gateway->incoming_request(&janus_websockets_transport, ws_client->ts, NULL, admin, root, &error); return 0; } case LWS_CALLBACK_SERVER_WRITEABLE: { if(ws_client == NULL || ws_client->wsi == NULL) { JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } if(!g_atomic_int_get(&ws_client->destroyed) && !g_atomic_int_get(&stopping)) { janus_mutex_lock(&ws_client->ts->mutex); /* Check if we have a pending/partial write to complete first */ if(ws_client->buffer && ws_client->bufpending > 0 && ws_client->bufoffset > 0 && !g_atomic_int_get(&ws_client->destroyed) && !g_atomic_int_get(&stopping)) { JANUS_LOG(LOG_HUGE, "[%s-%p] Completing pending WebSocket write (still need to write last %d bytes)...\n", log_prefix, wsi, ws_client->bufpending); int sent = lws_write(wsi, ws_client->buffer + ws_client->bufoffset, ws_client->bufpending, LWS_WRITE_TEXT); JANUS_LOG(LOG_HUGE, "[%s-%p] -- Sent %d/%d bytes\n", log_prefix, wsi, sent, ws_client->bufpending); if(sent > -1 && sent < ws_client->bufpending) { /* We still couldn't send everything that was left, we'll try and complete this in the next round */ ws_client->bufpending -= sent; ws_client->bufoffset += sent; } else { /* Clear the pending/partial write queue */ ws_client->bufpending = 0; ws_client->bufoffset = 0; } /* Done for this round, check the next response/notification later */ lws_callback_on_writable(wsi); janus_mutex_unlock(&ws_client->ts->mutex); return 0; } /* Shoot all the pending messages */ char *response = g_async_queue_try_pop(ws_client->messages); if(response && !g_atomic_int_get(&ws_client->destroyed) && !g_atomic_int_get(&stopping)) { /* Gotcha! */ int buflen = LWS_SEND_BUFFER_PRE_PADDING + strlen(response) + LWS_SEND_BUFFER_POST_PADDING; if (buflen > ws_client->buflen) { /* We need a larger shared buffer */ JANUS_LOG(LOG_HUGE, "[%s-%p] Re-allocating to %d bytes (was %d, response is %zu bytes)\n", log_prefix, wsi, buflen, ws_client->buflen, strlen(response)); ws_client->buflen = buflen; ws_client->buffer = g_realloc(ws_client->buffer, buflen); } memcpy(ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, response, strlen(response)); JANUS_LOG(LOG_HUGE, "[%s-%p] Sending WebSocket message (%zu bytes)...\n", log_prefix, wsi, strlen(response)); int sent = lws_write(wsi, ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, strlen(response), LWS_WRITE_TEXT); JANUS_LOG(LOG_HUGE, "[%s-%p] -- Sent %d/%zu bytes\n", log_prefix, wsi, sent, strlen(response)); if(sent > -1 && sent < (int)strlen(response)) { /* We couldn't send everything in a single write, we'll complete this in the next round */ ws_client->bufpending = strlen(response) - sent; ws_client->bufoffset = LWS_SEND_BUFFER_PRE_PADDING + sent; JANUS_LOG(LOG_HUGE, "[%s-%p] -- Couldn't write all bytes (%d missing), setting offset %d\n", log_prefix, wsi, ws_client->bufpending, ws_client->bufoffset); } /* We can get rid of the message */ free(response); /* Done for this round, check the next response/notification later */ lws_callback_on_writable(wsi); janus_mutex_unlock(&ws_client->ts->mutex); return 0; } janus_mutex_unlock(&ws_client->ts->mutex); } return 0; } case LWS_CALLBACK_CLOSED: { JANUS_LOG(LOG_VERB, "[%s-%p] WS connection down, closing\n", log_prefix, wsi); janus_websockets_destroy_client(ws_client, wsi, log_prefix); JANUS_LOG(LOG_VERB, "[%s-%p] -- closed\n", log_prefix, wsi); return 0; } case LWS_CALLBACK_WSI_DESTROY: { JANUS_LOG(LOG_VERB, "[%s-%p] WS connection down, destroying\n", log_prefix, wsi); janus_websockets_destroy_client(ws_client, wsi, log_prefix); JANUS_LOG(LOG_VERB, "[%s-%p] -- destroyed\n", log_prefix, wsi); return 0; } default: if(wsi != NULL) { JANUS_LOG(LOG_HUGE, "[%s-%p] %d (%s)\n", log_prefix, wsi, reason, janus_websockets_reason_string(reason)); } else { JANUS_LOG(LOG_HUGE, "[%s] %d (%s)\n", log_prefix, reason, janus_websockets_reason_string(reason)); } break; } return 0; }
int WebSocketTcpServer::__callback_websocket__(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { if ( nullptr == m_context ) return 0; WebSocketTcpServer *server = static_cast<WebSocketTcpServer*>(lws_context_user(m_context)); if ( server->m_run_thread == false ) return 0; switch ( reason ) { case LWS_CALLBACK_WSI_CREATE: break; case LWS_CALLBACK_ESTABLISHED: { LOG_DEBUG("LWS_CALLBACK_ESTABLISHED \n"); string protocol = server->__getProtocolFromHeader__(wsi); LOG_DEBUG("user : %s \n", protocol.data()); int client_fd = lws_get_socket_fd(wsi); struct sockaddr_in clientaddr; socklen_t peeraddrlen = sizeof(clientaddr); if ( 0 > getpeername(client_fd, (struct sockaddr*)&clientaddr, &peeraddrlen) ) { perror("getpeername error "); } size_t client_id = server->addClientInfo(ClientInfoPtr( new WebSocketTcpClientInfo(client_fd, &clientaddr, wsi, protocol))); NOTIFY_CLIENT_CONNECTED(server->m_server_id, client_id); } break; case LWS_CALLBACK_RECEIVE: LOG_DEBUG("LWS_CALLBACK_RECEIVE \n"); { LOG_DEBUG("received data: %s\n", (char *) in); int client_fd = lws_get_socket_fd(wsi); auto client = static_pointer_cast<WebSocketTcpClientInfo>(server->findClientInfo(client_fd)); NOTIFY_SERVER_RECEIVED_FROM_PROTOCOL( server->m_server_id, client->getClientId(), (char*)in, len, client->getProtocol()); } break; case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: LOG_DEBUG("LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION \n"); break; case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: LOG_DEBUG("LWS_CALLBACK_WS_PEER_INITIATED_CLOSE \n"); break; case LWS_CALLBACK_CLOSED: LOG_DEBUG("LWS_CALLBACK_CLOSED \n"); { int client_fd = lws_get_socket_fd(wsi); size_t client_id = server->removeClientInfo(client_fd); NOTIFY_CLIENT_DISCONNECTED(server->m_server_id, client_id); } break; case LWS_CALLBACK_PROTOCOL_INIT: LOG_DEBUG("LWS_CALLBACK_PROTOCOL_INIT \n"); break; case LWS_CALLBACK_PROTOCOL_DESTROY: LOG_DEBUG("LWS_CALLBACK_PROTOCOL_DESTROY \n"); break; case LWS_CALLBACK_GET_THREAD_ID: /* to be silent */ break; case LWS_CALLBACK_DEL_POLL_FD: LOG_DEBUG("LWS_CALLBACK_DEL_POLL_FD \n"); { int client_fd = lws_get_socket_fd(wsi); size_t client_id = server->removeClientInfo(client_fd); NOTIFY_CLIENT_DISCONNECTED(server->m_server_id, client_id); } break; default: LOG_WARNING("Unhandled callback reason [%d] \n", reason); break; } return 0; }
int callback_lws_status(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__lws_status *pss = (struct per_session_data__lws_status *)user, **pp; char name[128], rip[128]; int m; switch (reason) { case LWS_CALLBACK_PROTOCOL_INIT: /* * Prepare the static server info */ server_info_len = sprintf((char *)server_info, "\"version\":\"%s\"," " \"hostname\":\"%s\"", lws_get_library_version(), lws_canonical_hostname( lws_get_context(wsi))); break; case LWS_CALLBACK_ESTABLISHED: /* * we keep a linked list of live pss, so we can walk it */ pss->last = 0; pss->list = list; list = pss; live_wsi++; lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, sizeof(name), rip, sizeof(rip)); sprintf(pss->ip, "%s (%s)", name, rip); gettimeofday(&pss->tv_established, NULL); strcpy(pss->user_agent, "unknown"); lws_hdr_copy(wsi, pss->user_agent, sizeof(pss->user_agent), WSI_TOKEN_HTTP_USER_AGENT); update_status(wsi, pss); break; case LWS_CALLBACK_SERVER_WRITEABLE: m = lws_write(wsi, (unsigned char *)cache + LWS_PRE, cache_len, LWS_WRITE_TEXT); if (m < server_info_len) { lwsl_err("ERROR %d writing to di socket\n", m); return -1; } break; case LWS_CALLBACK_CLOSED: pp = &list; while (*pp) { if (*pp == pss) { *pp = pss->list; pss->list = NULL; live_wsi--; break; } pp = &((*pp)->list); } update_status(wsi, pss); break; default: break; } return 0; }
int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__http *pss = (struct per_session_data__http *)user; unsigned char buffer[4096 + LWS_PRE]; unsigned long amount, file_len, sent; char leaf_path[1024]; const char *mimetype; char *other_headers; unsigned char *end; struct timeval tv; unsigned char *p; #ifndef LWS_NO_CLIENT struct per_session_data__http *pss1; struct lws *wsi1; #endif char buf[256]; char b64[64]; int n, m; #ifdef EXTERNAL_POLL struct lws_pollargs *pa = (struct lws_pollargs *)in; #endif // lwsl_err("%s: reason %d\n", __func__, reason); switch (reason) { case LWS_CALLBACK_HTTP: { char name[100], rip[50]; lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, sizeof(name), rip, sizeof(rip)); sprintf(buf, "%s (%s)", name, rip); lwsl_notice("HTTP connect from %s\n", buf); } if (len < 1) { lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL); goto try_to_reuse; } #ifndef LWS_NO_CLIENT if (!strncmp(in, "/proxytest", 10)) { struct lws_client_connect_info i; char *rootpath = "/"; const char *p = (const char *)in; if (lws_get_child(wsi)) break; pss->client_finished = 0; memset(&i,0, sizeof(i)); i.context = lws_get_context(wsi); i.address = "git.libwebsockets.org"; i.port = 80; i.ssl_connection = 0; if (p[10]) i.path = (char *)in + 10; else i.path = rootpath; i.host = "git.libwebsockets.org"; i.origin = NULL; i.method = "GET"; i.parent_wsi = wsi; i.uri_replace_from = "git.libwebsockets.org/"; i.uri_replace_to = "/proxytest/"; if (!lws_client_connect_via_info(&i)) { lwsl_err("proxy connect fail\n"); break; } break; } #endif #if 1 /* this example server has no concept of directories */ if (strchr((const char *)in + 1, '/')) { lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL); goto try_to_reuse; } #endif /* if a legal POST URL, let it continue and accept data */ if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) return 0; /* check for the "send a big file by hand" example case */ lwsl_notice("%s\n", in); if (!strcmp((const char *)in, "/leaf.jpg")) { if (strlen(resource_path) > sizeof(leaf_path) - 10) return -1; sprintf(leaf_path, "%s/leaf.jpg", resource_path); /* well, let's demonstrate how to send the hard way */ p = buffer + LWS_PRE; end = p + sizeof(buffer) - LWS_PRE; pss->fd = lws_plat_file_open(wsi, leaf_path, &file_len, LWS_O_RDONLY); if (pss->fd == LWS_INVALID_FILE) { lwsl_err("faild to open file %s\n", leaf_path); return -1; } /* * we will send a big jpeg file, but it could be * anything. Set the Content-Type: appropriately * so the browser knows what to do with it. * * Notice we use the APIs to build the header, which * will do the right thing for HTTP 1/1.1 and HTTP2 * depending on what connection it happens to be working * on */ if (lws_add_http_header_status(wsi, 200, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"image/jpeg", 10, &p, end)) return 1; if (lws_add_http_header_content_length(wsi, file_len, &p, end)) return 1; if (lws_finalize_http_header(wsi, &p, end)) return 1; /* * send the http headers... * this won't block since it's the first payload sent * on the connection since it was established * (too small for partial) * * Notice they are sent using LWS_WRITE_HTTP_HEADERS * which also means you can't send body too in one step, * this is mandated by changes in HTTP2 */ *p = '\0'; lwsl_info("%s\n", buffer + LWS_PRE); n = lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS); if (n < 0) { lws_plat_file_close(wsi, pss->fd); return -1; } /* * book us a LWS_CALLBACK_HTTP_WRITEABLE callback */ lws_callback_on_writable(wsi); break; } /* if not, send a file the easy way */ if (!strncmp(in, "/cgit-data/", 11)) { in = (char *)in + 11; strcpy(buf, "/usr/share/cgit"); } else strcpy(buf, resource_path); if (strcmp(in, "/")) { if (*((const char *)in) != '/') strcat(buf, "/"); strncat(buf, in, sizeof(buf) - strlen(buf) - 1); } else /* default file to serve */ strcat(buf, "/test.html"); buf[sizeof(buf) - 1] = '\0'; /* refuse to serve files we don't understand */ mimetype = get_mimetype(buf); if (!mimetype) { lwsl_err("Unknown mimetype for %s\n", buf); lws_return_http_status(wsi, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL); return -1; } /* demonstrates how to set a cookie on / */ other_headers = leaf_path; p = (unsigned char *)leaf_path; if (!strcmp((const char *)in, "/") && !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) { /* this isn't very unguessable but it'll do for us */ gettimeofday(&tv, NULL); n = sprintf(b64, "test=LWS_%u_%u_COOKIE;Max-Age=360000", (unsigned int)tv.tv_sec, (unsigned int)tv.tv_usec); if (lws_add_http_header_by_name(wsi, (unsigned char *)"set-cookie:", (unsigned char *)b64, n, &p, (unsigned char *)leaf_path + sizeof(leaf_path))) return 1; } if (lws_is_ssl(wsi) && lws_add_http_header_by_name(wsi, (unsigned char *) "Strict-Transport-Security:", (unsigned char *) "max-age=15768000 ; " "includeSubDomains", 36, &p, (unsigned char *)leaf_path + sizeof(leaf_path))) return 1; n = (char *)p - leaf_path; n = lws_serve_http_file(wsi, buf, mimetype, other_headers, n); if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi))) return -1; /* error or can't reuse connection: close the socket */ /* * notice that the sending of the file completes asynchronously, * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when * it's done */ break; case LWS_CALLBACK_HTTP_BODY: strncpy(buf, in, 20); buf[20] = '\0'; if (len < 20) buf[len] = '\0'; lwsl_notice("LWS_CALLBACK_HTTP_BODY: %s... len %d\n", (const char *)buf, (int)len); break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); /* the whole of the sent body arrived, close or reuse the connection */ lws_return_http_status(wsi, HTTP_STATUS_OK, NULL); goto try_to_reuse; case LWS_CALLBACK_HTTP_FILE_COMPLETION: goto try_to_reuse; case LWS_CALLBACK_HTTP_WRITEABLE: lwsl_info("LWS_CALLBACK_HTTP_WRITEABLE\n"); if (pss->client_finished) return -1; if (pss->fd == LWS_INVALID_FILE) goto try_to_reuse; #ifdef LWS_WITH_CGI if (pss->reason_bf & 1) { if (lws_cgi_write_split_stdout_headers(wsi) < 0) goto bail; pss->reason_bf &= ~1; break; } #endif #ifndef LWS_NO_CLIENT if (pss->reason_bf & 2) { char *px = buf + LWS_PRE; int lenx = sizeof(buf) - LWS_PRE; /* * our sink is writeable and our source has something * to read. So read a lump of source material of * suitable size to send or what's available, whichever * is the smaller. */ pss->reason_bf &= ~2; wsi1 = lws_get_child(wsi); if (!wsi1) break; if (lws_http_client_read(wsi1, &px, &lenx) < 0) goto bail; if (pss->client_finished) return -1; break; } #endif /* * we can send more of whatever it is we were sending */ sent = 0; do { /* we'd like the send this much */ n = sizeof(buffer) - LWS_PRE; /* but if the peer told us he wants less, we can adapt */ m = lws_get_peer_write_allowance(wsi); /* -1 means not using a protocol that has this info */ if (m == 0) /* right now, peer can't handle anything */ goto later; if (m != -1 && m < n) /* he couldn't handle that much */ n = m; n = lws_plat_file_read(wsi, pss->fd, &amount, buffer + LWS_PRE, n); /* problem reading, close conn */ if (n < 0) { lwsl_err("problem reading file\n"); goto bail; } n = (int)amount; /* sent it all, close conn */ if (n == 0) goto penultimate; /* * To support HTTP2, must take care about preamble space * * identification of when we send the last payload frame * is handled by the library itself if you sent a * content-length header */ m = lws_write(wsi, buffer + LWS_PRE, n, LWS_WRITE_HTTP); if (m < 0) { lwsl_err("write failed\n"); /* write failed, close conn */ goto bail; } if (m) /* while still active, extend timeout */ lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 5); sent += m; } while (!lws_send_pipe_choked(wsi) && (sent < 1024 * 1024)); later: lws_callback_on_writable(wsi); break; penultimate: lws_plat_file_close(wsi, pss->fd); pss->fd = LWS_INVALID_FILE; goto try_to_reuse; bail: lws_plat_file_close(wsi, pss->fd); return -1; /* * callback for confirming to continue with client IP appear in * protocol 0 callback since no websocket protocol has been agreed * yet. You can just ignore this if you won't filter on client IP * since the default unhandled callback return is 0 meaning let the * connection continue. */ case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: /* if we returned non-zero from here, we kill the connection */ break; #ifndef LWS_NO_CLIENT case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { char ctype[64], ctlen = 0; lwsl_err("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP\n"); p = buffer + LWS_PRE; end = p + sizeof(buffer) - LWS_PRE; if (lws_add_http_header_status(lws_get_parent(wsi), 200, &p, end)) return 1; if (lws_add_http_header_by_token(lws_get_parent(wsi), WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) return 1; ctlen = lws_hdr_copy(wsi, ctype, sizeof(ctype), WSI_TOKEN_HTTP_CONTENT_TYPE); if (ctlen > 0) { if (lws_add_http_header_by_token(lws_get_parent(wsi), WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)ctype, ctlen, &p, end)) return 1; } #if 0 if (lws_add_http_header_content_length(lws_get_parent(wsi), file_len, &p, end)) return 1; #endif if (lws_finalize_http_header(lws_get_parent(wsi), &p, end)) return 1; *p = '\0'; lwsl_info("%s\n", buffer + LWS_PRE); n = lws_write(lws_get_parent(wsi), buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS); if (n < 0) return -1; break; } case LWS_CALLBACK_CLOSED_CLIENT_HTTP: //lwsl_err("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n"); return -1; break; case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p\n", wsi); assert(lws_get_parent(wsi)); if (!lws_get_parent(wsi)) break; // lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p: sock: %d, parent_wsi: %p, parent_sock:%d, len %d\n", // wsi, lws_get_socket_fd(wsi), // lws_get_parent(wsi), // lws_get_socket_fd(lws_get_parent(wsi)), len); pss1 = lws_wsi_user(lws_get_parent(wsi)); pss1->reason_bf |= 2; lws_callback_on_writable(lws_get_parent(wsi)); break; case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ len %d\n", len); assert(lws_get_parent(wsi)); m = lws_write(lws_get_parent(wsi), (unsigned char *)in, len, LWS_WRITE_HTTP); if (m < 0) return -1; break; case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: //lwsl_err("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n"); assert(lws_get_parent(wsi)); if (!lws_get_parent(wsi)) break; pss1 = lws_wsi_user(lws_get_parent(wsi)); pss1->client_finished = 1; break; #endif #ifdef LWS_WITH_CGI /* CGI IO events (POLLIN/OUT) appear here our demo user code policy is * * - POST data goes on subprocess stdin * - subprocess stdout goes on http via writeable callback * - subprocess stderr goes to the logs */ case LWS_CALLBACK_CGI: pss->args = *((struct lws_cgi_args *)in); //lwsl_notice("LWS_CALLBACK_CGI: ch %d\n", pss->args.ch); switch (pss->args.ch) { /* which of stdin/out/err ? */ case LWS_STDIN: /* TBD stdin rx flow control */ break; case LWS_STDOUT: pss->reason_bf |= 1; /* when writing to MASTER would not block */ lws_callback_on_writable(wsi); break; case LWS_STDERR: n = read(lws_get_socket_fd(pss->args.stdwsi[LWS_STDERR]), buf, 127); //lwsl_notice("stderr reads %d\n", n); if (n > 0) { if (buf[n - 1] != '\n') buf[n++] = '\n'; buf[n] = '\0'; lwsl_notice("CGI-stderr: %s\n", buf); } break; } break; case LWS_CALLBACK_CGI_TERMINATED: //lwsl_notice("LWS_CALLBACK_CGI_TERMINATED\n"); /* because we sent on openended http, close the connection */ return -1; case LWS_CALLBACK_CGI_STDIN_DATA: /* POST body for stdin */ //lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA\n"); pss->args = *((struct lws_cgi_args *)in); n = write(lws_get_socket_fd(pss->args.stdwsi[LWS_STDIN]), pss->args.data, pss->args.len); //lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: write says %d", n); if (n < pss->args.len) lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: sent %d only %d went", n, pss->args.len); return n; #endif /* * callbacks for managing the external poll() array appear in * protocol 0 callback */ case LWS_CALLBACK_LOCK_POLL: /* * lock mutex to protect pollfd state * called before any other POLL related callback * if protecting wsi lifecycle change, len == 1 */ test_server_lock(len); break; case LWS_CALLBACK_UNLOCK_POLL: /* * unlock mutex to protect pollfd state when * called after any other POLL related callback * if protecting wsi lifecycle change, len == 1 */ test_server_unlock(len); break; #ifdef EXTERNAL_POLL case LWS_CALLBACK_ADD_POLL_FD: if (count_pollfds >= max_poll_elements) { lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n"); return 1; } fd_lookup[pa->fd] = count_pollfds; pollfds[count_pollfds].fd = pa->fd; pollfds[count_pollfds].events = pa->events; pollfds[count_pollfds++].revents = 0; break; case LWS_CALLBACK_DEL_POLL_FD: if (!--count_pollfds) break; m = fd_lookup[pa->fd]; /* have the last guy take up the vacant slot */ pollfds[m] = pollfds[count_pollfds]; fd_lookup[pollfds[count_pollfds].fd] = m; break; case LWS_CALLBACK_CHANGE_MODE_POLL_FD: pollfds[fd_lookup[pa->fd]].events = pa->events; break; #endif case LWS_CALLBACK_GET_THREAD_ID: /* * if you will call "lws_callback_on_writable" * from a different thread, return the caller thread ID * here so lws can use this information to work out if it * should signal the poll() loop to exit and restart early */ /* return pthread_getthreadid_np(); */ break; default: break; } return 0; /* if we're on HTTP1.1 or 2.0, will keep the idle connection alive */ try_to_reuse: if (lws_http_transaction_completed(wsi)) return -1; return 0; }
LWS_VISIBLE LWS_EXTERN int libwebsocket_get_socket_fd(struct lws *wsi) { return lws_get_socket_fd(wsi); }
int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__http *pss = (struct per_session_data__http *)user; unsigned char buffer[4096 + LWS_PRE]; unsigned long amount, file_len, sent; char leaf_path[1024]; const char *mimetype; char *other_headers; unsigned char *end, *start; struct timeval tv; unsigned char *p; #ifndef LWS_NO_CLIENT struct per_session_data__http *pss1; struct lws *wsi1; #endif char buf[256]; char b64[64]; int n, m; #ifdef EXTERNAL_POLL struct lws_pollargs *pa = (struct lws_pollargs *)in; #endif switch (reason) { case LWS_CALLBACK_HTTP: lwsl_notice("lws_http_serve: %s\n",in); if (debug_level & LLL_INFO) { dump_handshake_info(wsi); /* dump the individual URI Arg parameters */ n = 0; while (lws_hdr_copy_fragment(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_URI_ARGS, n) > 0) { lwsl_notice("URI Arg %d: %s\n", ++n, buf); } } { char name[100], rip[50]; lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, sizeof(name), rip, sizeof(rip)); sprintf(buf, "%s (%s)", name, rip); lwsl_notice("HTTP connect from %s\n", buf); } if (len < 1) { lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL); goto try_to_reuse; } #ifndef LWS_NO_CLIENT if (!strncmp(in, "/proxytest", 10)) { struct lws_client_connect_info i; char *rootpath = "/"; const char *p = (const char *)in; if (lws_get_child(wsi)) break; pss->client_finished = 0; memset(&i,0, sizeof(i)); i.context = lws_get_context(wsi); i.address = "git.libwebsockets.org"; i.port = 80; i.ssl_connection = 0; if (p[10]) i.path = (char *)in + 10; else i.path = rootpath; i.host = "git.libwebsockets.org"; i.origin = NULL; i.method = "GET"; i.parent_wsi = wsi; i.uri_replace_from = "git.libwebsockets.org/"; i.uri_replace_to = "/proxytest/"; if (!lws_client_connect_via_info(&i)) { lwsl_err("proxy connect fail\n"); break; } break; } #endif #if 1 /* this example server has no concept of directories */ if (strchr((const char *)in + 1, '/')) { lws_return_http_status(wsi, HTTP_STATUS_NOT_ACCEPTABLE, NULL); goto try_to_reuse; } #endif /* if a legal POST URL, let it continue and accept data */ if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) return 0; /* check for the "send a big file by hand" example case */ if (!strcmp((const char *)in, "/leaf.jpg")) { if (strlen(resource_path) > sizeof(leaf_path) - 10) return -1; sprintf(leaf_path, "%s/leaf.jpg", resource_path); /* well, let's demonstrate how to send the hard way */ p = buffer + LWS_PRE; end = p + sizeof(buffer) - LWS_PRE; pss->fd = lws_plat_file_open(wsi, leaf_path, &file_len, LWS_O_RDONLY); if (pss->fd == LWS_INVALID_FILE) { lwsl_err("faild to open file %s\n", leaf_path); return -1; } /* * we will send a big jpeg file, but it could be * anything. Set the Content-Type: appropriately * so the browser knows what to do with it. * * Notice we use the APIs to build the header, which * will do the right thing for HTTP 1/1.1 and HTTP2 * depending on what connection it happens to be working * on */ if (lws_add_http_header_status(wsi, 200, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"image/jpeg", 10, &p, end)) return 1; if (lws_add_http_header_content_length(wsi, file_len, &p, end)) return 1; if (lws_finalize_http_header(wsi, &p, end)) return 1; /* * send the http headers... * this won't block since it's the first payload sent * on the connection since it was established * (too small for partial) * * Notice they are sent using LWS_WRITE_HTTP_HEADERS * which also means you can't send body too in one step, * this is mandated by changes in HTTP2 */ *p = '\0'; lwsl_info("%s\n", buffer + LWS_PRE); n = lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS); if (n < 0) { lws_plat_file_close(wsi, pss->fd); return -1; } /* * book us a LWS_CALLBACK_HTTP_WRITEABLE callback */ lws_callback_on_writable(wsi); break; } /* if not, send a file the easy way */ if (!strncmp(in, "/cgit-data/", 11)) { in = (char *)in + 11; strcpy(buf, "/usr/share/cgit"); } else strcpy(buf, resource_path); if (strcmp(in, "/")) { if (*((const char *)in) != '/') strcat(buf, "/"); strncat(buf, in, sizeof(buf) - strlen(buf) - 1); } else /* default file to serve */ strcat(buf, "/test.html"); buf[sizeof(buf) - 1] = '\0'; /* refuse to serve files we don't understand */ mimetype = get_mimetype(buf); if (!mimetype) { lwsl_err("Unknown mimetype for %s\n", buf); lws_return_http_status(wsi, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL); return -1; } /* demonstrates how to set a cookie on / */ other_headers = leaf_path; p = (unsigned char *)leaf_path; if (!strcmp((const char *)in, "/") && !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) { /* this isn't very unguessable but it'll do for us */ gettimeofday(&tv, NULL); n = sprintf(b64, "test=LWS_%u_%u_COOKIE;Max-Age=360000", (unsigned int)tv.tv_sec, (unsigned int)tv.tv_usec); if (lws_add_http_header_by_name(wsi, (unsigned char *)"set-cookie:", (unsigned char *)b64, n, &p, (unsigned char *)leaf_path + sizeof(leaf_path))) return 1; } if (lws_is_ssl(wsi) && lws_add_http_header_by_name(wsi, (unsigned char *) "Strict-Transport-Security:", (unsigned char *) "max-age=15768000 ; " "includeSubDomains", 36, &p, (unsigned char *)leaf_path + sizeof(leaf_path))) return 1; n = (char *)p - leaf_path; n = lws_serve_http_file(wsi, buf, mimetype, other_headers, n); if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi))) return -1; /* error or can't reuse connection: close the socket */ /* * notice that the sending of the file completes asynchronously, * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when * it's done */ break; case LWS_CALLBACK_HTTP_BODY: /* create the POST argument parser if not already existing */ if (!pss->spa) { pss->spa = lws_spa_create(wsi, param_names, ARRAY_SIZE(param_names), 1024, file_upload_cb, pss); if (!pss->spa) return -1; pss->filename[0] = '\0'; pss->file_length = 0; } /* let it parse the POST data */ if (lws_spa_process(pss->spa, in, len)) return -1; break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: lwsl_debug("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); /* * the whole of the sent body arrived, * respond to the client with a redirect to show the * results */ /* call to inform no more payload data coming */ lws_spa_finalize(pss->spa); p = (unsigned char *)pss->result + LWS_PRE; end = p + sizeof(pss->result) - LWS_PRE - 1; p += sprintf((char *)p, "<html><body><h1>Form results (after urldecoding)</h1>" "<table><tr><td>Name</td><td>Length</td><td>Value</td></tr>"); for (n = 0; n < ARRAY_SIZE(param_names); n++) p += snprintf((char *)p, end - p, "<tr><td><b>%s</b></td><td>%d</td><td>%s</td></tr>", param_names[n], lws_spa_get_length(pss->spa, n), lws_spa_get_string(pss->spa, n)); p += snprintf((char *)p, end - p, "</table><br><b>filename:</b> %s, <b>length</b> %ld", pss->filename, pss->file_length); p += snprintf((char *)p, end - p, "</body></html>"); pss->result_len = p - (unsigned char *)(pss->result + LWS_PRE); p = buffer + LWS_PRE; start = p; end = p + sizeof(buffer) - LWS_PRE; if (lws_add_http_header_status(wsi, 200, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"text/html", 9, &p, end)) return 1; if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end)) return 1; if (lws_finalize_http_header(wsi, &p, end)) return 1; n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); if (n < 0) return 1; n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE, pss->result_len, LWS_WRITE_HTTP); if (n < 0) return 1; goto try_to_reuse; case LWS_CALLBACK_HTTP_FILE_COMPLETION: goto try_to_reuse; case LWS_CALLBACK_HTTP_WRITEABLE: lwsl_info("LWS_CALLBACK_HTTP_WRITEABLE\n"); if (pss->client_finished) return -1; if (pss->fd == LWS_INVALID_FILE) goto try_to_reuse; #ifndef LWS_NO_CLIENT if (pss->reason_bf & 2) { char *px = buf + LWS_PRE; int lenx = sizeof(buf) - LWS_PRE; /* * our sink is writeable and our source has something * to read. So read a lump of source material of * suitable size to send or what's available, whichever * is the smaller. */ pss->reason_bf &= ~2; wsi1 = lws_get_child(wsi); if (!wsi1) break; if (lws_http_client_read(wsi1, &px, &lenx) < 0) goto bail; if (pss->client_finished) return -1; break; } #endif /* * we can send more of whatever it is we were sending */ sent = 0; do { /* we'd like the send this much */ n = sizeof(buffer) - LWS_PRE; /* but if the peer told us he wants less, we can adapt */ m = lws_get_peer_write_allowance(wsi); /* -1 means not using a protocol that has this info */ if (m == 0) /* right now, peer can't handle anything */ goto later; if (m != -1 && m < n) /* he couldn't handle that much */ n = m; n = lws_plat_file_read(wsi, pss->fd, &amount, buffer + LWS_PRE, n); /* problem reading, close conn */ if (n < 0) { lwsl_err("problem reading file\n"); goto bail; } n = (int)amount; /* sent it all, close conn */ if (n == 0) goto penultimate; /* * To support HTTP2, must take care about preamble space * * identification of when we send the last payload frame * is handled by the library itself if you sent a * content-length header */ m = lws_write(wsi, buffer + LWS_PRE, n, LWS_WRITE_HTTP); if (m < 0) { lwsl_err("write failed\n"); /* write failed, close conn */ goto bail; } if (m) /* while still active, extend timeout */ lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 5); sent += m; } while (!lws_send_pipe_choked(wsi) && (sent < 1024 * 1024)); later: lws_callback_on_writable(wsi); break; penultimate: lws_plat_file_close(wsi, pss->fd); pss->fd = LWS_INVALID_FILE; goto try_to_reuse; bail: lws_plat_file_close(wsi, pss->fd); return -1; /* * callback for confirming to continue with client IP appear in * protocol 0 callback since no websocket protocol has been agreed * yet. You can just ignore this if you won't filter on client IP * since the default unhandled callback return is 0 meaning let the * connection continue. */ case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: /* if we returned non-zero from here, we kill the connection */ break; #ifndef LWS_NO_CLIENT case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { char ctype[64], ctlen = 0; lwsl_err("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP\n"); p = buffer + LWS_PRE; end = p + sizeof(buffer) - LWS_PRE; if (lws_add_http_header_status(lws_get_parent(wsi), 200, &p, end)) return 1; if (lws_add_http_header_by_token(lws_get_parent(wsi), WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) return 1; ctlen = lws_hdr_copy(wsi, ctype, sizeof(ctype), WSI_TOKEN_HTTP_CONTENT_TYPE); if (ctlen > 0) { if (lws_add_http_header_by_token(lws_get_parent(wsi), WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)ctype, ctlen, &p, end)) return 1; } #if 0 if (lws_add_http_header_content_length(lws_get_parent(wsi), file_len, &p, end)) return 1; #endif if (lws_finalize_http_header(lws_get_parent(wsi), &p, end)) return 1; *p = '\0'; lwsl_info("%s\n", buffer + LWS_PRE); n = lws_write(lws_get_parent(wsi), buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS); if (n < 0) return -1; break; } case LWS_CALLBACK_CLOSED_CLIENT_HTTP: //lwsl_err("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n"); return -1; break; case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p\n", wsi); assert(lws_get_parent(wsi)); if (!lws_get_parent(wsi)) break; // lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p: sock: %d, parent_wsi: %p, parent_sock:%d, len %d\n", // wsi, lws_get_socket_fd(wsi), // lws_get_parent(wsi), // lws_get_socket_fd(lws_get_parent(wsi)), len); pss1 = lws_wsi_user(lws_get_parent(wsi)); pss1->reason_bf |= 2; lws_callback_on_writable(lws_get_parent(wsi)); break; case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ len %d\n", len); assert(lws_get_parent(wsi)); m = lws_write(lws_get_parent(wsi), (unsigned char *)in, len, LWS_WRITE_HTTP); if (m < 0) return -1; break; case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: //lwsl_err("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n"); assert(lws_get_parent(wsi)); if (!lws_get_parent(wsi)) break; pss1 = lws_wsi_user(lws_get_parent(wsi)); pss1->client_finished = 1; break; #endif /* * callbacks for managing the external poll() array appear in * protocol 0 callback */ case LWS_CALLBACK_LOCK_POLL: /* * lock mutex to protect pollfd state * called before any other POLL related callback * if protecting wsi lifecycle change, len == 1 */ test_server_lock(len); break; case LWS_CALLBACK_UNLOCK_POLL: /* * unlock mutex to protect pollfd state when * called after any other POLL related callback * if protecting wsi lifecycle change, len == 1 */ test_server_unlock(len); break; #ifdef EXTERNAL_POLL case LWS_CALLBACK_ADD_POLL_FD: if (count_pollfds >= max_poll_elements) { lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n"); return 1; } fd_lookup[pa->fd] = count_pollfds; pollfds[count_pollfds].fd = pa->fd; pollfds[count_pollfds].events = pa->events; pollfds[count_pollfds++].revents = 0; break; case LWS_CALLBACK_DEL_POLL_FD: if (!--count_pollfds) break; m = fd_lookup[pa->fd]; /* have the last guy take up the vacant slot */ pollfds[m] = pollfds[count_pollfds]; fd_lookup[pollfds[count_pollfds].fd] = m; break; case LWS_CALLBACK_CHANGE_MODE_POLL_FD: pollfds[fd_lookup[pa->fd]].events = pa->events; break; #endif case LWS_CALLBACK_GET_THREAD_ID: /* * if you will call "lws_callback_on_writable" * from a different thread, return the caller thread ID * here so lws can use this information to work out if it * should signal the poll() loop to exit and restart early */ /* return pthread_getthreadid_np(); */ break; #if defined(LWS_USE_POLARSSL) #else #if defined(LWS_USE_MBEDTLS) #else #if defined(LWS_OPENSSL_SUPPORT) case LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION: /* Verify the client certificate */ if (!len || (SSL_get_verify_result((SSL*)in) != X509_V_OK)) { int err = X509_STORE_CTX_get_error((X509_STORE_CTX*)user); int depth = X509_STORE_CTX_get_error_depth((X509_STORE_CTX*)user); const char* msg = X509_verify_cert_error_string(err); lwsl_err("LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION: SSL error: %s (%d), depth: %d\n", msg, err, depth); return 1; } break; #if defined(LWS_HAVE_SSL_CTX_set1_param) case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS: if (crl_path[0]) { /* Enable CRL checking */ X509_VERIFY_PARAM *param = X509_VERIFY_PARAM_new(); X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK); SSL_CTX_set1_param((SSL_CTX*)user, param); X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX*)user); X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); n = X509_load_cert_crl_file(lookup, crl_path, X509_FILETYPE_PEM); X509_VERIFY_PARAM_free(param); if (n != 1) { char errbuf[256]; n = ERR_get_error(); lwsl_err("LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS: SSL error: %s (%d)\n", ERR_error_string(n, errbuf), n); return 1; } } break; #endif #endif #endif #endif default: break; } return 0; /* if we're on HTTP1.1 or 2.0, will keep the idle connection alive */ try_to_reuse: if (lws_http_transaction_completed(wsi)) return -1; return 0; }
LWS_VISIBLE int lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { #ifdef LWS_WITH_CGI struct lws_cgi_args *args; char buf[128]; int n; #endif switch (reason) { case LWS_CALLBACK_HTTP: #ifndef LWS_NO_SERVER if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL)) return -1; if (lws_http_transaction_completed(wsi)) #endif return -1; break; case LWS_CALLBACK_HTTP_WRITEABLE: #ifdef LWS_WITH_CGI if (wsi->reason_bf & 1) { if (lws_cgi_write_split_stdout_headers(wsi) < 0) return -1; wsi->reason_bf &= ~1; break; } #endif break; #ifdef LWS_WITH_CGI /* CGI IO events (POLLIN/OUT) appear here, our default policy is: * * - POST data goes on subprocess stdin * - subprocess stdout goes on http via writeable callback * - subprocess stderr goes to the logs */ case LWS_CALLBACK_CGI: args = (struct lws_cgi_args *)in; switch (args->ch) { /* which of stdin/out/err ? */ case LWS_STDIN: /* TBD stdin rx flow control */ break; case LWS_STDOUT: wsi->reason_bf |= 1; /* when writing to MASTER would not block */ lws_callback_on_writable(wsi); break; case LWS_STDERR: n = read(lws_get_socket_fd(args->stdwsi[LWS_STDERR]), buf, sizeof(buf) - 2); if (n > 0) { if (buf[n - 1] != '\n') buf[n++] = '\n'; buf[n] = '\0'; lwsl_notice("CGI-stderr: %s\n", buf); } break; } break; case LWS_CALLBACK_CGI_TERMINATED: return -1; case LWS_CALLBACK_CGI_STDIN_DATA: /* POST body for stdin */ args = (struct lws_cgi_args *)in; args->data[args->len] = '\0'; n = write(lws_get_socket_fd(args->stdwsi[LWS_STDIN]), args->data, args->len); if (n < args->len) lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: " "sent %d only %d went", n, args->len); return n; #endif default: break; } return 0; }
LWS_VISIBLE LWS_EXTERN int lws_cgi_write_split_stdout_headers(struct lws *wsi) { int n, m, cmd; unsigned char buf[LWS_PRE + 4096], *start = &buf[LWS_PRE], *p = start, *end = &buf[sizeof(buf) - 1 - LWS_PRE], *name, *value = NULL; char c, hrs; if (!wsi->http.cgi) return -1; while (wsi->hdr_state != LHCS_PAYLOAD) { /* * We have to separate header / finalize and payload chunks, * since they need to be handled separately */ switch (wsi->hdr_state) { case LHCS_RESPONSE: lwsl_debug("LHCS_RESPONSE: issuing response %d\n", wsi->http.cgi->response_code); if (lws_add_http_header_status(wsi, wsi->http.cgi->response_code, &p, end)) return 1; if (!wsi->http.cgi->explicitly_chunked && !wsi->http.cgi->content_length && lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_TRANSFER_ENCODING, (unsigned char *)"chunked", 7, &p, end)) return 1; if (!(wsi->http2_substream)) if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION, (unsigned char *)"close", 5, &p, end)) return 1; n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS | LWS_WRITE_NO_FIN); /* * so we have a bunch of http/1 style ascii headers * starting from wsi->http.cgi->headers_buf through * wsi->http.cgi->headers_pos. These are OK for http/1 * connections, but they're no good for http/2 conns. * * Let's redo them at headers_pos forward using the * correct coding for http/1 or http/2 */ if (!wsi->http2_substream) goto post_hpack_recode; p = wsi->http.cgi->headers_start; wsi->http.cgi->headers_start = wsi->http.cgi->headers_pos; wsi->http.cgi->headers_dumped = wsi->http.cgi->headers_start; hrs = HR_NAME; name = buf; while (p < wsi->http.cgi->headers_start) { switch (hrs) { case HR_NAME: /* * in http/2 upper-case header names * are illegal. So convert to lower- * case. */ if (name - buf > 64) return -1; if (*p != ':') { if (*p >= 'A' && *p <= 'Z') *name++ = (*p++) + ('a' - 'A'); else *name++ = *p++; } else { p++; *name++ = '\0'; value = name; hrs = HR_WHITESPACE; } break; case HR_WHITESPACE: if (*p == ' ') { p++; break; } hrs = HR_ARG; /* fallthru */ case HR_ARG: if (name > end - 64) return -1; if (*p != '\x0a' && *p != '\x0d') { *name++ = *p++; break; } hrs = HR_CRLF; /* fallthru */ case HR_CRLF: if ((*p != '\x0a' && *p != '\x0d') || p + 1 == wsi->http.cgi->headers_start) { *name = '\0'; if ((strcmp((const char *)buf, "transfer-encoding") )) { lwsl_debug("+ %s: %s\n", buf, value); if ( lws_add_http_header_by_name(wsi, buf, (unsigned char *)value, name - value, (unsigned char **)&wsi->http.cgi->headers_pos, (unsigned char *)wsi->http.cgi->headers_end)) return 1; hrs = HR_NAME; name = buf; break; } } p++; break; } } post_hpack_recode: /* finalize cached headers before dumping them */ if (lws_finalize_http_header(wsi, (unsigned char **)&wsi->http.cgi->headers_pos, (unsigned char *)wsi->http.cgi->headers_end)) { lwsl_notice("finalize failed\n"); return -1; } wsi->hdr_state = LHCS_DUMP_HEADERS; wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_HEADERS; lws_callback_on_writable(wsi); /* back to the loop for writeability again */ return 0; case LHCS_DUMP_HEADERS: n = wsi->http.cgi->headers_pos - wsi->http.cgi->headers_dumped; if (n > 512) n = 512; lwsl_debug("LHCS_DUMP_HEADERS: %d\n", n); cmd = LWS_WRITE_HTTP_HEADERS_CONTINUATION; if (wsi->http.cgi->headers_dumped + n != wsi->http.cgi->headers_pos) { lwsl_notice("adding no fin flag\n"); cmd |= LWS_WRITE_NO_FIN; } m = lws_write(wsi, (unsigned char *)wsi->http.cgi->headers_dumped, n, cmd); if (m < 0) { lwsl_debug("%s: write says %d\n", __func__, m); return -1; } wsi->http.cgi->headers_dumped += n; if (wsi->http.cgi->headers_dumped == wsi->http.cgi->headers_pos) { wsi->hdr_state = LHCS_PAYLOAD; lws_free_set_NULL(wsi->http.cgi->headers_buf); lwsl_debug("freed cgi headers\n"); } else { wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_HEADERS; lws_callback_on_writable(wsi); } /* writeability becomes uncertain now we wrote * something, we must return to the event loop */ return 0; } if (!wsi->http.cgi->headers_buf) { /* if we don't already have a headers buf, cook one */ n = 2048; if (wsi->http2_substream) n = 4096; wsi->http.cgi->headers_buf = lws_malloc(n + LWS_PRE, "cgi hdr buf"); if (!wsi->http.cgi->headers_buf) { lwsl_err("OOM\n"); return -1; } lwsl_debug("allocated cgi hdrs\n"); wsi->http.cgi->headers_start = wsi->http.cgi->headers_buf + LWS_PRE; wsi->http.cgi->headers_pos = wsi->http.cgi->headers_start; wsi->http.cgi->headers_dumped = wsi->http.cgi->headers_pos; wsi->http.cgi->headers_end = wsi->http.cgi->headers_buf + n - 1; for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) { wsi->http.cgi->match[n] = 0; wsi->http.cgi->lp = 0; } } n = lws_get_socket_fd(wsi->http.cgi->stdwsi[LWS_STDOUT]); if (n < 0) return -1; n = read(n, &c, 1); if (n < 0) { if (errno != EAGAIN) { lwsl_debug("%s: read says %d\n", __func__, n); return -1; } else n = 0; if (wsi->http.cgi->headers_pos >= wsi->http.cgi->headers_end - 4) { lwsl_notice("CGI hdrs > buf size\n"); return -1; } } if (!n) goto agin; lwsl_debug("-- 0x%02X %c %d %d\n", (unsigned char)c, c, wsi->http.cgi->match[1], wsi->hdr_state); if (!c) return -1; switch (wsi->hdr_state) { case LCHS_HEADER: hdr: for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) { /* * significant headers with * numeric decimal payloads */ if (!significant_hdr[n][wsi->http.cgi->match[n]] && (c >= '0' && c <= '9') && wsi->http.cgi->lp < (int)sizeof(wsi->http.cgi->l) - 1) { wsi->http.cgi->l[wsi->http.cgi->lp++] = c; wsi->http.cgi->l[wsi->http.cgi->lp] = '\0'; switch (n) { case SIGNIFICANT_HDR_CONTENT_LENGTH: wsi->http.cgi->content_length = atoll(wsi->http.cgi->l); break; case SIGNIFICANT_HDR_STATUS: wsi->http.cgi->response_code = atol(wsi->http.cgi->l); lwsl_debug("Status set to %d\n", wsi->http.cgi->response_code); break; default: break; } } /* hits up to the NUL are sticky until next hdr */ if (significant_hdr[n][wsi->http.cgi->match[n]]) { if (tolower(c) == significant_hdr[n][wsi->http.cgi->match[n]]) wsi->http.cgi->match[n]++; else wsi->http.cgi->match[n] = 0; } } /* some cgi only send us \x0a for EOL */ if (c == '\x0a') { wsi->hdr_state = LCHS_SINGLE_0A; *wsi->http.cgi->headers_pos++ = '\x0d'; } *wsi->http.cgi->headers_pos++ = c; if (c == '\x0d') wsi->hdr_state = LCHS_LF1; if (wsi->hdr_state != LCHS_HEADER && !significant_hdr[SIGNIFICANT_HDR_TRANSFER_ENCODING] [wsi->http.cgi->match[ SIGNIFICANT_HDR_TRANSFER_ENCODING]]) { lwsl_info("cgi produced chunked\n"); wsi->http.cgi->explicitly_chunked = 1; } /* presence of Location: mandates 302 retcode */ if (wsi->hdr_state != LCHS_HEADER && !significant_hdr[SIGNIFICANT_HDR_LOCATION][ wsi->http.cgi->match[SIGNIFICANT_HDR_LOCATION]]) { lwsl_debug("CGI: Location hdr seen\n"); wsi->http.cgi->response_code = 302; } break; case LCHS_LF1: *wsi->http.cgi->headers_pos++ = c; if (c == '\x0a') { wsi->hdr_state = LCHS_CR2; break; } /* we got \r[^\n]... it's unreasonable */ lwsl_debug("%s: funny CRLF 0x%02X\n", __func__, (unsigned char)c); return -1; case LCHS_CR2: if (c == '\x0d') { /* drop the \x0d */ wsi->hdr_state = LCHS_LF2; break; } wsi->hdr_state = LCHS_HEADER; for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) wsi->http.cgi->match[n] = 0; wsi->http.cgi->lp = 0; goto hdr; case LCHS_LF2: case LCHS_SINGLE_0A: m = wsi->hdr_state; if (c == '\x0a') { lwsl_debug("Content-Length: %lld\n", (unsigned long long) wsi->http.cgi->content_length); wsi->hdr_state = LHCS_RESPONSE; /* * drop the \0xa ... finalize * will add it if needed (HTTP/1) */ break; } if (m == LCHS_LF2) /* we got \r\n\r[^\n]... unreasonable */ return -1; /* we got \x0anext header, it's reasonable */ *wsi->http.cgi->headers_pos++ = c; wsi->hdr_state = LCHS_HEADER; for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) wsi->http.cgi->match[n] = 0; wsi->http.cgi->lp = 0; break; case LHCS_PAYLOAD: break; } agin: /* ran out of input, ended the hdrs, or filled up the hdrs buf */ if (!n || wsi->hdr_state == LHCS_PAYLOAD) return 0; } /* payload processing */ m = !wsi->http.cgi->implied_chunked && !wsi->http2_substream && !wsi->http.cgi->explicitly_chunked && !wsi->http.cgi->content_length; n = lws_get_socket_fd(wsi->http.cgi->stdwsi[LWS_STDOUT]); if (n < 0) return -1; if (m) { uint8_t term[LWS_PRE + 6]; lwsl_info("%s: zero chunk\n", __func__); memcpy(term + LWS_PRE, (uint8_t *)"0\x0d\x0a\x0d\x0a", 5); if (lws_write(wsi, term + LWS_PRE, 5, LWS_WRITE_HTTP_FINAL) != 5) return -1; wsi->http.cgi->cgi_transaction_over = 1; return 0; } n = read(n, start, sizeof(buf) - LWS_PRE); if (n < 0 && errno != EAGAIN) { lwsl_debug("%s: stdout read says %d\n", __func__, n); return -1; } if (n > 0) { /* if (!wsi->http2_substream && m) { char chdr[LWS_HTTP_CHUNK_HDR_SIZE]; m = lws_snprintf(chdr, LWS_HTTP_CHUNK_HDR_SIZE - 3, "%X\x0d\x0a", n); memmove(start + m, start, n); memcpy(start, chdr, m); memcpy(start + m + n, "\x0d\x0a", 2); n += m + 2; } */ #if defined(LWS_WITH_HTTP2) if (wsi->http2_substream) { struct lws *nwsi = lws_get_network_wsi(wsi); __lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31); if (!nwsi->immortal_substream_count) __lws_set_timeout(nwsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31); } #endif cmd = LWS_WRITE_HTTP; if (wsi->http.cgi->content_length_seen + n == wsi->http.cgi->content_length) cmd = LWS_WRITE_HTTP_FINAL; m = lws_write(wsi, (unsigned char *)start, n, cmd); //lwsl_notice("write %d\n", m); if (m < 0) { lwsl_debug("%s: stdout write says %d\n", __func__, m); return -1; } wsi->http.cgi->content_length_seen += n; } else { if (wsi->cgi_stdout_zero_length) { lwsl_debug("%s: stdout is POLLHUP'd\n", __func__); if (wsi->http2_substream) m = lws_write(wsi, (unsigned char *)start, 0, LWS_WRITE_HTTP_FINAL); else return -1; return 1; } wsi->cgi_stdout_zero_length = 1; } return 0; }
int callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__lws_mirror *pss = (struct per_session_data__lws_mirror *)user; int n, m; int client_sockfd=lws_get_socket_fd(wsi); switch (reason) { case LWS_CALLBACK_ESTABLISHED: lwsl_notice("%s: LWS_CALLBACK_ESTABLISHED\n", __func__); pss->ringbuffer_tail = ringbuffer_head; pss->wsi = wsi; break; case LWS_CALLBACK_PROTOCOL_DESTROY: lwsl_notice("%s: mirror protocol cleaning up\n", __func__); for (n = 0; n < sizeof ringbuffer / sizeof ringbuffer[0]; n++) if (ringbuffer[n].payload) free(ringbuffer[n].payload); break; case LWS_CALLBACK_SERVER_WRITEABLE: //if (close_testing) // break; printf("服务器开始发送数据\n"); websocket_write_back(wsi, msg,-1); // n = lws_write(wsi, (char*)in + LWS_SEND_BUFFER_PRE_PADDING, len, LWS_WRITE_TEXT); // while (pss->ringbuffer_tail != ringbuffer_head) { // m = ringbuffer[pss->ringbuffer_tail].len; // n = lws_write(wsi, (unsigned char *) // ringbuffer[pss->ringbuffer_tail].payload + // LWS_PRE, m, LWS_WRITE_TEXT); // printf("the n is %d, the cilent fd is %d\n",n,client_sockfd); // if (n < 0) { // lwsl_err("ERROR %d writing to mirror socket\n", n); // return -1; // } // if (n < m) // lwsl_err("mirror partial write %d vs %d\n", n, m); // if (pss->ringbuffer_tail == (MAX_MESSAGE_QUEUE - 1)) // pss->ringbuffer_tail = 0; // else // pss->ringbuffer_tail++; // // if (((ringbuffer_head - pss->ringbuffer_tail) & // // (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 15)) // // lws_rx_flow_allow_all_protocol(lws_get_context(wsi), // // lws_get_protocol(wsi)); // // if (lws_send_pipe_choked(wsi)) { // // lws_callback_on_writable(wsi); // // break; // // } // } break; case LWS_CALLBACK_RECEIVE: client_sockfd=lws_get_socket_fd(wsi); lwsl_notice("接收到客户端 %d 的信息=[%s]\n",client_sockfd, (const char*)in); memcpy(msg,in,strlen(in)); if (((ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 1)) { lwsl_err("dropping!\n"); goto choke; } if (ringbuffer[ringbuffer_head].payload) free(ringbuffer[ringbuffer_head].payload); ringbuffer[ringbuffer_head].payload = malloc(LWS_PRE + len); ringbuffer[ringbuffer_head].len = len; memcpy((char *)ringbuffer[ringbuffer_head].payload + LWS_PRE, in, len); if (ringbuffer_head == (MAX_MESSAGE_QUEUE - 1)) ringbuffer_head = 0; else ringbuffer_head++; if (((ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)) != (MAX_MESSAGE_QUEUE - 2)) goto done; choke: lwsl_notice("LWS_CALLBACK_RECEIVE: throttling %p\n", wsi); lws_rx_flow_control(wsi, 0); done: printf("lws_callback_on_writable_all_protocol, the client fd is %d\n", client_sockfd); lws_callback_on_writable_all_protocol(lws_get_context(wsi), lws_get_protocol(wsi)); break; /* * this just demonstrates how to use the protocol filter. If you won't * study and reject connections based on header content, you don't need * to handle this callback */ case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: //dump_handshake_info(wsi); /* you could return non-zero here and kill the connection */ break; default: break; } return 0; }