1
0
mirror of https://github.com/apache/httpd.git synced 2025-08-05 16:55:50 +03:00

mod_lua: Add rudimentary WebSocket support. This is a WIP (emphasis on the W, I and P) and subject to change as the idea surrounding it evolves into something meaningful. But for now, WebSockets, yay! Please do review this!

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1522030 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Daniel Gruno
2013-09-11 20:50:46 +00:00
parent 751b6e7b44
commit 731d18dd4c
3 changed files with 363 additions and 2 deletions

View File

@@ -28,8 +28,7 @@
#include "apr_thread_mutex.h"
#include "apr_tables.h"
#include "util_cookies.h"
#include <lua.h>
#include "apr_want.h"
extern apr_thread_mutex_t* lua_ivm_mutex;
@@ -1930,6 +1929,322 @@ static int lua_set_cookie(lua_State *L)
return 0;
}
static apr_uint64_t ap_ntoh64(const apr_uint64_t *input)
{
apr_uint64_t rval;
unsigned char *data = (unsigned char *)&rval;
data[0] = *input >> 56;
data[1] = *input >> 48;
data[2] = *input >> 40;
data[3] = *input >> 32;
data[4] = *input >> 24;
data[5] = *input >> 16;
data[6] = *input >> 8;
data[7] = *input >> 0;
return rval;
}
static int lua_websocket_greet(lua_State *L)
{
const char *key = NULL;
unsigned char digest[APR_SHA1_DIGESTSIZE];
apr_sha1_ctx_t sha1;
char *encoded;
int encoded_len;
request_rec *r = ap_lua_check_request_rec(L, 1);
key = apr_table_get(r->headers_in, "Sec-WebSocket-Key");
if (key != NULL) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"Websocket: Got websocket key: %s", key);
key = apr_pstrcat(r->pool, key, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
NULL);
apr_sha1_init(&sha1);
apr_sha1_update(&sha1, key, strlen(key));
apr_sha1_final(digest, &sha1);
encoded_len = apr_base64_encode_len(APR_SHA1_DIGESTSIZE);
if (encoded_len) {
encoded = apr_palloc(r->pool, encoded_len);
encoded_len = apr_base64_encode(encoded, (char*) digest, APR_SHA1_DIGESTSIZE);
r->status = 101;
apr_table_set(r->headers_out, "Upgrade", "websocket");
apr_table_set(r->headers_out, "Connection", "Upgrade");
apr_table_set(r->headers_out, "Sec-WebSocket-Accept", encoded);
/* Trick httpd into NOT using the chunked filter, IMPORTANT!!!111*/
apr_table_set(r->headers_out, "Transfer-Encoding", "chunked");
r->clength = 0;
r->bytes_sent = 0;
r->read_chunked = 0;
ap_rflush(r);
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"Websocket: Upgraded from HTTP to Websocket");
lua_pushboolean(L, 1);
return 1;
}
}
ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
"Websocket: Upgrade from HTTP to Websocket failed");
return 0;
}
static int lua_websocket_read(lua_State *L)
{
apr_socket_t *sock;
apr_status_t rv;
int n = 0;
apr_size_t len = 1;
apr_size_t plen = 0;
unsigned short payload_short = 0;
apr_uint64_t payload_long = 0;
unsigned char *mask_bytes;
char byte;
int plaintext;
request_rec *r = ap_lua_check_request_rec(L, 1);
plaintext = ap_lua_ssl_is_https(r->connection) ? 0 : 1;
if (!plaintext) {
ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
"Websocket: WSS protocol reading not yet supported!");
return 0;
}
mask_bytes = apr_pcalloc(r->pool, 4);
sock = ap_get_conn_socket(r->connection);
/* Get opcode and FIN bit */
rv = apr_socket_recv(sock, &byte, &len);
if (rv == APR_SUCCESS) {
unsigned char fin, opcode, mask, payload;
fin = byte >> 7;
opcode = (byte << 4) >> 4;
/* Get the payload length and mask bit */
rv = apr_socket_recv(sock, &byte, &len);
if (rv == APR_SUCCESS) {
mask = byte >> 7;
payload = byte - 128;
plen = payload;
/* Extended payload? */
if (payload == 126) {
len = 2;
rv = apr_socket_recv(sock, (char*) &payload_short, &len);
payload_short = ntohs(payload_short);
if (rv == APR_SUCCESS) {
plen = payload_short;
}
else {
return 0;
}
}
/* Super duper extended payload? */
if (payload == 127) {
len = 8;
rv = apr_socket_recv(sock, (char*) &payload_long, &len);
if (rv == APR_SUCCESS) {
plen = ap_ntoh64(&payload_long);
}
else {
return 0;
}
}
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"Websocket: Reading %lu (%s) bytes, masking is %s. %s",
plen,
(payload >= 126) ? "extra payload" : "no extra payload",
mask ? "on" : "off",
fin ? "This is a final frame" : "more to follow");
if (mask) {
len = 4;
rv = apr_socket_recv(sock, (char*) mask_bytes, &len);
if (rv != APR_SUCCESS) {
return 0;
}
}
if (plen < (HUGE_STRING_LEN*1024) && plen > 0) {
apr_size_t remaining = plen;
apr_size_t received;
apr_off_t at = 0;
char *buffer = apr_palloc(r->pool, plen+1);
buffer[plen] = 0;
if (plaintext) {
while (remaining > 0) {
received = remaining;
rv = apr_socket_recv(sock, buffer+at, &received);
if (received > 0 ) {
remaining -= received;
at += received;
}
}
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
"Websocket: Frame contained %lu bytes, pushed to Lua stack",
at);
}
else {
at = ap_get_client_block(r, buffer, plen);
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
"Websocket: SSL Frame contained %lu bytes, "\
"pushed to Lua stack",
at);
}
if (mask) {
for (n = 0; n < plen; n++) {
buffer[n] ^= mask_bytes[n%4];
}
}
lua_pushlstring(L, buffer, (size_t) plen); /* push string to stack */
lua_pushboolean(L, fin); /* push FIN bit to stack as boolean */
return 2;
}
/* Decide if we need to react to the opcode or not */
if (opcode == 0x09) { /* ping */
char frame[2];
plen = 2;
frame[0] = 0x8A;
frame[1] = 0;
apr_socket_send(sock, frame, &plen); /* Pong! */
lua_websocket_read(L); /* read the next frame instead */
}
}
}
return 0;
}
static int lua_websocket_write(lua_State *L)
{
const char *string;
apr_status_t rv;
size_t len;
int raw = 0;
char prelude;
request_rec *r = ap_lua_check_request_rec(L, 1);
if (lua_isboolean(L, 3)) {
raw = lua_toboolean(L, 3);
}
string = lua_tolstring(L, 2, &len);
if (raw != 1) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"Websocket: Writing framed message to client");
prelude = 0x81; /* text frame, FIN */
ap_rputc(prelude, r);
if (len < 126) {
ap_rputc(len, r);
}
else if (len < 65535) {
unsigned short c = len;
ap_rputc(126, r);
c = htons(c);
ap_rwrite((char*) &c, 2, r);
}
else {
apr_uint64_t llen = len;
ap_rputc(127, r);
llen = ap_ntoh64(&len); /* ntoh doubles as hton */
ap_rwrite((char*) &llen, 8, r);
}
}
else {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"Websocket: Writing raw message to client");
}
ap_rwrite(string, len, r);
rv = ap_rflush(r);
if (rv == APR_SUCCESS) {
lua_pushboolean(L, 1);
}
else {
lua_pushboolean(L, 0);
}
return 1;
}
static int lua_websocket_close(lua_State *L)
{
apr_socket_t *sock;
apr_size_t plen;
char prelude[2];
request_rec *r = ap_lua_check_request_rec(L, 1);
sock = ap_get_conn_socket(r->connection);
/* Send a header that says: socket is closing. */
prelude[0] = 0x88; /* closing socket opcode */
prelude[1] = 0; /* zero length frame */
plen = 2;
apr_socket_send(sock, prelude, &plen);
/* Close up tell the MPM and filters to back off */
apr_socket_close(sock);
r->output_filters = NULL;
r->connection->keepalive = AP_CONN_CLOSE;
return 0;
}
static int lua_websocket_ping(lua_State *L)
{
apr_socket_t *sock;
apr_size_t plen;
char prelude[2];
apr_status_t rv;
request_rec *r = ap_lua_check_request_rec(L, 1);
sock = ap_get_conn_socket(r->connection);
/* Send a header that says: PING. */
prelude[0] = 0x89; /* ping opcode */
prelude[1] = 0;
plen = 2;
apr_socket_send(sock, prelude, &plen);
/* Get opcode and FIN bit from pong */
plen = 2;
rv = apr_socket_recv(sock, prelude, &plen);
if (rv == APR_SUCCESS) {
unsigned char opcode = prelude[0];
unsigned char len = prelude[1];
unsigned char mask = len >> 7;
if (mask) len -= 128;
plen = len;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"Websocket: Got PONG opcode: %x", opcode);
if (opcode == 0x8A) {
lua_pushboolean(L, 1);
}
else {
lua_pushboolean(L, 0);
}
if (plen > 0) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
"Websocket: Reading %lu bytes of PONG", plen);
return 1;
}
if (mask) {
plen = 2;
apr_socket_recv(sock, prelude, &plen);
plen = 2;
apr_socket_recv(sock, prelude, &plen);
}
}
else {
lua_pushboolean(L, 0);
}
return 1;
}
#define APLUA_REQ_TRACE(lev) static int req_trace##lev(lua_State *L) \
{ \
@@ -2285,6 +2600,16 @@ void ap_lua_load_request_lmodule(lua_State *L, apr_pool_t *p)
makefun(&lua_get_cookie, APL_REQ_FUNTYPE_LUACFUN, p));
apr_hash_set(dispatch, "setcookie", APR_HASH_KEY_STRING,
makefun(&lua_set_cookie, APL_REQ_FUNTYPE_LUACFUN, p));
apr_hash_set(dispatch, "wsupgrade", APR_HASH_KEY_STRING,
makefun(&lua_websocket_greet, APL_REQ_FUNTYPE_LUACFUN, p));
apr_hash_set(dispatch, "wsread", APR_HASH_KEY_STRING,
makefun(&lua_websocket_read, APL_REQ_FUNTYPE_LUACFUN, p));
apr_hash_set(dispatch, "wswrite", APR_HASH_KEY_STRING,
makefun(&lua_websocket_write, APL_REQ_FUNTYPE_LUACFUN, p));
apr_hash_set(dispatch, "wsclose", APR_HASH_KEY_STRING,
makefun(&lua_websocket_close, APL_REQ_FUNTYPE_LUACFUN, p));
apr_hash_set(dispatch, "wsping", APR_HASH_KEY_STRING,
makefun(&lua_websocket_ping, APL_REQ_FUNTYPE_LUACFUN, p));
lua_pushlightuserdata(L, dispatch);
lua_setfield(L, LUA_REGISTRYINDEX, "Apache2.Request.dispatch");