Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support multipart/form #32

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
541017d
support set multipart/form file
zhucebuliaopx Mar 22, 2020
0ee96a5
fix add content-length
zhucebuliaopx Mar 22, 2020
513cb46
update README
zhucebuliaopx Mar 22, 2020
1588a79
Merge tag '0.0.1' into develop
zhucebuliaopx Mar 22, 2020
3e7fe23
Merge branch 'release/0.0.1'
zhucebuliaopx Mar 22, 2020
8633569
local var
zhucebuliaopx Mar 22, 2020
51bd31a
fix code style
zhucebuliaopx Mar 23, 2020
fedcf9e
Merge tag '0.0.2' into develop
zhucebuliaopx Mar 23, 2020
a84db7e
Merge branch 'release/0.0.2'
zhucebuliaopx Mar 23, 2020
8a23a08
remove git address,once upstream merage my pr,so could install lua-mu…
zhucebuliaopx Mar 23, 2020
b38e3fd
Merge tag '0.0.3' into develop
zhucebuliaopx Mar 23, 2020
4a19a63
Merge branch 'release/0.0.3'
zhucebuliaopx Mar 23, 2020
3e46a90
add test example and fix code style
zhucebuliaopx Mar 24, 2020
b57d2ff
Merge branch 'release/0.0.4'
zhucebuliaopx Mar 24, 2020
51bf1e4
Merge tag '0.0.4' into develop
zhucebuliaopx Mar 24, 2020
117b669
del unnecessary local var
zhucebuliaopx Mar 24, 2020
3a47469
fix code style
zhucebuliaopx Apr 8, 2020
4c445cf
update multipart code
zhucebuliaopx May 1, 2020
158f7a3
fix code style
zhucebuliaopx May 1, 2020
0a8ebf4
fix code style && update test case
zhucebuliaopx May 2, 2020
e104658
Merge branch 'feature/0.0.2' into develop
zhucebuliaopx May 2, 2020
1cc478d
remove dependencies by Kong\lua-multipart && update readme
zhucebuliaopx May 2, 2020
aae1b4f
fix files body with userdata fp
zhucebuliaopx May 2, 2020
868162e
fix iter_base_func is_array
zhucebuliaopx May 2, 2020
2a9c604
local var error
zhucebuliaopx May 6, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ luac.out
*.x86_64
*.hex

#develop env
.idea
.vscode

t/servroot/*
46 changes: 25 additions & 21 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,28 @@ resty -e 'print(require "resty.requests".get{ url = "https://github.com", stream
Table of Contents
=================

* [Name](#name)
* [Status](#status)
* [Features](#features)
* [Synopsis](#synopsis)
* [Installation](#installation)
* [Methods](#methods)
* [request](#request)
* [state](#state)
* [get](#get)
* [head](#head)
* [post](#post)
* [put](#put)
* [delete](#delete)
* [options](#options)
* [patch](#patch)
* [Response Object](#response-object)
* [Session](#session)
* [TODO](#todo)
* [Author](#author)
* [Copyright and License](#copyright-and-license)
* [See Also](#see-also)
- [Name](#name)
- [Table of Contents](#table-of-contents)
- [Status](#status)
- [Features](#features)
- [Synopsis](#synopsis)
- [Installation](#installation)
- [Methods](#methods)
- [request](#request)
- [state](#state)
- [get](#get)
- [head](#head)
- [post](#post)
- [put](#put)
- [delete](#delete)
- [options](#options)
- [patch](#patch)
- [Response Object](#response-object)
- [Session](#session)
- [TODO](#todo)
- [Author](#author)
- [Copyright and License](#copyright-and-license)
- [See Also](#see-also)

Status
======
Expand Down Expand Up @@ -166,6 +167,8 @@ The third param, an optional Lua table, which contains a number of options:
* a Lua string, or
* a Lua function, without parameter and returns a piece of data (string) or an empty Lua string to represent EOF, or
* a Lua table, each key-value pair will be concatenated with the "&", and Content-Type header will `"application/x-www-form-urlencoded"`
* `files`, multipart/form upload file body, should be table contains more multi-tables, like that: {{"name", {"file_name",fp, "file_type"}},...}
* `fp` is binary file body or file func with method `read`

* `error_filter`, holds a Lua function which takes two parameters, `state` and `err`.
the parameter `err` describes the error and `state` is always one of these values(represents the current stage):
Expand Down Expand Up @@ -417,3 +420,4 @@ See Also

* upyun-resty: https://github.com/upyun/upyun-resty
* httpipe: https://github.com/timebug/lua-resty-httpipe
* python-requests: https://github.com/psf/requests
103 changes: 103 additions & 0 deletions lib/resty/requests/fields.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
local util = require "resty.requests.util"

local pairs = pairs
local concat = table.concat
local setmetatable = setmetatable
local strformat = string.format

local _M = { _VERSION = "0.0.1" }
local mt = { __index = _M , _ID = "FIELDS" }

local function format_header_param_html5(name, value)
-- todo _replace_multiple
return strformat('%s="%s"', name, value)
end


local function new(name, data, filename, headers, header_formatter)
local self = {
_name = name,
_filename = filename,
data = data,
headers = headers or {},
header_formatter = header_formatter or format_header_param_html5
}

return setmetatable(self, mt)
end


local function from_table(fieldname, value, header_formatter)
local filename, data, content_type
if util.is_tab(value) and util.is_array(value) then
filename, data, content_type = value[1], value[2], value[3] or "application/octet-stream"

else
data = value
end

local request_param = new(fieldname, data, filename, header_formatter)
request_param:make_multipart({content_type=content_type})
return request_param
end


local function _render_parts(self, headers_parts)
if util.is_func(headers_parts) and not util.is_array(headers_parts) then
headers_parts = util.to_key_value_list(headers_parts)
end

local parts = {}
local parts_index = 1
for i=1, util.len(headers_parts) do
local name = headers_parts[i][1]
local value = headers_parts[i][2]
if value then
parts[parts_index] = self.header_formatter(name, value)
end
end

return concat(parts, "; ")
end


local function make_multipart(self, opts)
self.headers["Content-Disposition"] = opts.content_disposition or "form-data"
self.headers["Content-Disposition"] = concat({self.headers["Content-Disposition"], self:_render_parts({{"name", self._name}, {"filename", self._filename}})}, "; ")
self.headers["Content-Type"] = opts.content_type
self.headers["Content-Location"] = opts.content_location
end


local function render_headers(self)
local lines = {}
local lines_index = 1
local sort_keys = {"Content-Disposition", "Content-Type", "Content-Location"}

for i=1, 3 do
local tmp_value = self.headers[sort_keys[i]]
if tmp_value then
lines[lines_index] = strformat("%s: %s", sort_keys[i], tmp_value)
lines_index = lines_index + 1
end
end

for k, v in pairs(self.headers) do
if not util.is_inarray(k, sort_keys) and v then
lines[lines_index] = strformat("%s: %s", k, v)
lines_index = lines_index + 1
end
end

lines[lines_index] = "\r\n"
return concat(lines, "\r\n")
end


_M.new = new
_M.from_table = from_table
_M.make_multipart = make_multipart
_M.render_headers = render_headers
_M._render_parts = _render_parts

return _M
52 changes: 52 additions & 0 deletions lib/resty/requests/filepost.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
local util = require "resty.requests.util"
local request_fields = require "resty.requests.fields"

local tostring = tostring
local str_sub = string.sub
local concat = table.concat

local _M = { _VERSION = "0.0.1" }

local function choose_boundary()
return str_sub(tostring({}), 10)
end


local function iter_base_func(fields, i)
i = i + 1
local field = fields[i]
if field == nil then
return
end

local is_array = util.is_array(field)
if is_array == true or (is_array == "table" and not field._ID) then
field = request_fields.from_table(field[1], field[2])
end

return i, field
end


local function iter_field_objects(fields)
return iter_base_func, fields, 0
end


local function encode_multipart_formdata(fields, boundary)
boundary = boundary or choose_boundary()
local body = ""
for i, field in iter_field_objects(fields) do
body = concat({body, "--", boundary, "\r\n", field:render_headers(), field.data, "\r\n"}, "")
end

body = body .. "--" .. boundary .. "--\r\n"
local content_type = "multipart/form-data; boundary=" .. boundary
return body, content_type
end


_M.encode_multipart_formdata = encode_multipart_formdata
_M.choose_boundary = choose_boundary

return _M
82 changes: 82 additions & 0 deletions lib/resty/requests/models.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
local util = require "resty.requests.util"
local filepost = require "resty.requests.filepost"
local request_fields = require "resty.requests.fields"

local error = error

local _M = { _VERSION = "0.0.1" }


local function encode_files(files, data)
if not files then
error("Files must be provided.")
end

local new_fields = {}
local new_fields_index = 1
local fields = util.to_key_value_list(data or {})
files = util.to_key_value_list(files)
for i=1, util.len(fields) do
local field = fields[i][1]
local val = fields[i][2]
if util.is_str(val) then
val = {val}
end

for i=1, util.len(val) do
if not val[i] then
goto CONTINUE
end
new_fields[new_fields_index] = {field, val[i]}
new_fields_index = new_fields_index + 1
::CONTINUE::
end
end

for i=1, util.len(files) do
local fn, ft, fh, fp, fdata
local k = files[i][1]
local v = files[i][2]
if util.is_array(v) then
local length = util.len(v)
if length == 2 then
fn, fp = v[1], v[2]

elseif length == 3 then
fn, fp, ft = v[1], v[2], v[3]

else
fn, fp, ft, fh = v[1], v[2], v[3], v[4]
end

else
fn = k
fp = v
end

if fp == nil then
goto CONTINUE

elseif util.is_userdata(fp) and util.is_func(fp.read) then
fdata = fp:read("*all")
fp:close()

else
fdata = fp
end

local rf = request_fields.new(k, fdata, fn, fh)
rf:make_multipart({content_type=ft})
new_fields[new_fields_index] = rf
new_fields_index = new_fields_index + 1
::CONTINUE::
end

local body, content_type = filepost.encode_multipart_formdata(new_fields)
return body, content_type
end


_M.encode_files = encode_files

return _M
10 changes: 10 additions & 0 deletions lib/resty/requests/request.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

local cjson = require "cjson.safe"
local util = require "resty.requests.util"
local models = require "resty.requests.models"

local setmetatable = setmetatable
local pairs = pairs
Expand Down Expand Up @@ -70,10 +71,19 @@ local function prepare(url_parts, session, config)
local json = config.json
local body = config.body

local files = config.files

if json then
content = cjson.encode(json)
headers["content-length"] = #content
headers["content-type"] = "application/json"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to leave an empty line here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok


elseif files and util.is_array(files) then
local multipart_body, content_type = models.encode_files(files, body)
headers["content-type"] = content_type
headers["content-length"] = #multipart_body
content = multipart_body
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto, leave an empty line here.


else
content = body
if is_func(body) then
Expand Down
Loading