-
Notifications
You must be signed in to change notification settings - Fork 0
/
handler.lua
180 lines (156 loc) · 5.6 KB
/
handler.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
local tablex = require "pl.tablex"
local basic_serializer = require "kong.plugins.log-serializers.basic"
local BasePlugin = require "kong.plugins.base_plugin"
local cjson = require "cjson"
local url = require "socket.url"
local string_format = string.format
local cjson_encode = cjson.encode
local HttpLogHandler = BasePlugin:extend()
HttpLogHandler.PRIORITY = 12
HttpLogHandler.VERSION = "0.1.0"
local HTTP = "http"
local HTTPS = "https"
local EMPTY = tablex.readonly({})
-- Generates the raw http message.
-- @param `method` http method to be used to send data
-- @param `content_type` the type to set in the header
-- @param `parsed_url` contains the host details
-- @param `body` Body of the message as a string (must be encoded according to the `content_type` parameter)
-- @return raw http message
local function generate_post_payload(method, content_type, parsed_url, body)
local url
if parsed_url.query then
url = parsed_url.path .. "?" .. parsed_url.query
else
url = parsed_url.path
end
local headers = string_format(
"%s %s HTTP/1.1\r\nHost: %s\r\nConnection: Keep-Alive\r\nContent-Type: %s\r\nContent-Length: %s\r\n",
method:upper(), url, parsed_url.host, content_type, #body)
if parsed_url.userinfo then
local auth_header = string_format(
"Authorization: Basic %s\r\n",
ngx.encode_base64(parsed_url.userinfo)
)
headers = headers .. auth_header
end
return string_format("%s\r\n%s", headers, body)
end
-- Parse host url.
-- @param `url` host url
-- @return `parsed_url` a table with host details like domain name, port, path etc
local function parse_url(host_url)
local parsed_url = url.parse(host_url)
if not parsed_url.port then
if parsed_url.scheme == HTTP then
parsed_url.port = 80
elseif parsed_url.scheme == HTTPS then
parsed_url.port = 443
end
end
if not parsed_url.path then
parsed_url.path = "/"
end
return parsed_url
end
-- Log to a Http end point.
-- This basically is structured as a timer callback.
-- @param `premature` see openresty ngx.timer.at function
-- @param `conf` plugin configuration table, holds http endpoint details
-- @param `body` raw http body to be logged
-- @param `name` the plugin name (used for logging purposes in case of errors etc.)
local function log(premature, conf, body, name)
if premature then
return
end
name = "[" .. name .. "] "
local ok, err
local parsed_url = parse_url(conf.http_endpoint)
local host = parsed_url.host
local port = tonumber(parsed_url.port)
local sock = ngx.socket.tcp()
sock:settimeout(conf.timeout)
ok, err = sock:connect(host, port)
if not ok then
ngx.log(ngx.ERR, name .. "failed to connect to " .. host .. ":" .. tostring(port) .. ": ", err)
return
end
if parsed_url.scheme == HTTPS then
local _, err = sock:sslhandshake(true, host, false)
if err then
ngx.log(ngx.ERR, name .. "failed to do SSL handshake with " .. host .. ":" .. tostring(port) .. ": ", err)
end
end
ok, err = sock:send(generate_post_payload(conf.method, conf.content_type, parsed_url, body))
if not ok then
ngx.log(ngx.ERR, name .. "failed to send data to " .. host .. ":" .. tostring(port) .. ": ", err)
end
ok, err = sock:setkeepalive(conf.keepalive)
if not ok then
ngx.log(ngx.ERR, name .. "failed to keepalive to " .. host .. ":" .. tostring(port) .. ": ", err)
return
end
end
-- Only provide `name` when deriving from this class. Not when initializing an instance.
function HttpLogHandler:new(name)
HttpLogHandler.super.new(self, name or "custom-http-log")
end
-- serializes context data into an html message body.
-- @param `ngx` The context table for the request being logged
-- @param `conf` plugin configuration table, holds http endpoint details
-- @return html body as string
function HttpLogHandler:serialize(ngx, conf)
return cjson_encode(basic_serializer.serialize(ngx))
end
local function _gen_data(ngx, conf)
local authenticated_entity
if ngx.ctx.authenticated_credential ~= nil then
authenticated_entity = {
id = ngx.ctx.authenticated_credential.id,
consumer_id = ngx.ctx.authenticated_credential.consumer_id
}
end
local log_data = {}
for _, v in pairs(conf.log_data or EMPTY) do
log_data[v] = ngx.var[v]
end
local serialize = {
request = {
uri = ngx.var.request_uri,
request_uri = ngx.var.scheme .. "://" .. ngx.var.host .. ":" .. ngx.var.server_port .. ngx.var.request_uri,
querystring = ngx.req.get_uri_args(), -- parameters, as a table
method = ngx.req.get_method(), -- http method
headers = ngx.req.get_headers(),
size = ngx.var.request_length
},
response = {
status = ngx.status,
headers = ngx.resp.get_headers(),
size = ngx.var.bytes_sent
},
tries = (ngx.ctx.balancer_address or EMPTY).tries,
latencies = {
kong = (ngx.ctx.KONG_ACCESS_TIME or 0) +
(ngx.ctx.KONG_RECEIVE_TIME or 0) +
(ngx.ctx.KONG_REWRITE_TIME or 0) +
(ngx.ctx.KONG_BALANCER_TIME or 0),
proxy = ngx.ctx.KONG_WAITING_TIME or -1,
request = ngx.var.request_time * 1000
},
authenticated_entity = authenticated_entity,
api = ngx.ctx.api,
consumer = ngx.ctx.authenticated_consumer,
client_ip = ngx.var.remote_addr,
started_at = ngx.req.start_time() * 1000,
log_data = log_data
}
return cjson_encode(serialize)
end
function HttpLogHandler:log(conf)
HttpLogHandler.super.log(self)
local ok, err = ngx.timer.at(0, log, conf, _gen_data(ngx, conf), self._name)
if not ok then
ngx.log(ngx.ERR, "[" .. self._name .. "] failed to create timer: ", err)
end
end
return HttpLogHandler