-
Notifications
You must be signed in to change notification settings - Fork 25
/
Copy pathbuild.lua
246 lines (199 loc) · 6.98 KB
/
build.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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
local json_encode = require("cjson.safe").new().encode
local protocols = {
ec2 = true,
json = true,
query = true,
["rest-xml"] = true,
["rest-json"] = true,
}
local function poor_mans_xml_encoding(output, shape, shape_name, data, indent)
local indent = indent or 0
local prefix = string.rep(" ", indent)
local element = shape.locationName or shape_name
local xmlns = ""
if (shape.xmlNamespace or {}).uri then
xmlns = (' xmlns="%s"'):format(shape.xmlNamespace.uri)
end
if shape.type == "structure" or
shape.type == "list" or
shape.type == "map" then
-- nested structures
output[#output+1] = prefix .. '<' .. element .. xmlns .. ">"
if shape.type == "structure" then
for name, member in pairs(shape.members) do
if data[name] then
poor_mans_xml_encoding(output, member, name, data[name], indent + 1)
end
end
elseif shape.type == "list" then
for i, member_data in ipairs(data or {}) do
poor_mans_xml_encoding(output, shape.member, "unknown", member_data, indent + 1)
end
else -- shape.type == "map"
error("protocol 'rest-xml' hasn't implemented the 'map' type")
end
output[#output+1] = prefix .. '</' .. element .. ">"
else
-- just a scalar value
output[#output+1] = prefix .. '<' .. element .. xmlns .. ">" ..
tostring(data) .. '</' .. element .. ">"
end
end
local ESCAPE_PATTERN = "[^!#$&'()*+,/:;=?@[\\]A-Z\\d-_.~%]"
local function percent_escape(m)
return string.format("%%%02X", string.byte(m[0]))
end
local function escape_uri(uri)
if ngx.re.find(uri, ESCAPE_PATTERN, "joi") then
return (ngx.re.gsub(uri, ESCAPE_PATTERN, percent_escape, "joi"))
end
return uri
end
local function parse_query(q)
local query_tbl = {}
for k, v in q:gmatch('([^&=?]+)=?([^&=?]*)') do
query_tbl[k] = v
end
return query_tbl
end
local function get_host_port(config)
local host = config.endpoint or config.globalEndpoint
do
local s, e = host:find("://")
if s then
-- the "globalSSL" one from the region_config_data file
local scheme = host:sub(1, s-1):lower()
host = host:sub(e+1, -1)
if config.tls == nil then
config.tls = scheme == "https"
end
end
end
local tls = config.tls
local port = config.port or (tls and 443 or 80)
local host_header do -- If the "standard" port is not in use, the port should be added to the Host header
local with_port
if tls then
with_port = port ~= 443
else
with_port = port ~= 80
end
if with_port then
host_header = string.format("%s:%d", host, port)
else
host_header = host
end
end
return host, port, host_header
end
-- implement AWS api protocols.
-- returns a request table;
-- * method; the HTTP method to use
-- * path; possibly containing rendered values
-- * query; hash-table with query arguments
-- * body; string with formatted body
-- * headers; hash table with headers
--
-- Input parameters:
-- * operation table
-- * config: config table for instantiated service
-- * params: parameters for the call
local function build_request(operation, config, params)
-- print(require("pl.pretty").write(config))
-- print(require("pl.pretty").write(operation))
if not protocols[config.protocol or ""] then
error("Bad config, field protocol is invalid, got: '" .. tostring(config.protocol) .. "'")
end
local http = operation.http or {}
local uri = http.requestUri or ""
local host, port, host_header = get_host_port(config)
local request = {
path = uri,
method = http.method,
query = {},
headers = { ["Host"] = host_header, },
host = host,
port = port,
}
local body_tbl = {}
if config.signingName or config.targetPrefix then
request.headers["X-Amz-Target"] = (config.signingName or config.targetPrefix) .. "." .. operation.name
end
if config.protocol == "query" then
request.query["Action"] = operation.name
request.query["Version"] = config.apiVersion
end
-- inject parameters in the right places; path/query/header/body
-- this assumes they all live on the top-level of the structure, is this correct??
for name, member_config in pairs(operation.input.members) do
local param_value = params[name]
-- TODO: date-time value should be properly formatted???
if param_value ~= nil then
-- a parameter value is provided
local location = member_config.location
local locationName = member_config.locationName
-- print(name," = ", param_value, ": ",location, " (", locationName,")")
if location == "uri" then
local place_holder = "{" .. locationName .. "%+?}"
local replacement = escape_uri(param_value):gsub("%%", "%%%%")
request.path = request.path:gsub(place_holder, replacement)
elseif location == "querystring" then
request.query[locationName] = param_value
elseif location == "header" then
request.headers[locationName] = param_value
elseif location == "headers" then
for k,v in pairs(param_value) do
request.headers[locationName .. k] = v
end
elseif location == nil then
if config.protocol == "query" then
-- no location specified, but protocol is query, so it goes into query
request.query[name] = param_value
elseif member_config.type == "blob" then
request.body = param_value
else
-- nowhere else to go, so put it in the body (for json and xml)
body_tbl[name] = param_value
end
else
error("Unknown location: " .. location)
end
end
end
local path, query = request.path:match("([^?]+)%??(.*)")
request.path = path
for k,v in pairs(parse_query(query)) do
request.query[k] = v
end
local table_empty = not next(body_tbl)
-- already has a raw body, or no body at all
if request.body then
assert(table_empty, "raw body set while parameters need to be encoded in body")
request.headers["Content-Length"] = #request.body
return request
end
if table_empty then
return request
end
-- format the body
if config.protocol == "ec2" then
error("protocol 'ec2' not implemented yet")
elseif config.protocol == "rest-xml" then
local xml_data = {
'<?xml version="1.0" encoding="UTF-8"?>',
}
-- encode rest of the body data here
poor_mans_xml_encoding(xml_data, operation.input, "input", body_tbl)
-- TODO: untested, assuming "application/xml" as the default type here???
request.headers["Content-Type"] = request.headers["Content-Type"] or "application/xml"
request.body = table.concat(xml_data, "\n")
else
-- assuming remaining protocols "rest-json", "json", "query" to be safe to json encode
local version = config.jsonVersion or '1.0'
request.headers["Content-Type"] = request.headers["Content-Type"] or "application/x-amz-json-" .. version
request.body = json_encode(body_tbl)
end
request.headers["Content-Length"] = #request.body
return request
end
return build_request