You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by jp...@apache.org on 2012/06/15 06:08:58 UTC

[2/2] git commit: TS-1302: Initial lua remap plugin

TS-1302: Initial lua remap plugin


Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/7722bb1b
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/7722bb1b
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/7722bb1b

Branch: refs/heads/master
Commit: 7722bb1b9f9431ed5a4aee75be23113e1c5241cb
Parents: 01f0e3f
Author: James Peach <jp...@apache.org>
Authored: Wed Apr 4 22:10:02 2012 -0700
Committer: James Peach <jp...@apache.org>
Committed: Thu Jun 14 21:07:44 2012 -0700

----------------------------------------------------------------------
 build/lua.m4            |  140 +++++++++++++
 configure.ac            |   10 +
 plugins/Makefile.am     |    2 +-
 plugins/lua/Makefile.am |   33 +++
 plugins/lua/example.lua |   63 ++++++
 plugins/lua/lapi.cc     |  465 ++++++++++++++++++++++++++++++++++++++++++
 plugins/lua/lapi.h      |   48 +++++
 plugins/lua/lua.cc      |  218 ++++++++++++++++++++
 plugins/lua/lutil.cc    |   80 +++++++
 plugins/lua/lutil.h     |   25 +++
 10 files changed, 1083 insertions(+), 1 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7722bb1b/build/lua.m4
----------------------------------------------------------------------
diff --git a/build/lua.m4 b/build/lua.m4
new file mode 100644
index 0000000..71e4e29
--- /dev/null
+++ b/build/lua.m4
@@ -0,0 +1,140 @@
+dnl -------------------------------------------------------- -*- autoconf -*-
+dnl Licensed to the Apache Software Foundation (ASF) under one or more
+dnl contributor license agreements.  See the NOTICE file distributed with
+dnl this work for additional information regarding copyright ownership.
+dnl The ASF licenses this file to You under the Apache License, Version 2.0
+dnl (the "License"); you may not use this file except in compliance with
+dnl the License.  You may obtain a copy of the License at
+dnl
+dnl     http://www.apache.org/licenses/LICENSE-2.0
+dnl
+dnl Unless required by applicable law or agreed to in writing, software
+dnl distributed under the License is distributed on an "AS IS" BASIS,
+dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+dnl See the License for the specific language governing permissions and
+dnl limitations under the License.
+
+dnl Check for Lua 5.1 Libraries
+dnl
+dnl CHECK_LUA(ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND])
+dnl Sets:
+dnl  LUA_CFLAGS
+dnl  LUA_LIBS
+AC_DEFUN([CHECK_LUA],
+[dnl
+
+AC_ARG_WITH(
+    lua,
+    [AC_HELP_STRING([--with-lua=PATH],[Path to the Lua 5.1 prefix])],
+    lua_path="$withval",
+    :)
+
+dnl # Determine lua lib directory
+if test -z "$lua_path"; then
+    test_paths=". /usr/local /usr"
+else
+    test_paths="${lua_path}"
+fi
+
+AC_CHECK_LIB(m, pow, lib_m="-lm")
+AC_CHECK_LIB(m, sqrt, lib_m="-lm")
+for x in $test_paths ; do
+  if test "x$x" = "x."; then
+    AC_CHECK_HEADER(lua.h,[
+        save_CFLAGS=$CFLAGS
+        save_LDFLAGS=$LDFLAGS
+        CFLAGS="$CFLAGS"
+        LDFLAGS="$LDFLAGS $lib_m"
+        AC_CHECK_LIB(lua5.1, luaL_newstate, [
+            LUA_LIBS="-llua5.1 $lib_m"
+        ],[
+            AC_CHECK_LIB(lua-5.1, luaL_newstate, [
+                LUA_LIBS="-llua-5.1 $lib_m"
+            ],[
+                AC_CHECK_LIB(lua, luaL_newstate, [
+                    LUA_LIBS="-llua $lib_m"
+                ])
+            ])
+        ])
+        LUA_CFLAGS=
+        CFLAGS=$save_CFLAGS
+        LDFLAGS=$save_LDFLAGS
+        break
+    ])
+  else
+    AC_MSG_CHECKING([for lua.h in ${x}/include/lua5.1])
+    if test -f ${x}/include/lua5.1/lua.h; then
+        AC_MSG_RESULT([yes])
+        save_CFLAGS=$CFLAGS
+        save_LDFLAGS=$LDFLAGS
+        CFLAGS="$CFLAGS"
+        LDFLAGS="-L$x/lib $LDFLAGS $lib_m"
+        AC_CHECK_LIB(lua5.1, luaL_newstate, [
+            LUA_LIBS="-L$x/lib -llua5.1 $lib_m"
+            LUA_CFLAGS="-I$x/include/lua5.1"
+            ])
+        CFLAGS=$save_CFLAGS
+        LDFLAGS=$save_LDFLAGS
+        break
+    else
+        AC_MSG_RESULT([no])
+    fi
+    AC_MSG_CHECKING([for lua.h in ${x}/include/lua51])
+    if test -f ${x}/include/lua51/lua.h; then
+        AC_MSG_RESULT([yes])
+        save_CFLAGS=$CFLAGS
+        save_LDFLAGS=$LDFLAGS
+        CFLAGS="$CFLAGS"
+        LDFLAGS="-L$x/lib/lua51 $LDFLAGS $lib_m"
+        AC_CHECK_LIB(lua, luaL_newstate, [
+            LUA_LIBS="-L$x/lib/lua51 -llua $lib_m"
+            LUA_CFLAGS="-I$x/include/lua51"
+            ])
+        CFLAGS=$save_CFLAGS
+        LDFLAGS=$save_LDFLAGS
+        break
+    else
+        AC_MSG_RESULT([no])
+    fi
+    AC_MSG_CHECKING([for lua.h in ${x}/include])
+    if test -f ${x}/include/lua.h; then
+        AC_MSG_RESULT([yes])
+        save_CFLAGS=$CFLAGS
+        save_LDFLAGS=$LDFLAGS
+        CFLAGS="$CFLAGS"
+        LDFLAGS="-L$x/lib $LDFLAGS $lib_m"
+        AC_CHECK_LIB(lua, luaL_newstate, [
+            LUA_LIBS="-L$x/lib -llua $lib_m"
+            LUA_CFLAGS="-I$x/include"
+            ])
+        CFLAGS=$save_CFLAGS
+        LDFLAGS=$save_LDFLAGS
+        break
+    else
+        AC_MSG_RESULT([no])
+    fi
+  fi
+done
+
+AC_SUBST(LUA_LIBS)
+AC_SUBST(LUA_CFLAGS)
+
+if test -z "${LUA_LIBS}"; then
+  AC_MSG_WARN([*** Lua 5.1 library not found.])
+  ifelse([$2], ,
+    enable_lua="no"
+    if test -z "${lua_path}"; then
+        AC_MSG_WARN([Lua 5.1 library is required])
+    else
+        AC_MSG_ERROR([Lua 5.1 library is required])
+    fi,
+    $2)
+else
+  AC_MSG_NOTICE([using '${LUA_LIBS}' for Lua Library])
+  AC_ARG_ENABLE(luajit,
+    APACHE_HELP_STRING(--enable-luajit,Enable LuaJit Support),
+    TS_ADDTO(CPPFLAGS, ["-DTS_ENABLE_LUAJIT"]))
+  ifelse([$1], , , $1) 
+fi 
+])
+

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7722bb1b/configure.ac
----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 94e93e2..ab30adf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1063,6 +1063,15 @@ AC_SUBST(arg_max)
 # Check for libreadline/libedit
 AX_LIB_READLINE
 
+#
+# Check for Lua, at least version 5.1, sets LUA_CFLAGS and LUA_LIBS.
+CHECK_LUA(
+  [ enable_lua_support="yes" ],
+  [ enable_lua_support="no" ]
+)
+
+AM_CONDITIONAL([BUILD_LUA_SUPPORT], [ test "x${enable_lua_support}" = "xyes" ])
+
 # -----------------------------------------------------------------------------
 # 5. CHECK FOR HEADER FILES
 
@@ -1459,6 +1468,7 @@ AC_CONFIG_FILES([plugins/conf_remap/Makefile])
 AC_CONFIG_FILES([plugins/regex_remap/Makefile])
 AC_CONFIG_FILES([plugins/header_filter/Makefile])
 AC_CONFIG_FILES([plugins/stats_over_http/Makefile])
+AC_CONFIG_FILES([plugins/lua/Makefile])
 # various tools
 AC_CONFIG_FILES([tools/Makefile])
 # example plugins

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7722bb1b/plugins/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 14d0bf9..6d3d433 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -14,4 +14,4 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-SUBDIRS = conf_remap regex_remap header_filter stats_over_http
+SUBDIRS = conf_remap regex_remap header_filter stats_over_http lua

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7722bb1b/plugins/lua/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/lua/Makefile.am b/plugins/lua/Makefile.am
new file mode 100644
index 0000000..81fb201
--- /dev/null
+++ b/plugins/lua/Makefile.am
@@ -0,0 +1,33 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+pkglibdir = ${pkglibexecdir}
+
+if BUILD_LUA_SUPPORT
+
+AM_CXXFLAGS = \
+  ${LUA_CFLAGS} \
+  -I$(top_builddir)/proxy/api \
+  -I$(top_srcdir)/proxy/api
+
+
+pkglib_LTLIBRARIES = lua.la
+
+lua_la_LIBADD = ${LUA_LIBS}
+lua_la_SOURCES = lua.cc lapi.cc lutil.cc
+lua_la_LDFLAGS = -module -avoid-version -shared
+
+endif

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7722bb1b/plugins/lua/example.lua
----------------------------------------------------------------------
diff --git a/plugins/lua/example.lua b/plugins/lua/example.lua
new file mode 100644
index 0000000..a57c601
--- /dev/null
+++ b/plugins/lua/example.lua
@@ -0,0 +1,63 @@
+-- Example Lua remap plugin. Load this with the following remap.comfig line:
+--
+-- map http://test.foo.com http://foo.foo.com @plugin=lua.so @pparam=/path/to/example.lua
+
+-- Pull in the Traffic Server API.
+local TS = require 'ts'
+
+require 'string'
+require 'math'
+
+-- Compulsory remap hook. We are given a request object that we can modify if necessary.
+function remap(request)
+  -- Get a copy of the current URL.
+  url = request:url()
+
+  TS.debug('example', string.format('remapping %s://%s', url.scheme, url.host))
+
+  -- Do some header manipulation, just to mess with the origin.
+  request.headers['added-bool'] = true
+  request.headers['added-int'] = 1
+  request.headers['added-string'] = 'ttt'
+  request.headers['added-table'] = {}
+  request.headers['deleted'] = nil
+
+  -- We can also print to stdout using Lua standard library.
+  print(string.format('request URL is %s://%s:%d/%s',
+        url.scheme, url.host, url.port, url.path and url.path or ''))
+
+  -- Modify components of the URL ... everybody loves slashdot.
+  url.host = 'www.slashdot.org'
+  url.port = 80
+  url.method = 'POST'
+
+  -- Plugin chain evaluation rules:
+  --    redirect: plugin chain terminates
+  --    reject: plugin chain terminates
+  --    rewrite: plugin chain continues
+
+  chance = math.random(4)
+  if chance == 1 then
+    -- Send a 301 redirect to the new URL.
+    request:redirect(url)
+  elseif chance == 2 then
+    -- Reject the request with an optional message.
+    request:reject(400, "Denied")
+  elseif chance == 3 then
+    -- Reject the request with a response body. We sniff the body to set the content type.
+    request:reject(500, [[
+      <HEAD></TITLE></HEAD>
+      <BODY>Internal error, sorry</BODY>
+      ]])
+  else
+    -- Rewrite the request URL. The remap plugin chain continues and other plugins
+    request:rewrite(url)
+  end
+
+end
+
+-- Optional module initialization hook.
+function init()
+    TS.debug("example", string.format('init called by Traffic Server %s', TS.VERSION));
+    return true
+end

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7722bb1b/plugins/lua/lapi.cc
----------------------------------------------------------------------
diff --git a/plugins/lua/lapi.cc b/plugins/lua/lapi.cc
new file mode 100644
index 0000000..18d1bb6
--- /dev/null
+++ b/plugins/lua/lapi.cc
@@ -0,0 +1,465 @@
+/*
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#include <ts/ts.h>
+#include <ts/remap.h>
+#include <string.h>
+#include "lapi.h"
+#include "lutil.h"
+
+// Return the type name string for the given index.
+#define LTYPEOF(L, index) lua_typename(L, lua_type(L, index))
+
+struct LuaRemapHeaders
+{
+  TSMBuffer buffer;
+  TSMLoc    headers;
+
+  static LuaRemapHeaders * get(lua_State * lua, int index) {
+    return (LuaRemapHeaders *)luaL_checkudata(lua, index, "ts.meta.rri.headers");
+  }
+
+  static LuaRemapHeaders * alloc(lua_State * lua) {
+    LuaRemapHeaders * hdrs;
+
+    hdrs = (LuaRemapHeaders *)lua_newuserdata(lua, sizeof(LuaRemapHeaders));
+    luaL_getmetatable(lua, "ts.meta.rri.headers");
+    lua_setmetatable(lua, -2);
+
+    return hdrs;
+  }
+};
+
+LuaRemapRequest *
+LuaRemapRequest::get(lua_State * lua, int index)
+{
+  return (LuaRemapRequest *)luaL_checkudata(lua, index, "ts.meta.rri");
+}
+
+LuaRemapRequest *
+LuaRemapRequest::alloc(lua_State * lua)
+{
+  LuaRemapRequest * rq;
+
+  rq = (LuaRemapRequest *)lua_newuserdata(lua, sizeof(LuaRemapRequest));
+  luaL_getmetatable(lua, "ts.meta.rri");
+  lua_setmetatable(lua, -2);
+
+  // Stash a new table as the environment for this object. We will use it later for __index.
+  lua_newtable(lua);
+  TSReleaseAssert(lua_setfenv(lua, -2));
+
+  return rq;
+}
+
+// Given a URL table on the top of the stack, pop it's values into the URL buffer.
+bool
+LuaPopUrl(lua_State * lua, TSMBuffer buffer, TSMLoc url)
+{
+  const char *  strval;
+  size_t        len;
+
+#define SET_URL_COMPONENT(name, setter) do { \
+  lua_getfield(lua, -1, name); \
+  if (!lua_isnil(lua, -1)) { \
+    strval = luaL_checklstring(lua, -1, &len); \
+    if (strval) { \
+      setter(buffer, url, strval, len); \
+    } \
+  } \
+  lua_pop(lua, 1); \
+} while (0)
+
+  // We ignore the 'href' field. When constructing URL tables, it's convenient, but it doesn't seem
+  // necessary here. Callers can easily construct the URL table.
+  SET_URL_COMPONENT("scheme", TSUrlSchemeSet);
+  SET_URL_COMPONENT("user", TSUrlUserSet);
+  SET_URL_COMPONENT("password", TSUrlPasswordSet);
+  SET_URL_COMPONENT("host", TSUrlHostSet);
+  SET_URL_COMPONENT("path", TSUrlPathSet);
+  SET_URL_COMPONENT("query", TSUrlHttpQuerySet);
+  SET_URL_COMPONENT("fragment", TSUrlHttpFragmentSet);
+
+  lua_getfield(lua, -1, "port");
+  if (!lua_isnil(lua, -1)) {
+    TSUrlPortSet(buffer, url, luaL_checkint(lua, -1));
+  }
+  lua_pop(lua, 1);
+
+#undef SET_URL_COMPONENT
+  return true;
+}
+
+bool
+LuaPushUrl(lua_State * lua, TSMBuffer buffer, TSMLoc url)
+{
+  int len;
+  const char * str;
+
+#define PUSH_URL_COMPONENT(accessor, name) do { \
+  str = accessor(buffer, url, &len); \
+  if (str) { \
+    lua_pushlstring(lua, str, len); \
+  }  else { \
+    lua_pushnil(lua); \
+  } \
+  lua_setfield(lua, -2, name); \
+} while (0)
+
+  lua_newtable(lua);
+
+  // Set fundamental URL fields.
+  // XXX should we be luvit-compatible with these names?
+  PUSH_URL_COMPONENT(TSUrlSchemeGet, "scheme");       // luvit: protocol
+  PUSH_URL_COMPONENT(TSUrlUserGet, "user");
+  PUSH_URL_COMPONENT(TSUrlPasswordGet, "password");
+  PUSH_URL_COMPONENT(TSUrlHostGet, "host");
+  lua_pushinteger(lua, TSUrlPortGet(buffer, url));
+  lua_setfield(lua, -2, "port");
+  PUSH_URL_COMPONENT(TSUrlPathGet, "path");           // luvit: pathname
+  PUSH_URL_COMPONENT(TSUrlHttpQueryGet, "query");     // luvit: search
+  PUSH_URL_COMPONENT(TSUrlHttpFragmentGet, "fragment");
+
+  // It would be cleaner to add a __tostring metamethod, but to do that we would have to keep the
+  // buffer and url around indefinitely. Better to make a straight copy now; use the 'href' key
+  // just like luvit does.
+  str = TSUrlStringGet(buffer, url, &len);
+  if (str) {
+    lua_pushlstring(lua, str, len);
+    lua_setfield(lua, -2, "href");
+    TSfree((void *)str);
+  }
+
+  TSReleaseAssert(lua_istable(lua, -1) == 1);
+  return true;
+
+#undef PUSH_URL_COMPONENT
+}
+
+static int
+LuaRemapRedirect(lua_State * lua)
+{
+  LuaRemapRequest * rq;
+
+  rq = LuaRemapRequest::get(lua, 1);
+  luaL_checktype(lua, 2, LUA_TTABLE);
+
+  TSDebug("lua", "redirecting request %p", rq->rri);
+
+  lua_pushvalue(lua, 2);
+  LuaPopUrl(lua, rq->rri->requestBufp, rq->rri->requestUrl);
+  lua_pop(lua, 1);
+
+  // A redirect always terminates plugin chain evaluation.
+  rq->rri->redirect = 1;
+  rq->status = TSREMAP_DID_REMAP_STOP;
+
+  // Return true back to Lua-space.
+  lua_pushboolean(lua, 1);
+  return 1;
+}
+
+static int
+LuaRemapRewrite(lua_State * lua)
+{
+  LuaRemapRequest * rq;
+
+  rq = LuaRemapRequest::get(lua, 1);
+  luaL_checktype(lua, 2, LUA_TTABLE);
+
+  TSDebug("lua", "rewriting request %p", rq->rri);
+
+  lua_pushvalue(lua, 2);
+  LuaPopUrl(lua, rq->rri->requestBufp, rq->rri->requestUrl);
+  lua_pop(lua, 1);
+
+  // A rewrite updates the request URL but never terminates plugin chain evaluation.
+  rq->status = TSREMAP_DID_REMAP;
+
+  // Return true back to Lua-space.
+  lua_pushboolean(lua, 1);
+  return 1;
+}
+
+static int
+LuaRemapReject(lua_State * lua)
+{
+  LuaRemapRequest * rq;
+  int status;
+  const char * body = NULL;
+
+  rq = LuaRemapRequest::get(lua, 1);
+  status = luaL_checkint(lua, 2);
+  if (!lua_isnoneornil(lua, 3)) {
+    body = luaL_checkstring(lua, 3);
+  }
+
+  TSDebug("lua", "rejecting request %p with status %d", rq->rri, status);
+
+  TSHttpTxnSetHttpRetStatus(rq->txn, (TSHttpStatus)status);
+  if (body && *body) {
+    // XXX Dubiously guess the content type from the body. This doesn't actually seem to work
+    // so it doesn't matter that our guess is pretty bad.
+    int isplain = (*body != '<');
+    TSHttpTxnSetHttpRetBody(rq->txn, body, isplain);
+  }
+
+  // A reject terminates plugin chain evaluation but does not update the request URL.
+  rq->status = TSREMAP_NO_REMAP_STOP;
+
+  return 1;
+}
+
+static int
+LuaRemapUrl(lua_State * lua)
+{
+  LuaRemapRequest * rq;
+
+  rq = LuaRemapRequest::get(lua, 1);
+  LuaPushUrl(lua, rq->rri->requestBufp, rq->rri->requestUrl);
+  return 1;
+}
+
+// Since we cannot add fields to userdata objects, we use the environment to store the fields. If the requested
+// field isn't in our metatable, try to find it in the environment. Populate keys in the environment on demand if
+// the request is for a key that we know about.
+//
+// XXX When we set __index in the metatable, Lua routes all method calls through here rather than checking for the
+// existing key first. That's a bit surprising and I wonder whether there's a better way to handle this.
+static int
+LuaRemapIndex(lua_State * lua)
+{
+  LuaRemapRequest * rq;
+  const char * index;
+
+  rq = LuaRemapRequest::get(lua, 1);
+  index = luaL_checkstring(lua, 2);
+
+  TSDebug("lua", "%s[%s]", __func__, index);
+
+  // Get the userdata's metatable and look up the index in it.
+  lua_getmetatable(lua, 1);
+  lua_getfield(lua, -1, index);
+  if (!lua_isnoneornil(lua, -1)) {
+    // Pop the metatable, leaving the field value on top.
+    lua_remove(lua, -2);
+    return 1;
+  }
+
+  // Pop the field value and the metatable.
+  lua_pop(lua, 2);
+
+  lua_getfenv(lua, 1);
+
+  // Get the requested field from the environment table.
+  lua_getfield(lua, -1, index);
+
+  // If we have a value for that field, pop the environment table, leaving the value on top.
+  if (!lua_isnoneornil(lua, -1)) {
+    lua_remove(lua, -2);
+    return 1;
+  }
+
+  // Pop the nil field value.
+  lua_pop(lua, 1);
+
+  if (strcmp(index, "headers") == 0) {
+    LuaRemapHeaders * hdrs;
+
+    hdrs = LuaRemapHeaders::alloc(lua);
+    hdrs->buffer = rq->rri->requestBufp;
+    hdrs->headers = rq->rri->requestHdrp;
+
+    // Set it for the 'headers' index and then push it on the stack.
+    lua_setfield(lua, -2, index);
+    lua_getfield(lua, -1, index);
+
+    // Pop the environment table, leaving the field value on top.
+    lua_remove(lua, -2);
+    return 1;
+  }
+
+  return 0;
+}
+
+static const luaL_Reg RRI[] =
+{
+  { "redirect", LuaRemapRedirect },
+  { "rewrite", LuaRemapRewrite },
+  { "reject", LuaRemapReject },
+  { "url", LuaRemapUrl },
+  { "__index", LuaRemapIndex },
+  { NULL, NULL}
+};
+
+static int
+LuaRemapHeaderIndex(lua_State * lua)
+{
+  LuaRemapHeaders * hdrs;
+  const char *      index;
+  const char *      value;
+  int               vlen;
+  TSMLoc            field;
+
+  hdrs = LuaRemapHeaders::get(lua, 1);;
+  index = luaL_checkstring(lua, 2);
+
+  TSDebug("lua", "%s[%s]", __func__, index);
+
+  field = TSMimeHdrFieldFind(hdrs->buffer, hdrs->headers, index, -1);
+  if (field == TS_NULL_MLOC) {
+    lua_pushnil(lua);
+    return 1;
+  }
+
+  value = TSMimeHdrFieldValueStringGet(hdrs->buffer, hdrs->headers, field, 0, &vlen);
+  lua_pushlstring(lua, value, vlen);
+  return 1;
+}
+
+static int
+LuaRemapHeaderNewIndex(lua_State * lua)
+{
+  LuaRemapHeaders * hdrs;
+  const char *      index;
+  const char *      value;
+  size_t            vlen;
+  TSMLoc            field;
+
+  hdrs = LuaRemapHeaders::get(lua, 1);
+  index = luaL_checkstring(lua, 2);
+
+  TSDebug("lua", "%s[%s] = (%s)", __func__, index, LTYPEOF(lua, 3));
+  field = TSMimeHdrFieldFind(hdrs->buffer, hdrs->headers, index, -1);
+
+  // Setting a key to nil means to delete it.
+  if (lua_isnoneornil(lua, 3)) {
+    if (field != TS_NULL_MLOC) {
+      TSMimeHdrFieldDestroy(hdrs->buffer, hdrs->headers, field);
+      TSHandleMLocRelease(hdrs->buffer, hdrs->headers, field);
+    }
+
+    return 1;
+  }
+
+  // If the MIME field doesn't exist yet, we'd better make it.
+  if (field == TS_NULL_MLOC) {
+    TSMimeHdrFieldCreateNamed(hdrs->buffer, hdrs->headers, index, -1, &field);
+    TSMimeHdrFieldAppend(hdrs->buffer, hdrs->headers, field);
+  }
+
+  TSMimeHdrFieldValuesClear(hdrs->buffer, hdrs->headers, field);
+
+  // Finally, we can set it's value.
+  switch(lua_type(lua, 3)) {
+    case LUA_TBOOLEAN:
+      value = lua_toboolean(lua, 3) ? "1" : "0";
+      vlen = 1;
+      break;
+    default:
+      value = lua_tolstring(lua, 3, &vlen);
+      break;
+  }
+
+  if (value) {
+    TSMimeHdrFieldValueStringInsert(hdrs->buffer, hdrs->headers, field, -1, value, vlen);
+  }
+
+  TSHandleMLocRelease(hdrs->buffer, hdrs->headers, field);
+  return 1;
+}
+
+static const luaL_Reg HEADERS[] =
+{
+  { "__index", LuaRemapHeaderIndex },
+  { "__newindex", LuaRemapHeaderNewIndex },
+  { NULL, NULL }
+};
+
+LuaRemapRequest *
+LuaPushRemapRequestInfo(lua_State * lua, TSHttpTxn txn, TSRemapRequestInfo * rri)
+{
+  LuaRemapRequest * rq;
+
+  rq = LuaRemapRequest::alloc(lua);
+  rq->rri = rri;
+  rq->txn = txn;
+  rq->status = TSREMAP_NO_REMAP;
+
+  TSReleaseAssert(lua_isuserdata(lua, -1) == 1);
+  return rq;
+}
+
+static int
+TSLuaDebug(lua_State * lua)
+{
+  const char * tag = luaL_checkstring(lua, 1);
+  const char * message = luaL_checkstring(lua, 2);
+
+  TSDebug(tag, "%s", message);
+  return 0;
+}
+
+static const luaL_Reg LUAEXPORTS[] =
+{
+  { "debug", TSLuaDebug },
+  { NULL, NULL}
+};
+
+int
+LuaApiInit(lua_State * lua)
+{
+  TSDebug("lua", "initializing Lua API");
+
+  lua_newtable(lua);
+
+  // Register functions in the "ts" module.
+  luaL_register(lua, NULL, LUAEXPORTS);
+
+  // Push constants into the "ts" module.
+  lua_pushstring(lua, TSTrafficServerVersionGet());
+  lua_setfield(lua, -2, "VERSION");
+
+  lua_pushinteger(lua, TSTrafficServerVersionGetMajor());
+  lua_setfield(lua, -2, "MAJOR_VERSION");
+
+  lua_pushinteger(lua, TSTrafficServerVersionGetMinor());
+  lua_setfield(lua, -2, "MINOR_VERSION");
+
+  lua_pushinteger(lua, TSTrafficServerVersionGetPatch());
+  lua_setfield(lua, -2, "PATCH_VERSION");
+
+  lua_pushinteger(lua, TSREMAP_DID_REMAP_STOP);
+  lua_setfield(lua, -2, "REMAP_COMPLETE");
+
+  lua_pushinteger(lua, TSREMAP_DID_REMAP);
+  lua_setfield(lua, -2, "REMAP_CONTINUE");
+
+  // Register TSRemapRequestInfo metatable.
+  LuaPushMetatable(lua, "ts.meta.rri", RRI);
+  // Pop the metatable.
+  lua_pop(lua, 1);
+
+  // Register the remap headers metatable.
+  LuaPushMetatable(lua, "ts.meta.rri.headers", HEADERS);
+  // Pop the metatable.
+  lua_pop(lua, 1);
+
+  TSReleaseAssert(lua_istable(lua, -1) == 1);
+  return 1;
+}

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7722bb1b/plugins/lua/lapi.h
----------------------------------------------------------------------
diff --git a/plugins/lua/lapi.h b/plugins/lua/lapi.h
new file mode 100644
index 0000000..9dc8534
--- /dev/null
+++ b/plugins/lua/lapi.h
@@ -0,0 +1,48 @@
+/*
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#ifndef LUA_LAPI_H_
+#define LUA_LAPI_H_
+
+extern "C" {
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+}
+
+struct LuaRemapRequest
+{
+  TSRemapRequestInfo *  rri;
+  TSHttpTxn             txn;
+  TSRemapStatus         status;
+
+  static LuaRemapRequest * get(lua_State * lua, int index);
+  static LuaRemapRequest * alloc(lua_State * lua);
+};
+
+// Initialize the 'ts' module.
+int LuaApiInit(lua_State * lua);
+
+// Push a copy of the given URL.
+bool LuaPushUrl(lua_State * lua, TSMBuffer buffer, TSMLoc url);
+
+// Push a wrapper object for the given TSRemapRequestInfo.
+LuaRemapRequest *
+LuaPushRemapRequestInfo(lua_State * lua, TSHttpTxn txn, TSRemapRequestInfo * rri);
+
+#endif // LUA_LAPI_H_

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7722bb1b/plugins/lua/lua.cc
----------------------------------------------------------------------
diff --git a/plugins/lua/lua.cc b/plugins/lua/lua.cc
new file mode 100644
index 0000000..9a74571
--- /dev/null
+++ b/plugins/lua/lua.cc
@@ -0,0 +1,218 @@
+/*
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#include <ts/ts.h>
+#include <ts/remap.h>
+#include <unistd.h>
+#include <string.h>
+#include <pthread.h>
+#include <string>
+#include <vector>
+#include "lapi.h"
+#include "lutil.h"
+
+static pthread_key_t LuaStateKey;
+
+struct LuaPluginState
+{
+  typedef std::vector<std::string> pathlist;
+
+  LuaPluginState(unsigned argc, const char ** argv) {
+    for (unsigned i = 0; i < argc; ++i) {
+      paths.push_back(argv[i]);
+    }
+  }
+
+  pathlist paths;
+};
+
+static TSReturnCode
+LuaPluginInit(lua_State * lua)
+{
+  TSReturnCode status = TS_ERROR;
+
+  lua_getglobal(lua, "init");
+  if (lua_isnil(lua, -1)) {
+    // No "init" callback.
+    return TS_SUCCESS;
+  }
+
+  if (lua_pcall(lua, 0, 1, 0) != 0) {
+    TSDebug("lua", "init failed: %s", lua_tostring(lua, -1));
+    lua_pop(lua, 1);
+  }
+
+  // Return type is bool; check it and pop it.
+  if (lua_isboolean(lua, 1) && lua_toboolean(lua, 1)) {
+    status = TS_SUCCESS;
+  }
+
+  lua_pop(lua, 1);
+  return status;
+}
+
+static TSReturnCode
+LuaPluginRelease(lua_State * lua)
+{
+  lua_getglobal(lua, "release");
+  if (lua_isnil(lua, -1)) {
+    // No "release" callback.
+    return TS_SUCCESS;
+  }
+
+  if (lua_pcall(lua, 0, 0, 0) != 0) {
+    TSDebug("lua", "release failed: %s", lua_tostring(lua, -1));
+    lua_pop(lua, 1);
+  }
+
+  lua_close(lua);
+  return TS_SUCCESS;
+}
+
+static TSRemapStatus
+LuaPluginRemap(lua_State * lua, TSHttpTxn txn, TSRemapRequestInfo * rri)
+{
+  LuaRemapRequest * rq;
+
+  lua_getglobal(lua, "remap");
+  if (lua_isnil(lua, -1)) {
+    // No "remap" callback, better continue.
+    return TSREMAP_NO_REMAP;
+  }
+
+  TSDebug("lua", "handling request %p on thread 0x%llx", rri, (unsigned long long)pthread_self());
+
+  // XXX We can also cache the RemapRequestInfo in the Lua state. We we just need to reset
+  // the rri pointer and status.
+  rq = LuaPushRemapRequestInfo(lua, txn, rri);
+
+  if (lua_pcall(lua, 1, 0, 0) != 0) {
+    TSDebug("lua", "remap failed: %s", lua_tostring(lua, -1));
+    lua_pop(lua, 1);
+    return TSREMAP_ERROR;
+  }
+
+  // XXX can we guarantee that rq has not been garbage collected?
+  return rq->status;
+}
+
+static lua_State *
+LuaPluginNewState(void)
+{
+  lua_State * lua;
+
+  lua = lua_newstate(LuaAllocate, NULL);
+  if (lua == NULL) {
+    return NULL;
+  }
+
+  LuaLoadLibraries(lua);
+  LuaRegisterLibrary(lua, "ts", LuaApiInit);
+
+  return lua;
+}
+
+static lua_State *
+LuaPluginNewState(LuaPluginState * remap)
+{
+  lua_State * lua;
+
+  lua = LuaPluginNewState();
+  if (lua == NULL) {
+    return NULL;
+  }
+
+  for (LuaPluginState::pathlist::const_iterator p = remap->paths.begin(); p < remap->paths.end(); ++p) {
+    if (access(p->c_str(), F_OK) != 0) {
+      continue;
+    }
+
+    if (luaL_dofile(lua, p->c_str()) != 0) {
+      // If the load failed, it should have pushed an error message.
+      TSDebug("lua", "failed to load Lua file %s: %s", p->c_str(), lua_tostring(lua, -1));
+      lua_close(lua);
+      return NULL;
+    }
+  }
+
+  if (LuaPluginInit(lua) == TS_SUCCESS) {
+    return lua;
+  } else {
+    lua_close(lua);
+    return NULL;
+  }
+}
+
+void
+TSRemapDeleteInstance(void * ih)
+{
+  lua_State * lua = (lua_State *)ih;
+
+  if (lua) {
+    LuaPluginRelease(lua);
+    lua_close(lua);
+  }
+}
+
+TSReturnCode
+TSRemapInit(TSRemapInterface * api_info, char * errbuf, int errbuf_size)
+{
+  TSDebug("lua", "loading lua plugin");
+  TSReleaseAssert(pthread_key_create(&LuaStateKey, TSRemapDeleteInstance) == 0);
+  return TS_SUCCESS;
+}
+
+TSReturnCode
+TSRemapNewInstance(int argc, char * argv[], void ** ih, char * errbuf, int errsz)
+{
+  LuaPluginState * remap;
+  lua_State * lua;
+
+  // Copy the plugin arguments so that we can use them to allocate a per-thread Lua state. It would be cleaner
+  // to clone a Lua state, but there's no built-in way to do that, and to implement that ourselves would require
+  // locking the template state (we need to manipulate the stack to copy values out).
+  remap = new LuaPluginState((unsigned)argc, (const char **)argv);
+
+  // Test whether we can successfully load the Lua program.
+  lua = LuaPluginNewState(remap);
+  if (!lua) {
+    delete remap;
+    return TS_ERROR;
+  }
+
+  *ih = remap;
+  return TS_SUCCESS;
+}
+
+TSRemapStatus
+TSRemapDoRemap(void * ih, TSHttpTxn txn, TSRemapRequestInfo * rri)
+{
+  lua_State * lua;
+
+  // Find or clone the per-thread Lua state.
+  lua = (lua_State *)pthread_getspecific(LuaStateKey);
+  if (!lua) {
+    LuaPluginState * remap = (LuaPluginState *)ih;
+
+    TSDebug("lua", "allocating new Lua state on thread 0x%llx", (unsigned long long)pthread_self());
+    lua = LuaPluginNewState(remap);
+    pthread_setspecific(LuaStateKey, lua);
+  }
+
+  return LuaPluginRemap(lua, txn, rri);
+}

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7722bb1b/plugins/lua/lutil.cc
----------------------------------------------------------------------
diff --git a/plugins/lua/lutil.cc b/plugins/lua/lutil.cc
new file mode 100644
index 0000000..b8241f1
--- /dev/null
+++ b/plugins/lua/lutil.cc
@@ -0,0 +1,80 @@
+/*
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#include <ts/ts.h>
+
+extern "C" {
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+}
+
+void *
+LuaAllocate(void * ud, void * ptr, size_t osize, size_t nsize)
+{
+  TSReleaseAssert(ud == NULL);
+
+  if (nsize == 0) {
+    TSfree(ptr);
+    return NULL;
+  }
+
+  return TSrealloc(ptr, nsize);
+}
+
+void
+LuaPushMetatable(lua_State * lua, const char * name, const luaL_Reg * exports)
+{
+  luaL_newmetatable(lua, name);
+  lua_pushvalue(lua, -1);
+  lua_setfield(lua, -2, "__index");
+  luaL_register(lua, NULL, exports);
+}
+
+void
+LuaRegisterLibrary(lua_State * lua, const char * name, lua_CFunction loader)
+{
+  // Pull up the preload table.
+  lua_getglobal(lua, "package");
+  lua_getfield(lua, -1, "preload");
+
+  lua_pushcfunction(lua, loader);
+  lua_setfield(lua, -2, name);
+
+  // Pop the 'package' and 'preload' tables.
+  lua_pop(lua, 2);
+}
+
+void
+LuaLoadLibraries(lua_State * lua)
+{
+#define REGISTER_LIBRARY(name) LuaRegisterLibrary(lua, #name, luaopen_ ## name)
+
+    lua_cpcall(lua, luaopen_base, NULL);
+    lua_cpcall(lua, luaopen_package, NULL);
+
+    REGISTER_LIBRARY(io);
+    REGISTER_LIBRARY(os);
+    REGISTER_LIBRARY(table);
+    REGISTER_LIBRARY(string);
+    REGISTER_LIBRARY(math);
+    REGISTER_LIBRARY(debug);
+
+#undef REGISTER_LIBRARY
+}
+

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7722bb1b/plugins/lua/lutil.h
----------------------------------------------------------------------
diff --git a/plugins/lua/lutil.h b/plugins/lua/lutil.h
new file mode 100644
index 0000000..d6e24a6
--- /dev/null
+++ b/plugins/lua/lutil.h
@@ -0,0 +1,25 @@
+/*
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+// Return the type name string for the given index.
+#define LTYPEOF(L, index) lua_typename(L, lua_type(L, index))
+
+void * LuaAllocate(void * ud, void * ptr, size_t osize, size_t nsize);
+void LuaPushMetatable(lua_State * lua, const char * name, const luaL_Reg * exports);
+void LuaLoadLibraries(lua_State * lua);
+void LuaRegisterLibrary(lua_State * lua, const char * name, lua_CFunction loader);