Skip to content

Commit

Permalink
ref: parse multiple requests when redirected (fix #235)
Browse files Browse the repository at this point in the history
  • Loading branch information
boltlessengineer committed Sep 4, 2024
1 parent 3030841 commit 057fea4
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 67 deletions.
140 changes: 109 additions & 31 deletions lua/rest-nvim/client/curl/cli.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,65 @@ end
---@private
local parser = {}

---@class rest.Result
---@field requests rest.Response_[]
---@field statistics table<string,string> Response statistics

---@class rest.Response_
---@field request rest.RequestCore
---@field response rest.Response

---@class rest.RequestCore
---@field method string
---@field url string
---@field http_version string
---@field headers table<string,string[]>

---@package
---@param str string
---@return rest.Response.status
function parser.parse_verbose_status(str)
local version, code, text = str:match("^(%S+) (%d+) ?(.*)")
---@return rest.RequestCore?
function parser.parse_req_info(str)
local method, url, version = str:match("^([A-Z]+) (.+) (HTTP/[%d%.]+)")
if not method then
return
end
return {
method = method,
url = url,
http_version = version,
headers = {},
}
end

function parser.parse_req_header(str, requests)
local info = parser.parse_req_info(str)
if info then
table.insert(requests, {
request = info,
response = {},
})
return
end
local req = requests[#requests].request
local key, value = parser.parse_header_pair(str)
if key then
if not req.headers[key] then
req.headers[key] = {}
end
table.insert(req.headers[key], value)
else
log.error("Error while parsing verbose curl output header:", str)
end
end

---@package
---@param str string
---@return rest.Response.status?
function parser.parse_res_status(str)
local version, code, text = str:match("^(HTTP/[%d%.]+) (%d+) ?(.*)")
if not version then
return
end
return {
version = version,
code = tonumber(code),
Expand All @@ -73,22 +127,49 @@ function parser.parse_header_pair(str)
end

---@package
---@param str string
function parser.parse_res_header(str, requests)
local status = parser.parse_res_status(str)
if status then
-- reset response object
requests[#requests].response = {
status = status,
headers = {},
}
return
end
local res = requests[#requests].response
local key, value = parser.parse_header_pair(str)
if key then
if not res.headers[key] then
res.headers[key] = {}
end
table.insert(res.headers[key], value)
else
log.error("Error while parsing verbose curl output header:", str)
end
end

---@package
---@param idx number
---@param line string
---@return {prefix:string,str:string?}|nil
function parser.parse_verbose_line(line)
---@return {idx:number,prefix:string,str:string?}|nil
function parser.lex_verbose_line(idx, line)
local prefix, str = line:match("(.) ?(.*)")
log.debug("line", idx, line)
if not prefix then
log.error("Error while parsing verbose curl output:\n" .. line)
log.error(("Error while parsing verbose curl output at line %d:"):format(idx), line)
return
end
return {
idx = idx,
prefix = prefix,
str = str,
}
end

local _VERBOSE_PREFIX_META = "*"
local _VERBOSE_PREFIX_REQ_HEADER = ">"
local VERBOSE_PREFIX_REQ_HEADER = ">"
local _VERBOSE_PREFIX_REQ_BODY = "}"
local VERBOSE_PREFIX_RES_HEADER = "<"
-- NOTE: we don't parse response body with trace output. response body will
Expand All @@ -114,35 +195,32 @@ function parser.parse_stat_pair(str)
end

---@param lines string[]
---@return rest.Response
---@return rest.Result
function parser.parse_verbose(lines)
local response = {
headers = {},
---@type rest.Result
local result = {
---@type rest.Response_[]
requests = {},
---@type table<string,string> Response statistics
statistics = {},
}
vim.iter(lines):map(parser.parse_verbose_line):each(function(ln)
if ln.prefix == VERBOSE_PREFIX_RES_HEADER then
if not response.status then
-- response status
response.status = parser.parse_verbose_status(ln.str)
else
-- response header
local key, value = parser.parse_header_pair(ln.str)
if key then
if not response.headers[key] then
response.headers[key] = {}
end
table.insert(response.headers[key], value)
end
end
-- ignore last newline
if lines[#lines] == "" then
lines[#lines] = nil
end
vim.iter(lines):enumerate():map(parser.lex_verbose_line):each(function(ln)
if ln.prefix == VERBOSE_PREFIX_REQ_HEADER then
parser.parse_req_header(ln.str, result.requests)
elseif ln.prefix == VERBOSE_PREFIX_RES_HEADER then
parser.parse_res_header(ln.str, result.requests)
elseif ln.prefix == VERBOSE_PREFIX_STAT then
local key, value = parser.parse_stat_pair(ln.str)
if key then
response.statistics[key] = value
result.statistics[key] = value
end
end
end)
return response
return result
end

--- Builder ---
Expand Down Expand Up @@ -332,7 +410,7 @@ end

---Send request via `curl` cli
---@param request rest.Request Request data to be passed to cURL
---@return nio.control.Future future Future containing rest.Response
---@return nio.control.Future future Future containing rest.Result
function curl.request(request)
local progress_handle = progress.handle.create({
title = "Executing",
Expand All @@ -354,9 +432,9 @@ function curl.request(request)
progress_handle:report({
message = "Parsing response...",
})
local response = parser.parse_verbose(vim.split(sc.stderr, "\n"))
response.body = sc.stdout
future.set(response)
local result = parser.parse_verbose(vim.split(sc.stderr, "\n"))
result.requests[#result.requests].response.body = sc.stdout
future.set(result)
progress_handle:report({
message = "Success",
})
Expand Down
26 changes: 22 additions & 4 deletions lua/rest-nvim/client/curl/libcurl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ end
---Execute an HTTP request using cURL
---return return nil if execution failed
---@param req rest.Request Request data to be passed to cURL
---@return rest.Response? info The request information (url, method, headers, body, etc)
---@return rest.Result?
function client.request(req)
logger.info("sending request to: " .. req.url)
if not found_curl then
Expand Down Expand Up @@ -162,8 +162,13 @@ function client.request(req)
logger.error("Something went wrong when making the request with cURL:\n" .. curl_utils.curl_error(err:no()))
return
end
local status_str = table.remove(res_raw_headers, 1)
---@diagnostic disable-next-line: invisible
local status = curl_cli.parser.parse_verbose_status(table.remove(res_raw_headers, 1))
local status = curl_cli.parser.parse_res_status(status_str)
if not status then
logger.error("can't parse response status:", status_str)
return
end
local res_headers = {}
for _, header in ipairs(res_raw_headers) do
---@diagnostic disable-next-line: invisible
Expand All @@ -180,12 +185,25 @@ function client.request(req)
status = status,
headers = res_headers,
body = table.concat(res_result),
statistics = get_stats(req_, {}),
}
logger.debug(vim.inspect(res.headers))
res.status.text = vim.trim(res.status.text)
req_:close()
return res
---@type rest.Result
return {
requests = {
{
request = {
method = req.method,
url = req.url,
http_version = req.http_version or "HTTP/1.1",
headers = req.headers,
},
response = res,
},
},
statistics = get_stats(req_, {}),
}
end

return client
11 changes: 6 additions & 5 deletions lua/rest-nvim/request.lua
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ local Context = require("rest-nvim.context").Context
---@field status rest.Response.status Status information from response
---@field body string? Raw response body
---@field headers table<string,string[]> Response headers
---@field statistics table<string,string> Response statistics

---@class rest.Response.status
---@field code number
Expand Down Expand Up @@ -69,28 +68,30 @@ local function run_request(req)
vim.notify("request failed. See `:Rest logs` for more info", vim.log.levels.ERROR, { title = "rest.nvim" })
return
end
---@cast res rest.Response
---@cast res rest.Result
logger.info("request success")

local last_response = res.requests[#res.requests].response

-- run request handler scripts
vim.iter(req.handlers):each(function(f)
f(res)
end)
logger.info("handler done")

_G.rest_request = req
_G.rest_response = res
_G.rest_response = last_response
vim.api.nvim_exec_autocmds("User", {
pattern = { "RestResponse", "RestResponsePre" },
})
_G.rest_request = nil
_G.rest_response = nil

-- update cookie jar
jar.update_jar(req.url, res)
jar.update_jar(req.url, last_response)

-- update result UI
ui.update({ response = res })
ui.update({ response = last_response, statistics = res.statistics })
end)
-- FIXME(boltless): return future to pass the command state
end
Expand Down
25 changes: 9 additions & 16 deletions lua/rest-nvim/ui/result.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ local data = {
request = nil,
---@type rest.Response?
response = nil,
---@type table<string,string>?
statistics = nil,
}

---@param req rest.Request
Expand Down Expand Up @@ -76,8 +78,9 @@ local panes = {
)
)
local content_type = data.response.headers["content-type"]
table.insert(lines, "")
table.insert(lines, "# @_RES")
local body = vim.split(data.response.body, "\n")
local body_meta = {}
if content_type then
local base_type, res_type = content_type[1]:match("(.*)/([^;]+)")
if base_type == "image" then
Expand All @@ -86,19 +89,9 @@ local panes = {
body = { "Binary answer" }
elseif config.response.hooks.format then
-- NOTE: format hook runs here because it should be done last.
local ok
body, ok = utils.gq_lines(body, res_type)
if ok then
table.insert(body_meta, "formatted")
end
body = utils.gq_lines(body, res_type)
end
end
local meta_str = ""
if #body_meta > 0 then
meta_str = " (" .. table.concat(body_meta, ",") .. ")"
end
table.insert(lines, "")
table.insert(lines, "# @_RES" .. meta_str)
vim.list_extend(lines, body)
table.insert(lines, "# @_END")
else
Expand Down Expand Up @@ -166,14 +159,14 @@ local panes = {
return
end
local lines = {}
if not data.response.statistics then
if not data.statistics then
set_lines(self.bufnr, { "No Statistics" })
return
end
syntax_highlight(self.bufnr, "http_stat")
for _, style in ipairs(config.clients.curl.statistics) do
local title = style.title or style.id
local value = data.response.statistics[style.id] or ""
local value = data.statistics[style.id] or ""
table.insert(lines, ("%s: %s"):format(title, value))
end
set_lines(self.bufnr, lines)
Expand All @@ -191,7 +184,7 @@ winbar = winbar .. "%#RestText#Press %#Keyword#?%#RestText# for help%#Normal# "
---@return string
function ui.stat_winbar()
local content = ""
if not data.response then
if not data.statistics then
return "Loading...%#Normal#"
end
for _, style in ipairs(config.clients.curl.statistics) do
Expand All @@ -200,7 +193,7 @@ function ui.stat_winbar()
if title ~= "" then
title = title .. ": "
end
local value = data.response.statistics[style.id] or ""
local value = data.statistics[style.id] or ""
content = content .. " %#RestText#" .. title .. "%#Normal#" .. value
end
end
Expand Down
Loading

0 comments on commit 057fea4

Please sign in to comment.