]> granicus.if.org Git - apache/commitdiff
Backport a few things in mod_lua:
authorDaniel Gruno <humbedooh@apache.org>
Tue, 17 Sep 2013 10:38:12 +0000 (10:38 +0000)
committerDaniel Gruno <humbedooh@apache.org>
Tue, 17 Sep 2013 10:38:12 +0000 (10:38 +0000)
- Fix filters
- Add setcookie/getcookie
- Add preliminary WebSocket support
- Update documentation

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1523974 13f79535-47bb-0310-9956-ffa450edef68

docs/manual/mod/mod_lua.xml
modules/lua/lua_request.c
modules/lua/mod_lua.c

index 1726401faa5fa9c5201a6efcd7cecbe780cf1f8b..f357ab9031953c09217dd3e13e4bb1de4498f32f 100644 (file)
@@ -269,9 +269,10 @@ performing access control, or setting mime types:</p>
     </tr>
     <tr>
         <td>Logging</td>
-        <td>(none)</td>
+        <td><directive module="mod_lua">LuaHookLog</directive></td>
         <td>Once a request has been handled, it enters several logging phases, 
-            which logs the request in either the error or access log</td>
+            which logs the request in either the error or access log. Mod_lua
+            is able to hook into the start of this and control logging output.</td>
     </tr>
 
 </table>
@@ -943,7 +944,7 @@ r:rmdir(dir) -- Removes a directory.
 </highlight>
 
 <highlight language="lua">
-r:touch([mtime]) -- Sets the file modification time to current time or to optional mtime msec value.
+r:touch(file [,mtime]) -- Sets the file modification time to current time or to optional mtime msec value.
 </highlight>
 
 <highlight language="lua">
@@ -966,6 +967,48 @@ end
 r.date_parse_rfc(string) -- Parses a date/time string and returns seconds since epoche.
 </highlight>
 
+<highlight language="lua">
+r:getcookie(key) -- Gets a HTTP cookie
+</highlight>
+
+<highlight language="lua">
+r:setcookie(key, value, secure, expires) -- Sets a HTTP cookie, for instance:
+r:setcookie("foo", "bar and stuff", false, os.time() + 86400)
+</highlight>
+
+<highlight language="lua">
+r:wsupgrade() -- Upgrades a connection to WebSockets if possible (and requested):
+if r:wsupgrade() then -- if we can upgrade:
+    r:wswrite("Welcome to websockets!") -- write something to the client
+    r:wsclose()  -- goodbye!
+end
+</highlight>
+
+<highlight language="lua">
+r:wsread() -- Reads a WebSocket frame from a WebSocket upgraded connection (see above):
+
+local line, isFinal = r:wsread() -- isFinal denotes whether this is the final frame.
+                                 -- If it isn't, then more frames can be read
+r:wswrite("You wrote: " .. line)
+</highlight>
+
+<highlight language="lua">
+r:wswrite(line) -- Writes a frame to a WebSocket client:
+r:wswrite("Hello, world!")
+</highlight>
+
+<highlight language="lua">
+r:wsclose() -- Closes a WebSocket request and terminates it for httpd:
+
+if r:wsupgrade() then
+    r:wswrite("Write something: ")
+    local line = r:wsread() or "nothing"
+    r:wswrite("You wrote: " .. line);
+    r:wswrite("Goodbye!")
+    r:wsclose()
+end
+</highlight>
+
 </section>
 
 <section id="logging"><title>Logging Functions</title>
@@ -1453,6 +1496,55 @@ processing</description>
 </usage>
 </directivesynopsis>
 
+<directivesynopsis>
+<name>LuaHookLog</name>
+<description>Provide a hook for the access log phase of a request
+processing</description>
+<syntax>LuaHookLog  /path/to/lua/script.lua log_function_name</syntax>
+<contextlist><context>server config</context><context>virtual host</context>
+<context>directory</context><context>.htaccess</context>
+</contextlist>
+<override>All</override>
+<usage>
+<p>
+    This simple logging hook allows you to run a function when httpd enters the 
+    logging phase of a request. With it, you can append data to your own logs, 
+    manipulate data before the regular log is written, or prevent a log entry 
+    from being created. To prevent the usual logging from happening, simply return
+    <code>apache2.DONE</code> in your logging handler, otherwise return 
+    <code>apache2.OK</code> to tell httpd to log as normal.
+</p>
+<p>Example:</p>
+<highlight language="config">
+LuaHookLog /path/to/script.lua logger
+</highlight>
+<highlight language="lua">
+-- /path/to/script.lua --
+function logger(r)
+    -- flip a coin:
+    -- If 1, then we write to our own Lua log and tell httpd not to log
+    -- in the main log.
+    -- If 2, then we just sanitize the output a bit and tell httpd to 
+    -- log the sanitized bits.
+
+    if math.random(1,2) == 1 then
+        -- Log stuff ourselves and don't log in the regular log
+        local f = io.open("/foo/secret.log", "a")
+        if f then
+            f:write("Something secret happened at " .. r.uri .. "\n")
+            f:close()
+        end
+        return apache2.DONE -- Tell httpd not to use the regular logging functions
+    else
+        r.uri = r.uri:gsub("somesecretstuff", "") -- sanitize the URI
+        return apache2.OK -- tell httpd to log it.
+    end
+end
+</highlight>
+</usage>
+</directivesynopsis>
+
+
 <directivesynopsis>
 <name>LuaHookMapToStorage</name>
 <description>Provide a hook for the map_to_storage phase of request processing</description>
@@ -1811,10 +1903,17 @@ function output_filter(r)
     ... -- insert filter stuff here
 end
 </highlight>
+<note><title>Lua filters with <module>mod_filter</module></title>
+<p> When a Lua filter is used as the underlying provider via the 
+<directive module="mod_filter">FilterProvider</directive> directive, filtering 
+will only work when the <var>filter-name</var> is identical to the <var>provider-name</var>.
+</p> </note>
+
 <p>
 See "<a href="#modifying_buckets">Modifying contents with Lua filters</a>" for more 
 information.
 </p>
+
 </usage>
 </directivesynopsis>
 
index eb6aef1d164a4a8a39de00f977ef5208dc622f6f..23efcc2a4e721faf53fb129188e4c6237491b103 100644 (file)
@@ -26,8 +26,9 @@
 #include "apr_date.h"
 #include "apr_pools.h"
 #include "apr_thread_mutex.h"
-
-#include <lua.h>
+#include "apr_tables.h"
+#include "util_cookies.h"
+#include "apr_want.h"
 
 extern apr_thread_mutex_t* lua_ivm_mutex;
 
@@ -839,6 +840,7 @@ static int lua_apr_sha1(lua_State *L)
     apr_sha1_init(&sha1);
     apr_sha1_update(&sha1, buffer, len);
     apr_sha1_final(digest, &sha1);
+    
     ap_bin2hex(digest, sizeof(digest), result);
     lua_pushstring(L, result);
     return 1;
@@ -1887,6 +1889,411 @@ static int lua_ivm_set(lua_State *L)
     return 0;
 }
 
+static int lua_get_cookie(lua_State *L) 
+{
+    const char *key, *cookie;
+    request_rec *r = ap_lua_check_request_rec(L, 1);
+    key = luaL_checkstring(L, 2);
+    cookie = NULL;
+    ap_cookie_read(r, key, &cookie, 0);
+    if (cookie != NULL) {
+        lua_pushstring(L, cookie);
+        return 1;
+    }
+    return 0;
+}
+
+static int lua_set_cookie(lua_State *L) 
+{
+    const char *key, *value, *out, *strexpires;
+    int secure, expires;
+    char cdate[APR_RFC822_DATE_LEN+1];
+    apr_status_t rv;
+    request_rec *r = ap_lua_check_request_rec(L, 1);
+    key = luaL_checkstring(L, 2);
+    value = luaL_checkstring(L, 3);
+    secure = 0;
+    if (lua_isboolean(L, 4)) {
+        secure = lua_toboolean(L, 4);
+    }
+    expires = luaL_optinteger(L, 5, 0);
+    strexpires = "";
+    if (expires > 0) {
+        rv = apr_rfc822_date(cdate, apr_time_from_sec(expires));
+        if (rv == APR_SUCCESS) {
+            strexpires = apr_psprintf(r->pool, "Expires=%s", cdate);
+        }
+    }
+    out = apr_psprintf(r->pool, "%s=%s; %s %s", key, value, secure ? "Secure;" : "", expires ? strexpires : "");
+    apr_table_set(r->headers_out, "Set-Cookie", out);
+    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 apr_status_t lua_websocket_readbytes(conn_rec* c, char* buffer, 
+        apr_off_t len) 
+{
+    apr_bucket_brigade *brigade = apr_brigade_create(c->pool, c->bucket_alloc);
+    apr_status_t rv;
+    rv = ap_get_brigade(c->input_filters, brigade, AP_MODE_READBYTES, 
+            APR_BLOCK_READ, len);
+    if (rv == APR_SUCCESS) {
+        if (!APR_BRIGADE_EMPTY(brigade)) {
+            apr_bucket* bucket = APR_BRIGADE_FIRST(brigade);
+            const char* data = NULL;
+            apr_size_t data_length = 0;
+            rv = apr_bucket_read(bucket, &data, &data_length, APR_BLOCK_READ);
+            if (rv == APR_SUCCESS) {
+                memcpy(buffer, data, len);
+            }
+            apr_bucket_delete(bucket);
+        }
+    }
+    apr_brigade_cleanup(brigade);
+    return rv;
+}
+
+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 = (request_rec *) lua_unboxpointer(L, 1);
+    plaintext = ap_lua_ssl_is_https(r->connection) ? 0 : 1;
+
+    
+    mask_bytes = apr_pcalloc(r->pool, 4);
+    sock = ap_get_conn_socket(r->connection);
+
+    /* Get opcode and FIN bit */
+    if (plaintext) {
+        rv = apr_socket_recv(sock, &byte, &len);
+    }
+    else {
+        rv = lua_websocket_readbytes(r->connection, &byte, 1);
+    }
+    if (rv == APR_SUCCESS) {
+        unsigned char fin, opcode, mask, payload;
+        fin = byte >> 7;
+        opcode = (byte << 4) >> 4;
+        
+        /* Get the payload length and mask bit */
+        if (plaintext) {
+            rv = apr_socket_recv(sock, &byte, &len);
+        }
+        else {
+            rv = lua_websocket_readbytes(r->connection, &byte, 1);
+        }
+        if (rv == APR_SUCCESS) {
+            mask = byte >> 7;
+            payload = byte - 128;
+            plen = payload;
+            
+            /* Extended payload? */
+            if (payload == 126) {
+                len = 2;
+                if (plaintext) {
+                    rv = apr_socket_recv(sock, (char*) &payload_short, &len);
+                }
+                else {
+                    rv = lua_websocket_readbytes(r->connection, 
+                        (char*) &payload_short, 2);
+                }
+                payload_short = ntohs(payload_short);
+                
+                if (rv == APR_SUCCESS) {
+                    plen = payload_short;
+                }
+                else {
+                    return 0;
+                }
+            }
+            /* Super duper extended payload? */
+            if (payload == 127) {
+                len = 8;
+                if (plaintext) {
+                    rv = apr_socket_recv(sock, (char*) &payload_long, &len);
+                }
+                else {
+                    rv = lua_websocket_readbytes(r->connection, 
+                            (char*) &payload_long, 8);
+                }
+                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;
+                if (plaintext) {
+                    rv = apr_socket_recv(sock, (char*) mask_bytes, &len);
+                }
+                else {
+                    rv = lua_websocket_readbytes(r->connection, 
+                            (char*) mask_bytes, 4);
+                }
+                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 {
+                    rv = lua_websocket_readbytes(r->connection, buffer, 
+                            remaining);
+                    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, 
+                    "Websocket: SSL Frame contained %lu bytes, "\
+                            "pushed to Lua stack", 
+                        remaining);
+                }
+                if (mask) {
+                    for (n = 0; n < plen; n++) {
+                        buffer[n] ^= mask_bytes[n%4];
+                    }
+                }
+                
+                lua_pushlstring(L, buffer, (size_t) plen); /* push 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 = (request_rec *) lua_unboxpointer(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) {
+            apr_uint16_t slen = len;
+            ap_rputc(126, r); 
+            slen = htons(slen);
+            ap_rwrite((char*) &slen, 2, r);
+        }
+        else {
+            apr_uint64_t llen = len;
+            ap_rputc(127, r);
+            llen = ap_ntoh64(&llen); /* 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;
+    char prelude[2];
+    request_rec *r = (request_rec *) lua_unboxpointer(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 */
+    ap_rwrite(prelude, 2, r);
+    
+    /* Close up tell the MPM and filters to back off */
+    apr_socket_close(sock);
+    r->output_filters = NULL;
+    r->connection->keepalive = AP_CONN_CLOSE;
+    ap_destroy_sub_req(r);
+    return DONE;
+}
+
+
+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)  \
 {                                                               \
     return req_log_at(L, APLOG_TRACE##lev);                     \
@@ -1994,6 +2401,7 @@ static const char* lua_ap_get_server_name(request_rec* r)
 
 
 
+
 static const struct luaL_Reg server_methods[] = {
     {NULL, NULL}
 };
@@ -2236,7 +2644,22 @@ void ap_lua_load_request_lmodule(lua_State *L, apr_pool_t *p)
                  makefun(&lua_ivm_get, APL_REQ_FUNTYPE_LUACFUN, p));
     apr_hash_set(dispatch, "ivm_set", APR_HASH_KEY_STRING,
                  makefun(&lua_ivm_set, APL_REQ_FUNTYPE_LUACFUN, p));
-    
+    apr_hash_set(dispatch, "getcookie", APR_HASH_KEY_STRING,
+                 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");
 
index 7c35011ec1e746ea02ab67a39c5e2836a6615e20..6e3390fbd2f346668c4f655821ec0c10e2a66fca 100644 (file)
@@ -318,7 +318,10 @@ static apr_status_t lua_setup_filter_ctx(ap_filter_t* f, request_rec* r, lua_fil
     ctx = apr_pcalloc(r->pool, sizeof(lua_filter_ctx));
     ctx->broken = 0;
     *c = ctx;
-    /* Find the filter that was called */
+    /* Find the filter that was called.
+     * XXX: If we were wired with mod_filter, the filter (mod_filters name)
+     *      and the provider (our underlying filters name) need to have matched.
+     */
     for (n = 0; n < cfg->mapped_filters->nelts; n++) {
         ap_lua_filter_handler_spec *hook_spec =
             ((ap_lua_filter_handler_spec **) cfg->mapped_filters->elts)[n];
@@ -374,6 +377,12 @@ static apr_status_t lua_setup_filter_ctx(ap_filter_t* f, request_rec* r, lua_fil
              */
             rc = lua_resume(L, 1);
             if (rc == LUA_YIELD) {
+                if (f->frec->providers == NULL) { 
+                    /* Not wired by mod_filter */
+                    apr_table_unset(r->headers_out, "Content-Length");
+                    apr_table_unset(r->headers_out, "Content-MD5");
+                    apr_table_unset(r->headers_out, "ETAG");
+                }
                 return OK;
             }
             else {
@@ -407,8 +416,25 @@ static apr_status_t lua_output_filter_handle(ap_filter_t *f, apr_bucket_brigade
             ap_remove_output_filter(f);
             return ap_pass_brigade(f->next,pbbIn);
         }
-        f->ctx = ctx;
-        ctx->tmpBucket = apr_brigade_create(r->pool, c->bucket_alloc);
+        else { 
+            /* We've got a willing lua filter, setup and check for a prefix */
+            size_t olen;
+            apr_bucket *pbktOut;
+            const char* output = lua_tolstring(ctx->L, 1, &olen);
+
+            f->ctx = ctx;
+            ctx->tmpBucket = apr_brigade_create(r->pool, c->bucket_alloc);
+
+            if (olen > 0) { 
+                pbktOut = apr_bucket_heap_create(output, olen, NULL, c->bucket_alloc);
+                APR_BRIGADE_INSERT_TAIL(ctx->tmpBucket, pbktOut);
+                rv = ap_pass_brigade(f->next, ctx->tmpBucket);
+                apr_brigade_cleanup(ctx->tmpBucket);
+                if (rv != APR_SUCCESS) {
+                    return rv;
+                }
+            }
+        }
     }
     ctx = (lua_filter_ctx*) f->ctx;
     L = ctx->L;
@@ -433,13 +459,15 @@ static apr_status_t lua_output_filter_handle(ap_filter_t *f, apr_bucket_brigade
             if (lua_resume(L, 0) == LUA_YIELD) {
                 size_t olen;
                 const char* output = lua_tolstring(L, 1, &olen);
-                pbktOut = apr_bucket_heap_create(output, olen, NULL,
-                                        c->bucket_alloc);
-                APR_BRIGADE_INSERT_TAIL(ctx->tmpBucket, pbktOut);
-                rv = ap_pass_brigade(f->next, ctx->tmpBucket);
-                apr_brigade_cleanup(ctx->tmpBucket);
-                if (rv != APR_SUCCESS) {
-                    return rv;
+                if (olen > 0) { 
+                    pbktOut = apr_bucket_heap_create(output, olen, NULL,
+                                            c->bucket_alloc);
+                    APR_BRIGADE_INSERT_TAIL(ctx->tmpBucket, pbktOut);
+                    rv = ap_pass_brigade(f->next, ctx->tmpBucket);
+                    apr_brigade_cleanup(ctx->tmpBucket);
+                    if (rv != APR_SUCCESS) {
+                        return rv;
+                    }
                 }
             }
             else {
@@ -461,9 +489,11 @@ static apr_status_t lua_output_filter_handle(ap_filter_t *f, apr_bucket_brigade
                 apr_bucket *pbktOut;
                 size_t olen;
                 const char* output = lua_tolstring(L, 1, &olen);
-                pbktOut = apr_bucket_heap_create(output, olen, NULL,
-                                        c->bucket_alloc);
-                APR_BRIGADE_INSERT_TAIL(ctx->tmpBucket, pbktOut);
+                if (olen > 0) { 
+                    pbktOut = apr_bucket_heap_create(output, olen, NULL,
+                            c->bucket_alloc);
+                    APR_BRIGADE_INSERT_TAIL(ctx->tmpBucket, pbktOut);
+                }
             }
             pbktEOS = apr_bucket_eos_create(c->bucket_alloc);
             APR_BRIGADE_INSERT_TAIL(ctx->tmpBucket, pbktEOS);
@@ -661,6 +691,13 @@ static int lua_request_rec_hook_harness(request_rec *r, const char *name, int ap
             rc = DECLINED;
             if (lua_isnumber(L, -1)) {
                 rc = lua_tointeger(L, -1);
+                ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r, "Lua hook %s:%s for phase %s returned %d", 
+                              hook_spec->file_name, hook_spec->function_name, name, rc);
+            }
+            else { 
+                ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, "Lua hook %s:%s for phase %s did not return a numeric value", 
+                              hook_spec->file_name, hook_spec->function_name, name);
+                return HTTP_INTERNAL_SERVER_ERROR;
             }
             if (rc != DECLINED) {
                 ap_lua_release_state(L, spec, r);
@@ -1076,7 +1113,8 @@ static const char *register_filter_function_hook(const char *filter,
     /* TODO: Make it work on other types than just AP_FTYPE_RESOURCE? */
     if (direction == AP_LUA_FILTER_OUTPUT) {
         spec->direction = AP_LUA_FILTER_OUTPUT;
-        ap_register_output_filter(filter, lua_output_filter_handle, NULL, AP_FTYPE_RESOURCE);
+        ap_register_output_filter_protocol(filter, lua_output_filter_handle, NULL, AP_FTYPE_RESOURCE,
+                                            AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH);
     }
     else {
         spec->direction = AP_LUA_FILTER_INPUT;
@@ -1155,6 +1193,11 @@ static void lua_insert_filter_harness(request_rec *r)
     /* ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "LuaHookInsertFilter not yet implemented"); */
 }
 
+static int lua_log_transaction_harness(request_rec *r)
+{
+    return lua_request_rec_hook_harness(r, "log_transaction", APR_HOOK_FIRST);
+}
+
 static int lua_quick_harness(request_rec *r, int lookup)
 {
     if (lookup) {
@@ -1219,6 +1262,15 @@ static const char *register_map_to_storage_hook(cmd_parms *cmd, void *_cfg,
     return register_named_file_function_hook("map_to_storage", cmd, _cfg,
                                              file, function, APR_HOOK_MIDDLE);
 }
+
+static const char *register_log_transaction_hook(cmd_parms *cmd, void *_cfg,
+                                                const char *file,
+                                                const char *function)
+{
+    return register_named_file_function_hook("log_transaction", cmd, _cfg,
+                                             file, function, APR_HOOK_FIRST);
+}
+
 static const char *register_map_to_storage_block(cmd_parms *cmd, void *_cfg,
                                                  const char *line)
 {
@@ -1226,6 +1278,7 @@ static const char *register_map_to_storage_block(cmd_parms *cmd, void *_cfg,
                                               line);
 }
 
+
 static const char *register_check_user_id_hook(cmd_parms *cmd, void *_cfg,
                                                const char *file,
                                                const char *function,
@@ -1783,6 +1836,10 @@ command_rec lua_commands[] = {
     AP_INIT_TAKE2("LuaHookInsertFilter", register_insert_filter_hook, NULL,
                   OR_ALL,
                   "Provide a hook for the insert_filter phase of request processing"),
+    
+    AP_INIT_TAKE2("LuaHookLog", register_log_transaction_hook, NULL,
+                  OR_ALL,
+                  "Provide a hook for the logging phase of request processing"),
 
     AP_INIT_TAKE123("LuaScope", register_lua_scope, NULL, OR_ALL,
                     "One of once, request, conn, server -- default is once"),
@@ -1983,6 +2040,10 @@ static void lua_register_hooks(apr_pool_t *p)
     
     /* ivm mutex */
     apr_thread_mutex_create(&lua_ivm_mutex, APR_THREAD_MUTEX_DEFAULT, p);
+    
+    /* Logging catcher */
+    ap_hook_log_transaction(lua_log_transaction_harness,NULL,NULL,
+                            APR_HOOK_FIRST);
 }
 
 AP_DECLARE_MODULE(lua) = {