init commit
This commit is contained in:
45
.gitignore
vendored
Normal file
45
.gitignore
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.o
|
||||||
|
*.dylib
|
||||||
|
/bin/*
|
||||||
|
/testbin/*
|
||||||
|
/luajit/*
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.sln.docstates
|
||||||
|
/.vscode
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
x64/
|
||||||
|
build/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
|
||||||
|
# NuGet Packages Directory
|
||||||
|
packages/*
|
||||||
|
|
||||||
|
# Test binary, build with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
envoy-apisix
|
||||||
32
Makefile
Normal file
32
Makefile
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
TARGET = envoy-apisix
|
||||||
|
LUA_VERSION = 2.1.0-beta3
|
||||||
|
|
||||||
|
SRCS = $(wildcard envoy/*.cpp)
|
||||||
|
OBJS = $(SRCS:.cpp=.o)
|
||||||
|
|
||||||
|
INCLUDES = -I./luajit/include/luajit-2.1
|
||||||
|
CXXFLAGS = -fPIC -O2 -Wall -std=c++11 $(INCLUDES)
|
||||||
|
LDFLAGS = -L./luajit/lib -lluajit-5.1 -ldl
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: run
|
||||||
|
|
||||||
|
$(TARGET): $(OBJS)
|
||||||
|
$(CXX) -o $@ $^ $(LDFLAGS)
|
||||||
|
|
||||||
|
luajit:
|
||||||
|
$(RM) -r /tmp/LuaJIT-$(LUA_VERSION)
|
||||||
|
curl -sL https://luajit.org/download/LuaJIT-$(LUA_VERSION).tar.gz | tar zxf - -C /tmp
|
||||||
|
make -C /tmp/LuaJIT-$(LUA_VERSION) install PREFIX=$(shell pwd)/luajit XCFLAGS=-DLUAJIT_ENABLE_LUA52COMPAT
|
||||||
|
|
||||||
|
.PHONY: run
|
||||||
|
run: ${TARGET}
|
||||||
|
@./$(TARGET) ./plugins/entry.lua
|
||||||
|
|
||||||
|
.PHONY: envoy
|
||||||
|
envoy:
|
||||||
|
docker run -it --rm --net host -v $(shell pwd):/etc/envoy -e "LUA_PATH=/etc/envoy/?.lua" envoyproxy/envoy-distroless:v1.24.0
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
${RM} ${TARGET} ${OBJS}
|
||||||
22
envoy.sln
Normal file
22
envoy.sln
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Express 2013 for Windows Desktop
|
||||||
|
VisualStudioVersion = 12.0.40629.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "envoy", "envoy\envoy.vcxproj", "{E152FBD5-2A29-400E-8E18-9C515986217C}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Win32 = Debug|Win32
|
||||||
|
Release|Win32 = Release|Win32
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{E152FBD5-2A29-400E-8E18-9C515986217C}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||||
|
{E152FBD5-2A29-400E-8E18-9C515986217C}.Debug|Win32.Build.0 = Debug|Win32
|
||||||
|
{E152FBD5-2A29-400E-8E18-9C515986217C}.Release|Win32.ActiveCfg = Release|Win32
|
||||||
|
{E152FBD5-2A29-400E-8E18-9C515986217C}.Release|Win32.Build.0 = Release|Win32
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
126
envoy.yaml
Normal file
126
envoy.yaml
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# docker run --rm --network host -v $(pwd):/etc/envoy -w /etc/envoy -it envoyproxy/envoy-distroless:v1.24.0
|
||||||
|
# curl -u 'admin:admin' -H 'Origin: http://lenovo.com' -i http://vcap.me:10000/ip
|
||||||
|
|
||||||
|
static_resources:
|
||||||
|
listeners:
|
||||||
|
- address:
|
||||||
|
socket_address:
|
||||||
|
address: 0.0.0.0
|
||||||
|
port_value: 10000
|
||||||
|
filter_chains:
|
||||||
|
- filters:
|
||||||
|
- name: envoy.filters.network.http_connection_manager
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||||
|
codec_type: AUTO
|
||||||
|
stat_prefix: ingress_http
|
||||||
|
strip_any_host_port: true
|
||||||
|
generate_request_id: false
|
||||||
|
route_config:
|
||||||
|
name: local_route
|
||||||
|
virtual_hosts:
|
||||||
|
- name: vcap_me
|
||||||
|
domains:
|
||||||
|
- vcap.me
|
||||||
|
routes:
|
||||||
|
- match:
|
||||||
|
path: /ip
|
||||||
|
route:
|
||||||
|
auto_host_rewrite: true
|
||||||
|
cluster: httpbin
|
||||||
|
metadata:
|
||||||
|
filter_metadata:
|
||||||
|
envoy.filters.http.lua:
|
||||||
|
plugins:
|
||||||
|
- name: user-code
|
||||||
|
conf:
|
||||||
|
body_filter: |
|
||||||
|
for key, value in pairs(ctx.headers) do
|
||||||
|
log.info("user-code: ", key, " = ", value)
|
||||||
|
end
|
||||||
|
- name: basic-auth
|
||||||
|
conf:
|
||||||
|
username: admin
|
||||||
|
password: admin
|
||||||
|
- name: redirect
|
||||||
|
conf:
|
||||||
|
ret_code: 301
|
||||||
|
uri: /headers
|
||||||
|
headers:
|
||||||
|
x-earth-token: xxx
|
||||||
|
x-earth-project: yyy
|
||||||
|
- match:
|
||||||
|
path: /body
|
||||||
|
direct_response:
|
||||||
|
status: 200
|
||||||
|
body:
|
||||||
|
inline_string: "Get Body\n"
|
||||||
|
metadata:
|
||||||
|
filter_metadata:
|
||||||
|
envoy.filters.http.lua:
|
||||||
|
plugins:
|
||||||
|
- name: user-code
|
||||||
|
conf:
|
||||||
|
access: |
|
||||||
|
ctx.var.path = ctx.headers:get(":path")
|
||||||
|
ctx.log.info("hit access")
|
||||||
|
body_filter: |
|
||||||
|
for key, value in pairs(ctx.var) do
|
||||||
|
ctx.log.info("var: ", key, " = ", value)
|
||||||
|
end
|
||||||
|
ctx.log.info("hit body_filter")
|
||||||
|
- match:
|
||||||
|
path: /auth
|
||||||
|
direct_response:
|
||||||
|
status: 200
|
||||||
|
body:
|
||||||
|
inline_string: "Autherd\n"
|
||||||
|
metadata:
|
||||||
|
filter_metadata:
|
||||||
|
envoy.filters.http.lua:
|
||||||
|
plugins:
|
||||||
|
- name: basic-auth
|
||||||
|
conf:
|
||||||
|
htpasswd: | # htpasswd -bnBC 10 admin admin
|
||||||
|
admin:$2y$10$7y/gRzOG6zhB5WOnGp8xw.wMF9c4Fw6ZkPwaALHlMNFG5IZy1W3Um
|
||||||
|
- match:
|
||||||
|
prefix: /
|
||||||
|
direct_response:
|
||||||
|
status: 200
|
||||||
|
body:
|
||||||
|
inline_string: "Lucky\n"
|
||||||
|
metadata:
|
||||||
|
filter_metadata:
|
||||||
|
envoy.filters.http.lua:
|
||||||
|
plugins:
|
||||||
|
- name: user-code
|
||||||
|
conf:
|
||||||
|
body_filter: |
|
||||||
|
for key, value in pairs(ctx.headers) do
|
||||||
|
ctx.log.info("header: ", key, " = ", value)
|
||||||
|
end
|
||||||
|
http_filters:
|
||||||
|
- name: envoy.filters.http.lua
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
|
||||||
|
default_source_code:
|
||||||
|
filename: /etc/envoy/core/entry.lua
|
||||||
|
- name: envoy.filters.http.router
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
||||||
|
access_log:
|
||||||
|
- name: envoy.access_loggers.stdout
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
|
||||||
|
clusters:
|
||||||
|
- name: httpbin
|
||||||
|
type: LOGICAL_DNS
|
||||||
|
load_assignment:
|
||||||
|
cluster_name: httpbin
|
||||||
|
endpoints:
|
||||||
|
- lb_endpoints:
|
||||||
|
- endpoint:
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: httpbin.org
|
||||||
|
port_value: 80
|
||||||
204
envoy/envoy.cpp
Normal file
204
envoy/envoy.cpp
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
|
||||||
|
#include "envoy.h"
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace lua {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper for a buffer.
|
||||||
|
*/
|
||||||
|
int BufferWrapper::luaLength(lua_State* state) {
|
||||||
|
lua_pushnumber(state, 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int BufferWrapper::luaGetBytes(lua_State* state) {
|
||||||
|
lua_pushlstring(state, "buffer", 6);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int BufferWrapper::luaSetBytes(lua_State* state) {
|
||||||
|
lua_pushnumber(state, 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lua wrapper for a header map. Methods that will modify the map will call a check function
|
||||||
|
* to see if modification is allowed.
|
||||||
|
*/
|
||||||
|
int HeaderMapWrapper::luaAdd(lua_State* state) {
|
||||||
|
UNUSED(state);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HeaderMapWrapper::luaGet(lua_State* state) {
|
||||||
|
UNUSED(state);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HeaderMapWrapper::luaPairs(lua_State* state) {
|
||||||
|
if (iterator_.get() != nullptr) {
|
||||||
|
luaL_error(state, "cannot create a second iterator before completing the first");
|
||||||
|
}
|
||||||
|
iterator_.reset(HeaderMapIterator::create(state), true);
|
||||||
|
lua_pushcclosure(state, HeaderMapIterator::static_luaPairsIterator, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HeaderMapIterator::luaPairsIterator(lua_State* state) {
|
||||||
|
UNUSED(state);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lua wrapper for a metadata map.
|
||||||
|
*/
|
||||||
|
int MetadataMapWrapper::luaGet(lua_State* state) {
|
||||||
|
const char* key = luaL_checkstring(state, 2);
|
||||||
|
UNUSED(key);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MetadataMapWrapper::luaPairs(lua_State* state) {
|
||||||
|
if (iterator_.get() != nullptr) {
|
||||||
|
luaL_error(state, "cannot create a second iterator before completing the first");
|
||||||
|
}
|
||||||
|
iterator_.reset(MetadataMapIterator::create(state), true);
|
||||||
|
lua_pushcclosure(state, MetadataMapIterator::static_luaPairsIterator, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MetadataMapIterator::luaPairsIterator(lua_State* state) {
|
||||||
|
UNUSED(state);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lua wrapper for a stream info.
|
||||||
|
*/
|
||||||
|
int StreamInfoWrapper::luaProtocol(lua_State* state) {
|
||||||
|
const std::string protocol = "HTTP/1.1";
|
||||||
|
lua_pushlstring(state, protocol.data(), protocol.size());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamInfoWrapper::luaDynamicMetadata(lua_State* state) {
|
||||||
|
UNUSED(state);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamInfoWrapper::luaDownstreamSslConnection(lua_State* state) {
|
||||||
|
lua_pushnil(state);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamInfoWrapper::luaDownstreamLocalAddress(lua_State* state) {
|
||||||
|
const std::string local_address = "127.0.0.1:1234";
|
||||||
|
lua_pushlstring(state, local_address.data(), local_address.size());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamInfoWrapper::luaDownstreamDirectRemoteAddress(lua_State* state) {
|
||||||
|
const std::string direct_remote_address = "172.17.0.3:80";
|
||||||
|
lua_pushlstring(state, direct_remote_address.data(), direct_remote_address.size());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamInfoWrapper::luaRequestedServerName(lua_State* state) {
|
||||||
|
const std::string server_name = "envoy";
|
||||||
|
lua_pushlstring(state, server_name.data(), server_name.size());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper for a currently running request/response. This is the primary handle passed to Lua.
|
||||||
|
* The script interacts with Envoy entirely through this handle.
|
||||||
|
*/
|
||||||
|
int StreamHandleWrapper::luaHeaders(lua_State* state) {
|
||||||
|
if (headers_wrapper_.get() != nullptr) {
|
||||||
|
headers_wrapper_.pushStack();
|
||||||
|
} else {
|
||||||
|
headers_wrapper_.reset(HeaderMapWrapper::create(state), true);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamHandleWrapper::luaBody(lua_State* state) {
|
||||||
|
UNUSED(state);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamHandleWrapper::luaBodyChunks(lua_State* state) {
|
||||||
|
lua_pushcclosure(state, static_luaBodyIterator, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamHandleWrapper::luaBodyIterator(lua_State* state) {
|
||||||
|
LuaDeathRef<BufferWrapper> wrapper;
|
||||||
|
wrapper.reset(BufferWrapper::create(state), true);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamHandleWrapper::luaMetadata(lua_State* state) {
|
||||||
|
if (metadata_wrapper_.get() != nullptr) {
|
||||||
|
metadata_wrapper_.pushStack();
|
||||||
|
} else {
|
||||||
|
metadata_wrapper_.reset(MetadataMapWrapper::create(state), true);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamHandleWrapper::luaStreamInfo(lua_State* state) {
|
||||||
|
if (stream_info_wrapper_.get() != nullptr) {
|
||||||
|
stream_info_wrapper_.pushStack();
|
||||||
|
} else {
|
||||||
|
stream_info_wrapper_.reset(StreamInfoWrapper::create(state), true);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaLog(lua_State* state, const char* prefix) {
|
||||||
|
size_t input_size = 0;
|
||||||
|
const char* input = luaL_checklstring(state, 2, &input_size);
|
||||||
|
std::string message(input, input_size);
|
||||||
|
std::cout << prefix << ": " << message << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamHandleWrapper::luaLogTrace(lua_State* state) {
|
||||||
|
return luaLog(state, "trace");
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamHandleWrapper::luaLogDebug(lua_State* state) {
|
||||||
|
return luaLog(state, "debug");
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamHandleWrapper::luaLogInfo(lua_State* state) {
|
||||||
|
return luaLog(state, "info");
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamHandleWrapper::luaLogWarn(lua_State* state) {
|
||||||
|
return luaLog(state, "warn");
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamHandleWrapper::luaLogErr(lua_State* state) {
|
||||||
|
return luaLog(state, "error");
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamHandleWrapper::luaLogCritical(lua_State* state) {
|
||||||
|
return luaLog(state, "crit");
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamHandleWrapper::luaHttpCall(lua_State* state) {
|
||||||
|
UNUSED(state);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamHandleWrapper::luaRespond(lua_State* state) {
|
||||||
|
UNUSED(state);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
130
envoy/envoy.h
Normal file
130
envoy/envoy.h
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lua.h"
|
||||||
|
|
||||||
|
namespace lua {
|
||||||
|
|
||||||
|
class BufferWrapper : public BaseLuaObject<BufferWrapper> {
|
||||||
|
public:
|
||||||
|
static ExportedFunctions exportedFunctions() {
|
||||||
|
return {{"length", static_luaLength},
|
||||||
|
{"getBytes", static_luaGetBytes},
|
||||||
|
{"setBytes", static_luaSetBytes}};
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
DECLARE_LUA_FUNCTION(BufferWrapper, luaLength);
|
||||||
|
DECLARE_LUA_FUNCTION(BufferWrapper, luaGetBytes);
|
||||||
|
DECLARE_LUA_FUNCTION(BufferWrapper, luaSetBytes);
|
||||||
|
};
|
||||||
|
|
||||||
|
class HeaderMapIterator : public BaseLuaObject<HeaderMapIterator> {
|
||||||
|
public:
|
||||||
|
static ExportedFunctions exportedFunctions() { return {}; }
|
||||||
|
|
||||||
|
DECLARE_LUA_CLOSURE(HeaderMapIterator, luaPairsIterator);
|
||||||
|
};
|
||||||
|
|
||||||
|
class HeaderMapWrapper : public BaseLuaObject<HeaderMapWrapper> {
|
||||||
|
public:
|
||||||
|
static ExportedFunctions exportedFunctions() {
|
||||||
|
return {
|
||||||
|
{ "add", static_luaAdd },
|
||||||
|
{ "get", static_luaGet },
|
||||||
|
{ "__pairs", static_luaPairs },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DECLARE_LUA_FUNCTION(HeaderMapWrapper, luaAdd);
|
||||||
|
DECLARE_LUA_FUNCTION(HeaderMapWrapper, luaGet);
|
||||||
|
DECLARE_LUA_FUNCTION(HeaderMapWrapper, luaPairs);
|
||||||
|
|
||||||
|
LuaDeathRef<HeaderMapIterator> iterator_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MetadataMapIterator : public BaseLuaObject<MetadataMapIterator> {
|
||||||
|
public:
|
||||||
|
static ExportedFunctions exportedFunctions() { return {}; }
|
||||||
|
|
||||||
|
DECLARE_LUA_CLOSURE(MetadataMapIterator, luaPairsIterator);
|
||||||
|
};
|
||||||
|
|
||||||
|
class MetadataMapWrapper : public BaseLuaObject<MetadataMapWrapper> {
|
||||||
|
public:
|
||||||
|
static ExportedFunctions exportedFunctions() {
|
||||||
|
return {{"get", static_luaGet}, {"__pairs", static_luaPairs}};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DECLARE_LUA_FUNCTION(MetadataMapWrapper, luaGet);
|
||||||
|
DECLARE_LUA_FUNCTION(MetadataMapWrapper, luaPairs);
|
||||||
|
|
||||||
|
LuaDeathRef<MetadataMapIterator> iterator_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class StreamInfoWrapper : public BaseLuaObject<StreamInfoWrapper> {
|
||||||
|
public:
|
||||||
|
static ExportedFunctions exportedFunctions() {
|
||||||
|
return {
|
||||||
|
{ "protocol", static_luaProtocol },
|
||||||
|
{ "dynamicMetadata", static_luaDynamicMetadata },
|
||||||
|
{ "downstreamLocalAddress", static_luaDownstreamLocalAddress },
|
||||||
|
{ "downstreamDirectRemoteAddress", static_luaDownstreamDirectRemoteAddress },
|
||||||
|
{ "downstreamSslConnection", static_luaDownstreamSslConnection },
|
||||||
|
{ "requestedServerName", static_luaRequestedServerName }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
DECLARE_LUA_FUNCTION(StreamInfoWrapper, luaProtocol);
|
||||||
|
DECLARE_LUA_FUNCTION(StreamInfoWrapper, luaDynamicMetadata);
|
||||||
|
DECLARE_LUA_FUNCTION(StreamInfoWrapper, luaDownstreamSslConnection);
|
||||||
|
DECLARE_LUA_FUNCTION(StreamInfoWrapper, luaDownstreamLocalAddress);
|
||||||
|
DECLARE_LUA_FUNCTION(StreamInfoWrapper, luaDownstreamDirectRemoteAddress);
|
||||||
|
DECLARE_LUA_FUNCTION(StreamInfoWrapper, luaRequestedServerName);
|
||||||
|
};
|
||||||
|
|
||||||
|
class StreamHandleWrapper : public BaseLuaObject<StreamHandleWrapper> {
|
||||||
|
public:
|
||||||
|
static ExportedFunctions exportedFunctions() {
|
||||||
|
return {
|
||||||
|
{ "headers", static_luaHeaders },
|
||||||
|
{ "body", static_luaBody },
|
||||||
|
{ "bodyChunks", static_luaBodyChunks },
|
||||||
|
{ "metadata", static_luaMetadata },
|
||||||
|
{ "streamInfo", static_luaStreamInfo },
|
||||||
|
{ "logTrace", static_luaLogTrace },
|
||||||
|
{ "logDebug", static_luaLogDebug },
|
||||||
|
{ "logInfo", static_luaLogInfo },
|
||||||
|
{ "logWarn", static_luaLogWarn },
|
||||||
|
{ "logErr", static_luaLogErr },
|
||||||
|
{ "logCritical", static_luaLogCritical },
|
||||||
|
{ "httpCall", static_luaHttpCall },
|
||||||
|
{ "respond", static_luaRespond },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaHeaders);
|
||||||
|
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaBody);
|
||||||
|
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaBodyChunks);
|
||||||
|
DECLARE_LUA_CLOSURE(StreamHandleWrapper, luaBodyIterator);
|
||||||
|
|
||||||
|
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaMetadata);
|
||||||
|
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaStreamInfo);
|
||||||
|
|
||||||
|
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaLogTrace);
|
||||||
|
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaLogDebug);
|
||||||
|
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaLogInfo);
|
||||||
|
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaLogWarn);
|
||||||
|
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaLogErr);
|
||||||
|
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaLogCritical);
|
||||||
|
|
||||||
|
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaHttpCall);
|
||||||
|
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaRespond);
|
||||||
|
|
||||||
|
LuaDeathRef<HeaderMapWrapper> headers_wrapper_;
|
||||||
|
LuaDeathRef<MetadataMapWrapper> metadata_wrapper_;
|
||||||
|
LuaDeathRef<StreamInfoWrapper> stream_info_wrapper_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
105
envoy/envoy.vcxproj
Normal file
105
envoy/envoy.vcxproj
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<ProjectConfiguration Include="Debug|Win32">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|Win32">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectGuid>{E152FBD5-2A29-400E-8E18-9C515986217C}</ProjectGuid>
|
||||||
|
<Keyword>Win32Proj</Keyword>
|
||||||
|
<RootNamespace>envoy</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v120</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v120</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<PropertyGroup Label="UserMacros">
|
||||||
|
<NuGetPackageImportStamp>3c953278</NuGetPackageImportStamp>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<LinkIncremental>true</LinkIncremental>
|
||||||
|
<OutDir>$(SolutionDir)bin\$(Configuration)\$(PlatformTarget)\</OutDir>
|
||||||
|
<IntDir>$(SolutionDir)obj\$(Configuration)\$(PlatformTarget)\</IntDir>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<LinkIncremental>false</LinkIncremental>
|
||||||
|
<OutDir>$(SolutionDir)bin\$(Configuration)\$(PlatformTarget)\</OutDir>
|
||||||
|
<IntDir>$(SolutionDir)obj\$(Configuration)\$(PlatformTarget)\</IntDir>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<PrecompiledHeader>
|
||||||
|
</PrecompiledHeader>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<Optimization>Disabled</Optimization>
|
||||||
|
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<PrecompiledHeader>
|
||||||
|
</PrecompiledHeader>
|
||||||
|
<Optimization>MaxSpeed</Optimization>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="envoy.cpp" />
|
||||||
|
<ClCompile Include="main.cpp" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="envoy.h" />
|
||||||
|
<ClInclude Include="lua.h" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="packages.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
<Import Project="..\packages\luajit.native.2.0.5\build\native\luajit.native.targets" Condition="Exists('..\packages\luajit.native.2.0.5\build\native\luajit.native.targets')" />
|
||||||
|
</ImportGroup>
|
||||||
|
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||||
|
<PropertyGroup>
|
||||||
|
<ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。启用“NuGet 程序包还原”可下载这些程序包。有关详细信息,请参阅 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Error Condition="!Exists('..\packages\luajit.native.2.0.5\build\native\luajit.native.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\luajit.native.2.0.5\build\native\luajit.native.targets'))" />
|
||||||
|
</Target>
|
||||||
|
</Project>
|
||||||
36
envoy/envoy.vcxproj.filters
Normal file
36
envoy/envoy.vcxproj.filters
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup>
|
||||||
|
<Filter Include="源文件">
|
||||||
|
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||||
|
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="头文件">
|
||||||
|
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||||
|
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="资源文件">
|
||||||
|
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||||
|
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||||
|
</Filter>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="main.cpp">
|
||||||
|
<Filter>源文件</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="envoy.cpp">
|
||||||
|
<Filter>源文件</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="envoy.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="lua.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="packages.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
292
envoy/lua.h
Normal file
292
envoy/lua.h
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <lua.hpp>
|
||||||
|
|
||||||
|
#define ASSERT(...)
|
||||||
|
#define UNUSED(x) (void)(x)
|
||||||
|
|
||||||
|
#ifndef alignof
|
||||||
|
#define alignof(T) std::alignment_of<T>::value
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace lua {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base macro for declaring a Lua/C function. Any function declared will need to be exported via
|
||||||
|
* the exportedFunctions() function in BaseLuaObject. See BaseLuaObject below for more
|
||||||
|
* information. This macro declares a static "thunk" which checks the user data, optionally checks
|
||||||
|
* for object death (again see BaseLuaObject below for more info), and then invokes a normal
|
||||||
|
* object method. The actual object method needs to be implemented by the class.
|
||||||
|
* @param Class supplies the owning class name.
|
||||||
|
* @param Name supplies the function name.
|
||||||
|
* @param Index supplies the stack index where "this" (Lua/C userdata) is found.
|
||||||
|
*/
|
||||||
|
#define DECLARE_LUA_FUNCTION_EX(Class, Name, Index) \
|
||||||
|
static int static_##Name(lua_State* state) { \
|
||||||
|
Class* object = lua::alignAndCast<Class>( \
|
||||||
|
luaL_checkudata(state, Index, typeid(Class).name())); \
|
||||||
|
object->checkDead(state); \
|
||||||
|
return object->Name(state); \
|
||||||
|
} \
|
||||||
|
int Name(lua_State* state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declare a Lua function in which userdata is in stack slot 1. See DECLARE_LUA_FUNCTION_EX()
|
||||||
|
*/
|
||||||
|
#define DECLARE_LUA_FUNCTION(Class, Name) DECLARE_LUA_FUNCTION_EX(Class, Name, 1)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declare a Lua function in which userdata is in upvalue slot 1. See DECLARE_LUA_FUNCTION_EX()
|
||||||
|
*/
|
||||||
|
#define DECLARE_LUA_CLOSURE(Class, Name) DECLARE_LUA_FUNCTION_EX(Class, Name, lua_upvalueindex(1))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the maximum space needed to be aligned.
|
||||||
|
*/
|
||||||
|
template <typename T> size_t maximumSpaceNeededToAlign() {
|
||||||
|
// The allocated memory can be misaligned up to `alignof(T) - 1` bytes. Adding it to the size to
|
||||||
|
// allocate.
|
||||||
|
return sizeof(T) + alignof(T) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> inline T* alignAndCast(void* mem) {
|
||||||
|
size_t size = maximumSpaceNeededToAlign<T>();
|
||||||
|
return static_cast<T*>(std::align(alignof(T), sizeof(T), mem, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new user data and assign its metatable.
|
||||||
|
*/
|
||||||
|
template <typename T> inline T* allocateLuaUserData(lua_State* state) {
|
||||||
|
void* mem = lua_newuserdata(state, maximumSpaceNeededToAlign<T>());
|
||||||
|
luaL_getmetatable(state, typeid(T).name());
|
||||||
|
ASSERT(lua_istable(state, -1));
|
||||||
|
lua_setmetatable(state, -2);
|
||||||
|
|
||||||
|
return alignAndCast<T>(mem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the base class for all C++ objects that we expose out to Lua. The goal is to hide as
|
||||||
|
* much ugliness as possible. In general, to use this, do the following:
|
||||||
|
* 1) Make your class derive from BaseLuaObject<YourClass>
|
||||||
|
* 2) Define methods using DECLARE_LUA_FUNCTION* macros
|
||||||
|
* 3) Export your functions by declaring a static exportedFunctions() method in your class.
|
||||||
|
* 4) Optionally manage "death" status on your object. (See checkDead() and markDead() below).
|
||||||
|
* 5) Generally you will want to hold your objects inside a LuaRef or a LuaDeathRef. See below
|
||||||
|
* for more information on those containers.
|
||||||
|
*
|
||||||
|
* It's very important to understand the Lua memory model: Once an object is created, *it is
|
||||||
|
* owned by Lua*. Lua can GC it at any time. If you want to make sure that does not happen, you
|
||||||
|
* must hold a ref to it in C++, generally via LuaRef or LuaDeathRef.
|
||||||
|
*/
|
||||||
|
template <class T> class BaseLuaObject {
|
||||||
|
public:
|
||||||
|
using ExportedFunctions = std::vector<std::pair<const char*, lua_CFunction>>;
|
||||||
|
|
||||||
|
virtual ~BaseLuaObject() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new object of this type, owned by Lua. This type must have previously been registered
|
||||||
|
* via the registerType() routine below.
|
||||||
|
* @param state supplies the owning Lua state.
|
||||||
|
* @param args supplies the variadic constructor arguments for the object.
|
||||||
|
* @return a pair containing a pointer to the new object and the state it was created with. (This
|
||||||
|
* is done for convenience when passing a created object to a LuaRef or a LuaDeathRef.
|
||||||
|
*/
|
||||||
|
template <typename... ConstructorArgs>
|
||||||
|
static std::pair<T*, lua_State*> create(lua_State* state, ConstructorArgs&&... args) {
|
||||||
|
// Memory is allocated via Lua and it is raw. We use placement new to run the constructor.
|
||||||
|
T* mem = allocateLuaUserData<T>(state);
|
||||||
|
return {new (mem) T(std::forward<ConstructorArgs>(args)...), state};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a type with Lua.
|
||||||
|
* @param state supplies the state to register with.
|
||||||
|
*/
|
||||||
|
static void registerType(lua_State* state) {
|
||||||
|
std::vector<luaL_Reg> to_register;
|
||||||
|
|
||||||
|
// Fetch all of the functions to be exported to Lua so that we can register them in the
|
||||||
|
// metatable.
|
||||||
|
ExportedFunctions functions = T::exportedFunctions();
|
||||||
|
for (auto function : functions) {
|
||||||
|
to_register.push_back({function.first, function.second});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always register a __gc method so that we can run the object's destructor. We do this
|
||||||
|
// manually because the memory is raw and was allocated by Lua.
|
||||||
|
to_register.push_back(
|
||||||
|
{"__gc", [](lua_State* state) {
|
||||||
|
T* object = alignAndCast<T>(luaL_checkudata(state, 1, typeid(T).name()));
|
||||||
|
object->~T();
|
||||||
|
return 0;
|
||||||
|
}});
|
||||||
|
|
||||||
|
// Add the sentinel.
|
||||||
|
to_register.push_back({nullptr, nullptr});
|
||||||
|
|
||||||
|
// Register the type by creating a new metatable, setting __index to itself, and then
|
||||||
|
// performing the register.
|
||||||
|
luaL_newmetatable(state, typeid(T).name());
|
||||||
|
|
||||||
|
lua_pushvalue(state, -1);
|
||||||
|
lua_setfield(state, -2, "__index");
|
||||||
|
luaL_register(state, nullptr, to_register.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called as part of the DECLARE_LUA_FUNCTION* macros. The idea here is that
|
||||||
|
* we cannot control when Lua destroys things. However, we may expose wrappers to a script that
|
||||||
|
* should not be used after some event. This allows us to mark objects as dead so that if they
|
||||||
|
* are used again they will throw a Lua error and not reach our code.
|
||||||
|
* @param state supplies the calling LuaState.
|
||||||
|
*/
|
||||||
|
int checkDead(lua_State* state) {
|
||||||
|
if (dead_) {
|
||||||
|
return luaL_error(state, "object used outside of proper scope");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark an object as dead so that a checkDead() call will throw an error. See checkDead().
|
||||||
|
*/
|
||||||
|
void markDead() {
|
||||||
|
dead_ = true;
|
||||||
|
onMarkDead();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark an object as live so that a checkDead() call will not throw an error. See checkDead().
|
||||||
|
*/
|
||||||
|
void markLive() {
|
||||||
|
dead_ = false;
|
||||||
|
onMarkLive();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Called from markDead() when an object is marked dead. This is effectively a C++ destructor for
|
||||||
|
* Lua/C objects. Objects can perform inline cleanup or mark other objects as dead if needed. It
|
||||||
|
* can also be used to protect objects from use if they get assigned to a global variable and
|
||||||
|
* used across coroutines.
|
||||||
|
*/
|
||||||
|
virtual void onMarkDead() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from markLive() when an object is marked live. This is a companion to onMarkDead(). See
|
||||||
|
* the comments there.
|
||||||
|
*/
|
||||||
|
virtual void onMarkLive() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool dead_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is basically a Lua smart pointer. The idea is that given a Lua object, if we want to
|
||||||
|
* guarantee that Lua won't destroy it, we need to reference it. This wraps the reference
|
||||||
|
* functionality. While a LuaRef owns an object it's guaranteed that Lua will not GC it.
|
||||||
|
* TODO(mattklein123): Add dedicated unit tests. This will require mocking a Lua state.
|
||||||
|
*/
|
||||||
|
template <typename T> class LuaRef {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Create an empty LuaRef.
|
||||||
|
*/
|
||||||
|
LuaRef() { reset(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a LuaRef from an object.
|
||||||
|
* @param object supplies the object. Generally this is the return value from a Object::create()
|
||||||
|
* call. The object must be at the top of the Lua stack.
|
||||||
|
* @param leave_on_stack supplies whether to leave the object on the stack or not when the ref
|
||||||
|
* is constructed.
|
||||||
|
*/
|
||||||
|
LuaRef(const std::pair<T*, lua_State*>& object, bool leave_on_stack) {
|
||||||
|
reset(object, leave_on_stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
~LuaRef() { unref(); }
|
||||||
|
T* get() { return object_.first; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as the LuaRef non-default constructor, but post-construction.
|
||||||
|
*/
|
||||||
|
void reset(const std::pair<T*, lua_State*>& object, bool leave_on_stack) {
|
||||||
|
unref();
|
||||||
|
|
||||||
|
if (leave_on_stack) {
|
||||||
|
lua_pushvalue(object.second, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
object_ = object;
|
||||||
|
ref_ = luaL_ref(object_.second, LUA_REGISTRYINDEX);
|
||||||
|
ASSERT(ref_ != LUA_REFNIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a LuaRef to its default/empty state.
|
||||||
|
*/
|
||||||
|
void reset() {
|
||||||
|
unref();
|
||||||
|
object_ = std::pair<T*, lua_State*>{};
|
||||||
|
ref_ = LUA_NOREF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push the referenced object back onto the stack.
|
||||||
|
*/
|
||||||
|
void pushStack() {
|
||||||
|
ASSERT(object_.first);
|
||||||
|
lua_rawgeti(object_.second, LUA_REGISTRYINDEX, ref_);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void unref() {
|
||||||
|
if (object_.second != nullptr) {
|
||||||
|
luaL_unref(object_.second, LUA_REGISTRYINDEX, ref_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<T*, lua_State*> object_;
|
||||||
|
int ref_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a variant of LuaRef which also marks an object as dead during destruction. This is
|
||||||
|
* useful if an object should not be used after the scope of the pcall() or resume().
|
||||||
|
* TODO(mattklein123): Add dedicated unit tests. This will require mocking a Lua state.
|
||||||
|
*/
|
||||||
|
template <typename T> class LuaDeathRef : public LuaRef<T> {
|
||||||
|
public:
|
||||||
|
~LuaDeathRef() { markDead(); }
|
||||||
|
|
||||||
|
void markDead() {
|
||||||
|
if (this->object_.first) {
|
||||||
|
this->object_.first->markDead();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void markLive() {
|
||||||
|
if (this->object_.first) {
|
||||||
|
this->object_.first->markLive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset(const std::pair<T*, lua_State*>& object, bool leave_on_stack) {
|
||||||
|
markDead();
|
||||||
|
LuaRef<T>::reset(object, leave_on_stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
markDead();
|
||||||
|
LuaRef<T>::reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
41
envoy/main.cpp
Normal file
41
envoy/main.cpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#include "envoy.h"
|
||||||
|
|
||||||
|
int callLua(lua_State* state, const char *func)
|
||||||
|
{
|
||||||
|
lua::LuaDeathRef<lua::StreamHandleWrapper> handle;
|
||||||
|
lua_getglobal(state, func);
|
||||||
|
if (lua_isfunction(state, -1)) {
|
||||||
|
handle.reset(lua::StreamHandleWrapper::create(state), true);
|
||||||
|
lua_call(state, 1, 0);
|
||||||
|
}
|
||||||
|
lua_pop(state, 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
if (argc < 1) {
|
||||||
|
printf("usage: envoy-apisix [lua file]\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_State* state = luaL_newstate();
|
||||||
|
luaL_openlibs(state);
|
||||||
|
|
||||||
|
lua::BufferWrapper::registerType(state);
|
||||||
|
lua::HeaderMapIterator::registerType(state);
|
||||||
|
lua::HeaderMapWrapper::registerType(state);
|
||||||
|
lua::MetadataMapWrapper::registerType(state);
|
||||||
|
lua::MetadataMapIterator::registerType(state);
|
||||||
|
lua::StreamInfoWrapper::registerType(state);
|
||||||
|
lua::StreamHandleWrapper::registerType(state);
|
||||||
|
|
||||||
|
if (luaL_dofile(state, argv[1])) {
|
||||||
|
printf("script load error: %s\n", lua_tostring(state, -1));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
callLua(state, "envoy_on_request");
|
||||||
|
|
||||||
|
lua_close(state);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
4
envoy/packages.config
Normal file
4
envoy/packages.config
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
<package id="luajit.native" version="2.0.5" targetFramework="Native" />
|
||||||
|
</packages>
|
||||||
7
plugins/core.lua
Normal file
7
plugins/core.lua
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
return {
|
||||||
|
crypto = require("core.crypto"),
|
||||||
|
json = require("core.json"),
|
||||||
|
table = require("core.table"),
|
||||||
|
request = require("core.request"),
|
||||||
|
}
|
||||||
39
plugins/crypto.lua
Normal file
39
plugins/crypto.lua
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
local ffi = require('ffi')
|
||||||
|
local crypto = ffi.load("crypto.so.1.1", true)
|
||||||
|
local ffi_string = ffi.string
|
||||||
|
|
||||||
|
ffi.cdef[[
|
||||||
|
char *MD5(const char *d, size_t n, char *md);
|
||||||
|
int EVP_DecodeBlock(unsigned char *t, const unsigned char *f, int n);
|
||||||
|
int EVP_EncodeBlock(unsigned char *t, const unsigned char *f, int n);
|
||||||
|
]]
|
||||||
|
|
||||||
|
local _M = { version = 0.2 }
|
||||||
|
|
||||||
|
function string.tohex(str)
|
||||||
|
return (str:gsub('.', function (c)
|
||||||
|
return string.format('%02X', string.byte(c))
|
||||||
|
end))
|
||||||
|
end
|
||||||
|
|
||||||
|
function _M.md5(data)
|
||||||
|
local buf = ffi.new("char[16]")
|
||||||
|
crypto.MD5(data, #data, buf)
|
||||||
|
return ffi_string(buf):tohex()
|
||||||
|
end
|
||||||
|
|
||||||
|
function _M.decode_base64(data)
|
||||||
|
local len = #data - #data/4
|
||||||
|
local buf = ffi.new("unsigned char["..len.."]")
|
||||||
|
len = crypto.EVP_DecodeBlock(buf, data, #data)
|
||||||
|
return ffi_string(buf, len - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _M.encode_base64(data)
|
||||||
|
local len = #data + #data/4
|
||||||
|
local buf = ffi.new("unsigned char["..len.."]")
|
||||||
|
len = crypto.EVP_EncodeBlock(buf, data, #data)
|
||||||
|
return ffi_string(buf, len - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
return _M
|
||||||
46
plugins/ctx.lua
Normal file
46
plugins/ctx.lua
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
local _M = { version = 0.2 }
|
||||||
|
|
||||||
|
local function get_client_ip(stream)
|
||||||
|
local ip = stream:downstreamLocalAddress()
|
||||||
|
if ip then
|
||||||
|
return ip
|
||||||
|
end
|
||||||
|
|
||||||
|
ip = stream:downstreamDirectRemoteAddress()
|
||||||
|
if ip then
|
||||||
|
return ip
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function _M.set_vars_meta(handle)
|
||||||
|
local stream = handle:streamInfo()
|
||||||
|
local meta = stream:dynamicMetadata()
|
||||||
|
|
||||||
|
local var = {}
|
||||||
|
var._cache = meta:get("envoy.filters.http.lua") or {}
|
||||||
|
var.remote_addr = get_client_ip(stream)
|
||||||
|
|
||||||
|
setmetatable(var, {
|
||||||
|
__index = function(self, key)
|
||||||
|
local cached = self._cache[key]
|
||||||
|
if cached ~= nil then
|
||||||
|
return cached
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end,
|
||||||
|
|
||||||
|
__newindex = function(self, key, val)
|
||||||
|
meta:set("envoy.filters.http.lua", key, val)
|
||||||
|
self._cache[key] = val
|
||||||
|
end,
|
||||||
|
|
||||||
|
__pairs = function (self)
|
||||||
|
return next, self._cache, nil
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
return var
|
||||||
|
end
|
||||||
|
|
||||||
|
return _M
|
||||||
15
plugins/entry.lua
Normal file
15
plugins/entry.lua
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
local plugin = require("core.plugin")
|
||||||
|
|
||||||
|
function envoy_on_request(request_handle)
|
||||||
|
local conf = request_handle:metadata():get("plugins")
|
||||||
|
if conf then
|
||||||
|
plugin.run(request_handle, "request", conf)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function envoy_on_response(response_handle)
|
||||||
|
local conf = response_handle:metadata():get("plugins")
|
||||||
|
if conf then
|
||||||
|
plugin.run(response_handle, "response", conf)
|
||||||
|
end
|
||||||
|
end
|
||||||
110
plugins/envoy.yaml
Normal file
110
plugins/envoy.yaml
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# docker run --rm --network host -v $(pwd):/etc/envoy -w /etc/envoy -it envoyproxy/envoy-distroless:v1.24.0
|
||||||
|
# curl -u 'admin:admin' -H 'Origin: http://lenovo.com' -i http://vcap.me:10000/ip
|
||||||
|
|
||||||
|
static_resources:
|
||||||
|
listeners:
|
||||||
|
- address:
|
||||||
|
socket_address:
|
||||||
|
address: 0.0.0.0
|
||||||
|
port_value: 10000
|
||||||
|
filter_chains:
|
||||||
|
- filters:
|
||||||
|
- name: envoy.filters.network.http_connection_manager
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||||
|
codec_type: AUTO
|
||||||
|
stat_prefix: ingress_http
|
||||||
|
strip_any_host_port: true
|
||||||
|
route_config:
|
||||||
|
name: local_route
|
||||||
|
virtual_hosts:
|
||||||
|
- name: vcap_me
|
||||||
|
domains:
|
||||||
|
- vcap.me
|
||||||
|
routes:
|
||||||
|
- match:
|
||||||
|
path: /ip
|
||||||
|
route:
|
||||||
|
auto_host_rewrite: true
|
||||||
|
cluster: httpbin
|
||||||
|
metadata:
|
||||||
|
filter_metadata:
|
||||||
|
envoy.filters.http.lua:
|
||||||
|
plugins:
|
||||||
|
- name: user-code
|
||||||
|
conf:
|
||||||
|
access: |
|
||||||
|
ctx.var.requst = {
|
||||||
|
path = ctx.headers:get(":path")
|
||||||
|
}
|
||||||
|
- name: basic-auth
|
||||||
|
conf:
|
||||||
|
username: admin
|
||||||
|
password: admin
|
||||||
|
- name: redirect
|
||||||
|
conf:
|
||||||
|
ret_code: 301
|
||||||
|
uri: /headers
|
||||||
|
headers:
|
||||||
|
x-earth-token: xxx
|
||||||
|
x-earth-project: yyy
|
||||||
|
- match:
|
||||||
|
path: /body
|
||||||
|
direct_response:
|
||||||
|
status: 200
|
||||||
|
body:
|
||||||
|
inline_string: "Body\n"
|
||||||
|
metadata:
|
||||||
|
filter_metadata:
|
||||||
|
envoy.filters.http.lua:
|
||||||
|
plugins:
|
||||||
|
- name: user-code
|
||||||
|
conf:
|
||||||
|
access: |
|
||||||
|
ctx.var.path = ctx.headers:get(":path")
|
||||||
|
ctx.var.method = ctx.headers:get(":method")
|
||||||
|
ctx.log.info("hit access")
|
||||||
|
body_filter: |
|
||||||
|
for key, value in pairs(ctx.var) do
|
||||||
|
ctx.log.info("var: ", key, " = ", value)
|
||||||
|
end
|
||||||
|
ctx.log.info("hit body_filter")
|
||||||
|
- match:
|
||||||
|
path: /auth
|
||||||
|
direct_response:
|
||||||
|
status: 200
|
||||||
|
body:
|
||||||
|
inline_string: "Autherd\n"
|
||||||
|
metadata:
|
||||||
|
filter_metadata:
|
||||||
|
envoy.filters.http.lua:
|
||||||
|
plugins:
|
||||||
|
- name: basic-auth
|
||||||
|
conf:
|
||||||
|
htpasswd: | # htpasswd -bnBC 10 admin admin
|
||||||
|
admin:$2y$10$7y/gRzOG6zhB5WOnGp8xw.wMF9c4Fw6ZkPwaALHlMNFG5IZy1W3Um
|
||||||
|
http_filters:
|
||||||
|
- name: envoy.filters.http.lua
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
|
||||||
|
default_source_code:
|
||||||
|
filename: /etc/envoy/entry.lua
|
||||||
|
- name: envoy.filters.http.router
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
||||||
|
access_log:
|
||||||
|
- name: envoy.access_loggers.stdout
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
|
||||||
|
clusters:
|
||||||
|
- name: httpbin
|
||||||
|
type: LOGICAL_DNS
|
||||||
|
load_assignment:
|
||||||
|
cluster_name: httpbin
|
||||||
|
endpoints:
|
||||||
|
- lb_endpoints:
|
||||||
|
- endpoint:
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: httpbin.org
|
||||||
|
port_value: 80
|
||||||
388
plugins/json.lua
Normal file
388
plugins/json.lua
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
--
|
||||||
|
-- json.lua
|
||||||
|
--
|
||||||
|
-- Copyright (c) 2020 rxi
|
||||||
|
--
|
||||||
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
-- this software and associated documentation files (the "Software"), to deal in
|
||||||
|
-- the Software without restriction, including without limitation the rights to
|
||||||
|
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
-- of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
-- so, subject to the following conditions:
|
||||||
|
--
|
||||||
|
-- The above copyright notice and this permission notice shall be included in all
|
||||||
|
-- copies or substantial portions of the Software.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
-- SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
local json = { _version = "0.1.2" }
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
-- Encode
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local encode
|
||||||
|
|
||||||
|
local escape_char_map = {
|
||||||
|
[ "\\" ] = "\\",
|
||||||
|
[ "\"" ] = "\"",
|
||||||
|
[ "\b" ] = "b",
|
||||||
|
[ "\f" ] = "f",
|
||||||
|
[ "\n" ] = "n",
|
||||||
|
[ "\r" ] = "r",
|
||||||
|
[ "\t" ] = "t",
|
||||||
|
}
|
||||||
|
|
||||||
|
local escape_char_map_inv = { [ "/" ] = "/" }
|
||||||
|
for k, v in pairs(escape_char_map) do
|
||||||
|
escape_char_map_inv[v] = k
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function escape_char(c)
|
||||||
|
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function encode_nil(val)
|
||||||
|
return "null"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function encode_table(val, stack)
|
||||||
|
local res = {}
|
||||||
|
stack = stack or {}
|
||||||
|
|
||||||
|
-- Circular reference?
|
||||||
|
if stack[val] then error("circular reference") end
|
||||||
|
|
||||||
|
stack[val] = true
|
||||||
|
|
||||||
|
if rawget(val, 1) ~= nil or next(val) == nil then
|
||||||
|
-- Treat as array -- check keys are valid and it is not sparse
|
||||||
|
local n = 0
|
||||||
|
for k in pairs(val) do
|
||||||
|
if type(k) ~= "number" then
|
||||||
|
error("invalid table: mixed or invalid key types")
|
||||||
|
end
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
if n ~= #val then
|
||||||
|
error("invalid table: sparse array")
|
||||||
|
end
|
||||||
|
-- Encode
|
||||||
|
for i, v in ipairs(val) do
|
||||||
|
table.insert(res, encode(v, stack))
|
||||||
|
end
|
||||||
|
stack[val] = nil
|
||||||
|
return "[" .. table.concat(res, ",") .. "]"
|
||||||
|
|
||||||
|
else
|
||||||
|
-- Treat as an object
|
||||||
|
for k, v in pairs(val) do
|
||||||
|
if type(k) ~= "string" then
|
||||||
|
error("invalid table: mixed or invalid key types")
|
||||||
|
end
|
||||||
|
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||||
|
end
|
||||||
|
stack[val] = nil
|
||||||
|
return "{" .. table.concat(res, ",") .. "}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function encode_string(val)
|
||||||
|
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function encode_number(val)
|
||||||
|
-- Check for NaN, -inf and inf
|
||||||
|
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||||
|
error("unexpected number value '" .. tostring(val) .. "'")
|
||||||
|
end
|
||||||
|
return string.format("%.14g", val)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local type_func_map = {
|
||||||
|
[ "nil" ] = encode_nil,
|
||||||
|
[ "table" ] = encode_table,
|
||||||
|
[ "string" ] = encode_string,
|
||||||
|
[ "number" ] = encode_number,
|
||||||
|
[ "boolean" ] = tostring,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
encode = function(val, stack)
|
||||||
|
local t = type(val)
|
||||||
|
local f = type_func_map[t]
|
||||||
|
if f then
|
||||||
|
return f(val, stack)
|
||||||
|
end
|
||||||
|
error("unexpected type '" .. t .. "'")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function json.encode(val)
|
||||||
|
return ( encode(val) )
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
-- Decode
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local parse
|
||||||
|
|
||||||
|
local function create_set(...)
|
||||||
|
local res = {}
|
||||||
|
for i = 1, select("#", ...) do
|
||||||
|
res[ select(i, ...) ] = true
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||||
|
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||||
|
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||||
|
local literals = create_set("true", "false", "null")
|
||||||
|
|
||||||
|
local literal_map = {
|
||||||
|
[ "true" ] = true,
|
||||||
|
[ "false" ] = false,
|
||||||
|
[ "null" ] = nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
local function next_char(str, idx, set, negate)
|
||||||
|
for i = idx, #str do
|
||||||
|
if set[str:sub(i, i)] ~= negate then
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return #str + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function decode_error(str, idx, msg)
|
||||||
|
local line_count = 1
|
||||||
|
local col_count = 1
|
||||||
|
for i = 1, idx - 1 do
|
||||||
|
col_count = col_count + 1
|
||||||
|
if str:sub(i, i) == "\n" then
|
||||||
|
line_count = line_count + 1
|
||||||
|
col_count = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function codepoint_to_utf8(n)
|
||||||
|
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||||
|
local f = math.floor
|
||||||
|
if n <= 0x7f then
|
||||||
|
return string.char(n)
|
||||||
|
elseif n <= 0x7ff then
|
||||||
|
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||||
|
elseif n <= 0xffff then
|
||||||
|
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||||
|
elseif n <= 0x10ffff then
|
||||||
|
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||||
|
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||||
|
end
|
||||||
|
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function parse_unicode_escape(s)
|
||||||
|
local n1 = tonumber( s:sub(1, 4), 16 )
|
||||||
|
local n2 = tonumber( s:sub(7, 10), 16 )
|
||||||
|
-- Surrogate pair?
|
||||||
|
if n2 then
|
||||||
|
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||||
|
else
|
||||||
|
return codepoint_to_utf8(n1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function parse_string(str, i)
|
||||||
|
local res = ""
|
||||||
|
local j = i + 1
|
||||||
|
local k = j
|
||||||
|
|
||||||
|
while j <= #str do
|
||||||
|
local x = str:byte(j)
|
||||||
|
|
||||||
|
if x < 32 then
|
||||||
|
decode_error(str, j, "control character in string")
|
||||||
|
|
||||||
|
elseif x == 92 then -- `\`: Escape
|
||||||
|
res = res .. str:sub(k, j - 1)
|
||||||
|
j = j + 1
|
||||||
|
local c = str:sub(j, j)
|
||||||
|
if c == "u" then
|
||||||
|
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
|
||||||
|
or str:match("^%x%x%x%x", j + 1)
|
||||||
|
or decode_error(str, j - 1, "invalid unicode escape in string")
|
||||||
|
res = res .. parse_unicode_escape(hex)
|
||||||
|
j = j + #hex
|
||||||
|
else
|
||||||
|
if not escape_chars[c] then
|
||||||
|
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
|
||||||
|
end
|
||||||
|
res = res .. escape_char_map_inv[c]
|
||||||
|
end
|
||||||
|
k = j + 1
|
||||||
|
|
||||||
|
elseif x == 34 then -- `"`: End of string
|
||||||
|
res = res .. str:sub(k, j - 1)
|
||||||
|
return res, j + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
j = j + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
decode_error(str, i, "expected closing quote for string")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function parse_number(str, i)
|
||||||
|
local x = next_char(str, i, delim_chars)
|
||||||
|
local s = str:sub(i, x - 1)
|
||||||
|
local n = tonumber(s)
|
||||||
|
if not n then
|
||||||
|
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||||
|
end
|
||||||
|
return n, x
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function parse_literal(str, i)
|
||||||
|
local x = next_char(str, i, delim_chars)
|
||||||
|
local word = str:sub(i, x - 1)
|
||||||
|
if not literals[word] then
|
||||||
|
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||||
|
end
|
||||||
|
return literal_map[word], x
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function parse_array(str, i)
|
||||||
|
local res = {}
|
||||||
|
local n = 1
|
||||||
|
i = i + 1
|
||||||
|
while 1 do
|
||||||
|
local x
|
||||||
|
i = next_char(str, i, space_chars, true)
|
||||||
|
-- Empty / end of array?
|
||||||
|
if str:sub(i, i) == "]" then
|
||||||
|
i = i + 1
|
||||||
|
break
|
||||||
|
end
|
||||||
|
-- Read token
|
||||||
|
x, i = parse(str, i)
|
||||||
|
res[n] = x
|
||||||
|
n = n + 1
|
||||||
|
-- Next token
|
||||||
|
i = next_char(str, i, space_chars, true)
|
||||||
|
local chr = str:sub(i, i)
|
||||||
|
i = i + 1
|
||||||
|
if chr == "]" then break end
|
||||||
|
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||||
|
end
|
||||||
|
return res, i
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function parse_object(str, i)
|
||||||
|
local res = {}
|
||||||
|
i = i + 1
|
||||||
|
while 1 do
|
||||||
|
local key, val
|
||||||
|
i = next_char(str, i, space_chars, true)
|
||||||
|
-- Empty / end of object?
|
||||||
|
if str:sub(i, i) == "}" then
|
||||||
|
i = i + 1
|
||||||
|
break
|
||||||
|
end
|
||||||
|
-- Read key
|
||||||
|
if str:sub(i, i) ~= '"' then
|
||||||
|
decode_error(str, i, "expected string for key")
|
||||||
|
end
|
||||||
|
key, i = parse(str, i)
|
||||||
|
-- Read ':' delimiter
|
||||||
|
i = next_char(str, i, space_chars, true)
|
||||||
|
if str:sub(i, i) ~= ":" then
|
||||||
|
decode_error(str, i, "expected ':' after key")
|
||||||
|
end
|
||||||
|
i = next_char(str, i + 1, space_chars, true)
|
||||||
|
-- Read value
|
||||||
|
val, i = parse(str, i)
|
||||||
|
-- Set
|
||||||
|
res[key] = val
|
||||||
|
-- Next token
|
||||||
|
i = next_char(str, i, space_chars, true)
|
||||||
|
local chr = str:sub(i, i)
|
||||||
|
i = i + 1
|
||||||
|
if chr == "}" then break end
|
||||||
|
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||||
|
end
|
||||||
|
return res, i
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local char_func_map = {
|
||||||
|
[ '"' ] = parse_string,
|
||||||
|
[ "0" ] = parse_number,
|
||||||
|
[ "1" ] = parse_number,
|
||||||
|
[ "2" ] = parse_number,
|
||||||
|
[ "3" ] = parse_number,
|
||||||
|
[ "4" ] = parse_number,
|
||||||
|
[ "5" ] = parse_number,
|
||||||
|
[ "6" ] = parse_number,
|
||||||
|
[ "7" ] = parse_number,
|
||||||
|
[ "8" ] = parse_number,
|
||||||
|
[ "9" ] = parse_number,
|
||||||
|
[ "-" ] = parse_number,
|
||||||
|
[ "t" ] = parse_literal,
|
||||||
|
[ "f" ] = parse_literal,
|
||||||
|
[ "n" ] = parse_literal,
|
||||||
|
[ "[" ] = parse_array,
|
||||||
|
[ "{" ] = parse_object,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
parse = function(str, idx)
|
||||||
|
local chr = str:sub(idx, idx)
|
||||||
|
local f = char_func_map[chr]
|
||||||
|
if f then
|
||||||
|
return f(str, idx)
|
||||||
|
end
|
||||||
|
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function json.decode(str)
|
||||||
|
if type(str) ~= "string" then
|
||||||
|
error("expected argument of type string, got " .. type(str))
|
||||||
|
end
|
||||||
|
local res, idx = parse(str, next_char(str, 1, space_chars, true))
|
||||||
|
idx = next_char(str, idx, space_chars, true)
|
||||||
|
if idx <= #str then
|
||||||
|
decode_error(str, idx, "trailing garbage")
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return json
|
||||||
41
plugins/log.lua
Normal file
41
plugins/log.lua
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
local _M = { }
|
||||||
|
|
||||||
|
local log_levels = {
|
||||||
|
crit = "logCritical",
|
||||||
|
error = "logErr",
|
||||||
|
warn = "logWarn",
|
||||||
|
notice = "logTrace",
|
||||||
|
info = "logInfo",
|
||||||
|
debug = "logDebug",
|
||||||
|
}
|
||||||
|
|
||||||
|
local _tostring = tostring
|
||||||
|
local tostring = function(...)
|
||||||
|
local t = {}
|
||||||
|
for i = 1, select('#', ...) do
|
||||||
|
local x = select(i, ...)
|
||||||
|
if type(x) == "number" then
|
||||||
|
x = round(x, .01)
|
||||||
|
end
|
||||||
|
t[#t + 1] = _tostring(x)
|
||||||
|
end
|
||||||
|
return table.concat(t, " ")
|
||||||
|
end
|
||||||
|
|
||||||
|
function _M.new(handle)
|
||||||
|
local o = {}
|
||||||
|
setmetatable(o, {__index = function(self, cmd)
|
||||||
|
local t = getmetatable(handle)
|
||||||
|
local method = rawget(t, log_levels[cmd])
|
||||||
|
if not method then
|
||||||
|
return do_nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(...)
|
||||||
|
method(handle, tostring(...))
|
||||||
|
end
|
||||||
|
end})
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
return _M
|
||||||
62
plugins/plugin.lua
Normal file
62
plugins/plugin.lua
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
local log = require("core.log")
|
||||||
|
local ctx = require("core.ctx")
|
||||||
|
local json = require("core.json")
|
||||||
|
|
||||||
|
local _M = {version = 0.2}
|
||||||
|
|
||||||
|
local phases = {
|
||||||
|
request = {
|
||||||
|
'access',
|
||||||
|
'rewrite'
|
||||||
|
},
|
||||||
|
response = {
|
||||||
|
'header_filter',
|
||||||
|
'body_filter'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _M.run(handle, phase, plugins)
|
||||||
|
if not plugins or #plugins == 0 then
|
||||||
|
return ctx
|
||||||
|
end
|
||||||
|
|
||||||
|
local resp_header = {}
|
||||||
|
|
||||||
|
local context = {
|
||||||
|
headers = handle:headers(),
|
||||||
|
body = handle:body(),
|
||||||
|
log = log.new(handle),
|
||||||
|
var = ctx.set_vars_meta(handle),
|
||||||
|
}
|
||||||
|
|
||||||
|
function context.set_resp_header(key, value)
|
||||||
|
resp_header[key] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, plugin in ipairs(plugins) do
|
||||||
|
local ok, plugin_object = pcall(require, "plugins." .. plugin.name)
|
||||||
|
if ok then
|
||||||
|
local earth_phases = phases[phase]
|
||||||
|
for _, phase_name in ipairs(earth_phases) do
|
||||||
|
local phase_func = plugin_object[phase_name]
|
||||||
|
if type(phase_func) == "function" then
|
||||||
|
handle:logTrace("phase_name: " .. plugin.name .. "." .. phase_name)
|
||||||
|
local status, body = phase_func(plugin.conf, context)
|
||||||
|
if status then
|
||||||
|
resp_header[":status"] = status
|
||||||
|
if type(body) == "table" then
|
||||||
|
body = json.encode(body)
|
||||||
|
end
|
||||||
|
return handle:respond(resp_header, body)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
handle:logWarn("failed to load plugin ["..plugin.name.."] err: "..plugin_object)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
end
|
||||||
|
|
||||||
|
return _M
|
||||||
64
plugins/plugins/basic-auth.lua
Normal file
64
plugins/plugins/basic-auth.lua
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
local core = require("core")
|
||||||
|
|
||||||
|
local plugin_name = "basic-auth"
|
||||||
|
|
||||||
|
local _M = {
|
||||||
|
version = 0.1,
|
||||||
|
priority = 2520,
|
||||||
|
type = 'auth',
|
||||||
|
name = plugin_name,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function extract_auth_header(auth)
|
||||||
|
local obj = { username = "", password = "" }
|
||||||
|
local userpass = auth:match("Basic%s+(.*)")
|
||||||
|
if not userpass then
|
||||||
|
return nil, "Invalid authorization header format"
|
||||||
|
end
|
||||||
|
|
||||||
|
local decoded = core.crypto.decode_base64(userpass)
|
||||||
|
if not decoded then
|
||||||
|
return nil, "Failed to decode authentication header: " .. m[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
user, pass = decoded:match("([^:]*):(.*)")
|
||||||
|
obj.username = user:gsub("%s+", "")
|
||||||
|
obj.password = pass:gsub("%s+", "")
|
||||||
|
return obj, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function _M.rewrite(conf, ctx)
|
||||||
|
ctx.log.info("plugin access phase, conf: ", core.json.encode(conf))
|
||||||
|
|
||||||
|
-- 1. extract authorization from header
|
||||||
|
local auth_header = core.request.header(ctx, "Authorization")
|
||||||
|
if not auth_header then
|
||||||
|
ctx.set_resp_header("WWW-Authenticate", "Basic realm='.'")
|
||||||
|
return 401, { message = "Missing authorization in request" }
|
||||||
|
end
|
||||||
|
|
||||||
|
local user, err = extract_auth_header(auth_header)
|
||||||
|
if err then
|
||||||
|
ctx.log.warn("extract auth header: ", err)
|
||||||
|
return 401, { message = "Invalid authorization in request" }
|
||||||
|
end
|
||||||
|
ctx.log.info("plugin access phase, authorization: ", user.username, ":", user.password)
|
||||||
|
|
||||||
|
-- 2. get user info from cache
|
||||||
|
|
||||||
|
-- 4. check the password is correct
|
||||||
|
if conf.password ~= user.password then
|
||||||
|
ctx.log.info("check: ["..type(conf.password).."], ["..type(user.password).."]")
|
||||||
|
ctx.log.info("check: ["..#conf.password.."], ["..#user.password.."\0".."]")
|
||||||
|
return 401, { message = "Invalid user authorization" }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 5. hide `Authorization` request header if `hide_credentials` is `true`
|
||||||
|
if conf.hide_credentials then
|
||||||
|
ctx.headers:remove("Authorization")
|
||||||
|
end
|
||||||
|
|
||||||
|
ctx.log.info("hit basic-auth access")
|
||||||
|
end
|
||||||
|
|
||||||
|
return _M
|
||||||
84
plugins/plugins/ldap-auth.lua
Normal file
84
plugins/plugins/ldap-auth.lua
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
local core = require("core")
|
||||||
|
local ldap = require("resty.ldap")
|
||||||
|
|
||||||
|
local plugin_name = "ldap-auth"
|
||||||
|
|
||||||
|
local _M = {
|
||||||
|
version = 0.1,
|
||||||
|
priority = 2540,
|
||||||
|
type = 'auth',
|
||||||
|
name = plugin_name,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function extract_auth_header(auth)
|
||||||
|
local obj = { username = "", password = "" }
|
||||||
|
local userpass = auth:match("Basic%s+(.*)")
|
||||||
|
if not userpass then
|
||||||
|
return nil, "Invalid authorization header format"
|
||||||
|
end
|
||||||
|
|
||||||
|
local decoded = core.crypto.decode_base64(userpass)
|
||||||
|
if not decoded then
|
||||||
|
return nil, "Failed to decode authentication header: " .. m[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
user, pass = decoded:match("([^:]*):(.*)")
|
||||||
|
obj.username = user:gsub("%s+", "")
|
||||||
|
obj.password = pass:gsub("%s+", "")
|
||||||
|
return obj, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function _M.rewrite(conf, ctx)
|
||||||
|
ctx.log.info("plugin rewrite phase, conf: ", core.json.encode(conf))
|
||||||
|
|
||||||
|
-- 1. extract authorization from header
|
||||||
|
local auth_header = core.request.header(ctx, "Authorization")
|
||||||
|
if not auth_header then
|
||||||
|
ctx.set_resp_header("WWW-Authenticate", "Basic realm='.'")
|
||||||
|
return 401, { message = "Missing authorization in request" }
|
||||||
|
end
|
||||||
|
|
||||||
|
local user, err = extract_auth_header(auth_header)
|
||||||
|
if err then
|
||||||
|
ctx.log.warn(err)
|
||||||
|
return 401, { message = "Invalid authorization in request" }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 2. try authenticate the user against the ldap server
|
||||||
|
local ldap_host, ldap_port = core.utils.parse_addr(conf.ldap_uri)
|
||||||
|
|
||||||
|
local userdn = conf.uid .. "=" .. user.username .. "," .. conf.base_dn
|
||||||
|
local ldapconf = {
|
||||||
|
timeout = 10000,
|
||||||
|
start_tls = false,
|
||||||
|
ldap_host = ldap_host,
|
||||||
|
ldap_port = ldap_port or 389,
|
||||||
|
ldaps = conf.use_tls,
|
||||||
|
tls_verify = conf.tls_verify,
|
||||||
|
base_dn = conf.base_dn,
|
||||||
|
attribute = conf.uid,
|
||||||
|
keepalive = 60000,
|
||||||
|
}
|
||||||
|
local res, err = ldap.ldap_authenticate(user.username, user.password, ldapconf)
|
||||||
|
if not res then
|
||||||
|
ctx.log.warn("ldap-auth failed: ", err)
|
||||||
|
return 401, { message = "Invalid user authorization" }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 3. Retrieve consumer for authorization plugin
|
||||||
|
local consumer_conf = consumer_mod.plugin(plugin_name)
|
||||||
|
if not consumer_conf then
|
||||||
|
return 401, { message = "Missing related consumer" }
|
||||||
|
end
|
||||||
|
local consumers = lrucache("consumers_key", consumer_conf.conf_version,
|
||||||
|
create_consumer_cache, consumer_conf)
|
||||||
|
local consumer = consumers[userdn]
|
||||||
|
if not consumer then
|
||||||
|
return 401, {message = "Invalid user authorization"}
|
||||||
|
end
|
||||||
|
consumer_mod.attach_consumer(ctx, consumer, consumer_conf)
|
||||||
|
|
||||||
|
ctx.log.info("hit basic-auth access")
|
||||||
|
end
|
||||||
|
|
||||||
|
return _M
|
||||||
26
plugins/plugins/redirect.lua
Normal file
26
plugins/plugins/redirect.lua
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
local core = require("core")
|
||||||
|
|
||||||
|
local plugin_name = "redirect"
|
||||||
|
|
||||||
|
local _M = {
|
||||||
|
version = 0.1,
|
||||||
|
priority = 900,
|
||||||
|
name = plugin_name
|
||||||
|
}
|
||||||
|
|
||||||
|
function _M.rewrite(conf, ctx)
|
||||||
|
local ret_code = conf.ret_code
|
||||||
|
local uri = conf.uri
|
||||||
|
|
||||||
|
if conf.http_to_https and ctx.var.scheme == "http" then
|
||||||
|
uri = "https://$host$request_uri"
|
||||||
|
ret_code = 301
|
||||||
|
end
|
||||||
|
|
||||||
|
if uri and ret_code then
|
||||||
|
ctx.set_resp_header("Location", uri)
|
||||||
|
return ret_code
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return _M
|
||||||
29
plugins/plugins/user-code.lua
Normal file
29
plugins/plugins/user-code.lua
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
local plugin_name = "user-code"
|
||||||
|
|
||||||
|
local _M = {
|
||||||
|
version = 0.1,
|
||||||
|
priority = 2520,
|
||||||
|
name = plugin_name
|
||||||
|
}
|
||||||
|
|
||||||
|
local function eval(equation, variables)
|
||||||
|
if(type(equation) == "string") then
|
||||||
|
local eval = load(equation);
|
||||||
|
if(type(eval) == "function") then
|
||||||
|
setfenv(eval, variables or {});
|
||||||
|
return eval();
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setmetatable(_M, {__index = function(self, cmd)
|
||||||
|
return function(conf, ctx)
|
||||||
|
local equation = rawget(conf, cmd)
|
||||||
|
return eval(equation, {
|
||||||
|
pairs = pairs,
|
||||||
|
ctx = ctx,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end})
|
||||||
|
|
||||||
|
return _M
|
||||||
30
plugins/request.lua
Normal file
30
plugins/request.lua
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
local encode_json = require("core.json").encode
|
||||||
|
local concat_tab = table.concat
|
||||||
|
|
||||||
|
local _M = { version = 0.1 }
|
||||||
|
|
||||||
|
function _M.header(ctx, name)
|
||||||
|
return ctx.headers:get(name)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _M.set_header(ctx, name, value)
|
||||||
|
return ctx.headers:replace(name, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _M.get_ip(ctx)
|
||||||
|
return ctx.var.remote_addr or ctx.headers:get("x-forwarded-for")
|
||||||
|
end
|
||||||
|
|
||||||
|
function _M.getbody(max_size, ctx)
|
||||||
|
return ctx.body:getBytes(0, max_size)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _M.get_scheme(ctx)
|
||||||
|
return ctx.headers:get(":scheme")
|
||||||
|
end
|
||||||
|
|
||||||
|
function _M.get_host(ctx)
|
||||||
|
return ctx.headers:get(":authority")
|
||||||
|
end
|
||||||
|
|
||||||
|
return _M
|
||||||
68
plugins/string.lua
Normal file
68
plugins/string.lua
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
--
|
||||||
|
-- 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.
|
||||||
|
--
|
||||||
|
local error = error
|
||||||
|
local type = type
|
||||||
|
local str_find = string.find
|
||||||
|
local ffi = require("ffi")
|
||||||
|
local C = ffi.C
|
||||||
|
local ffi_cast = ffi.cast
|
||||||
|
|
||||||
|
|
||||||
|
ffi.cdef[[
|
||||||
|
int memcmp(const void *s1, const void *s2, size_t n);
|
||||||
|
]]
|
||||||
|
|
||||||
|
|
||||||
|
local _M = {
|
||||||
|
version = 0.1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setmetatable(_M, {__index = string})
|
||||||
|
|
||||||
|
|
||||||
|
-- find a needle from a haystack in the plain text way
|
||||||
|
function _M.find(haystack, needle, from)
|
||||||
|
return str_find(haystack, needle, from or 1, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function _M.has_prefix(s, prefix)
|
||||||
|
if type(s) ~= "string" or type(prefix) ~= "string" then
|
||||||
|
error("unexpected type: s:" .. type(s) .. ", prefix:" .. type(prefix))
|
||||||
|
end
|
||||||
|
if #s < #prefix then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local rc = C.memcmp(s, prefix, #prefix)
|
||||||
|
return rc == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function _M.has_suffix(s, suffix)
|
||||||
|
if type(s) ~= "string" or type(suffix) ~= "string" then
|
||||||
|
error("unexpected type: s:" .. type(s) .. ", suffix:" .. type(suffix))
|
||||||
|
end
|
||||||
|
if #s < #suffix then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local rc = C.memcmp(ffi_cast("char *", s) + #s - #suffix, suffix, #suffix)
|
||||||
|
return rc == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return _M
|
||||||
162
plugins/table.lua
Normal file
162
plugins/table.lua
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
--
|
||||||
|
-- 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.
|
||||||
|
--
|
||||||
|
local newproxy = newproxy
|
||||||
|
local getmetatable = getmetatable
|
||||||
|
local setmetatable = setmetatable
|
||||||
|
local select = select
|
||||||
|
local new_tab = require("table.new")
|
||||||
|
local pairs = pairs
|
||||||
|
local type = type
|
||||||
|
local string = string
|
||||||
|
|
||||||
|
|
||||||
|
local _M = {
|
||||||
|
version = 0.2,
|
||||||
|
new = new_tab,
|
||||||
|
clear = require("table.clear"),
|
||||||
|
insert = table.insert,
|
||||||
|
concat = table.concat,
|
||||||
|
sort = table.sort,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setmetatable(_M, {__index = table})
|
||||||
|
|
||||||
|
|
||||||
|
local nkeys
|
||||||
|
do
|
||||||
|
local ok, table_nkeys = pcall(require, 'table.nkeys')
|
||||||
|
if ok then
|
||||||
|
nkeys = table_nkeys
|
||||||
|
else
|
||||||
|
nkeys = function(t)
|
||||||
|
local count = 0
|
||||||
|
for _, _ in pairs(t) do
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
_M.nkeys = nkeys
|
||||||
|
|
||||||
|
|
||||||
|
function _M.insert_tail(tab, ...)
|
||||||
|
local idx = #tab
|
||||||
|
for i = 1, select('#', ...) do
|
||||||
|
idx = idx + 1
|
||||||
|
tab[idx] = select(i, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
return idx
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function _M.set(tab, ...)
|
||||||
|
for i = 1, select('#', ...) do
|
||||||
|
tab[i] = select(i, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- only work under lua51 or luajit
|
||||||
|
function _M.setmt__gc(t, mt)
|
||||||
|
local prox = newproxy(true)
|
||||||
|
getmetatable(prox).__gc = function() mt.__gc(t) end
|
||||||
|
t[prox] = true
|
||||||
|
return setmetatable(t, mt)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function deepcopy(orig)
|
||||||
|
local orig_type = type(orig)
|
||||||
|
if orig_type ~= 'table' then
|
||||||
|
return orig
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If the array-like table contains nil in the middle,
|
||||||
|
-- the len might be smaller than the expected.
|
||||||
|
-- But it doesn't affect the correctness.
|
||||||
|
local len = #orig
|
||||||
|
local copy = new_tab(len, nkeys(orig) - len)
|
||||||
|
for orig_key, orig_value in pairs(orig) do
|
||||||
|
copy[orig_key] = deepcopy(orig_value)
|
||||||
|
end
|
||||||
|
|
||||||
|
return copy
|
||||||
|
end
|
||||||
|
_M.deepcopy = deepcopy
|
||||||
|
|
||||||
|
local ngx_null = nil
|
||||||
|
local function merge(origin, extend)
|
||||||
|
for k,v in pairs(extend) do
|
||||||
|
if type(v) == "table" then
|
||||||
|
if type(origin[k] or false) == "table" then
|
||||||
|
if _M.nkeys(origin[k]) ~= #origin[k] then
|
||||||
|
merge(origin[k] or {}, extend[k] or {})
|
||||||
|
else
|
||||||
|
origin[k] = v
|
||||||
|
end
|
||||||
|
else
|
||||||
|
origin[k] = v
|
||||||
|
end
|
||||||
|
elseif v == ngx_null then
|
||||||
|
origin[k] = nil
|
||||||
|
else
|
||||||
|
origin[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return origin
|
||||||
|
end
|
||||||
|
_M.merge = merge
|
||||||
|
|
||||||
|
|
||||||
|
local function patch(node_value, sub_path, conf)
|
||||||
|
local sub_value = node_value
|
||||||
|
local sub_paths = string.split(sub_path, "/")
|
||||||
|
for i = 1, #sub_paths - 1 do
|
||||||
|
local sub_name = sub_paths[i]
|
||||||
|
if sub_value[sub_name] == nil then
|
||||||
|
sub_value[sub_name] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
sub_value = sub_value[sub_name]
|
||||||
|
|
||||||
|
if type(sub_value) ~= "table" then
|
||||||
|
return 400, "invalid sub-path: /"
|
||||||
|
.. _M.concat(sub_paths, 1, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(sub_value) ~= "table" then
|
||||||
|
return 400, "invalid sub-path: /" .. sub_path
|
||||||
|
end
|
||||||
|
|
||||||
|
local sub_name = sub_paths[#sub_paths]
|
||||||
|
if sub_name and sub_name ~= "" then
|
||||||
|
sub_value[sub_name] = conf
|
||||||
|
else
|
||||||
|
node_value = conf
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil, nil, node_value
|
||||||
|
end
|
||||||
|
_M.patch = patch
|
||||||
|
|
||||||
|
|
||||||
|
return _M
|
||||||
Reference in New Issue
Block a user