socket.http.request(): simultaneous support for http and https URL schemes, with caller-adjustable scheme-to-transport mappings (default "socket.http" and "ssl.https")

This commit is contained in:
E. Westbrook 2018-07-12 01:10:11 -06:00
parent 38865fad3a
commit e587800164

View File

@ -26,15 +26,20 @@ _M.TIMEOUT = 60
-- user agent field sent in request -- user agent field sent in request
_M.USERAGENT = socket._VERSION _M.USERAGENT = socket._VERSION
-- supported schemes -- supported schemes and their particulars
local SCHEMES = { local SCHEMES = {
http = { port = 80 } http = {
, https = { port = 443 }} port = 80
, create = function(t)
return socket.tcp end }
, https = {
port = 443
, create = function(t)
return require("ssl.https").tcp(t) end }}
-- default scheme and port for document retrieval -- default scheme and port for document retrieval
local SCHEME = 'http' local SCHEME = 'http'
local PORT = SCHEMES[SCHEME].port local PORT = SCHEMES[SCHEME].port
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Reads MIME headers from a connection, unfolding where needed -- Reads MIME headers from a connection, unfolding where needed
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
@ -115,13 +120,13 @@ local metat = { __index = {} }
function _M.open(host, port, create) function _M.open(host, port, create)
-- create socket with user connect function, or with default -- create socket with user connect function, or with default
local c = socket.try((create or socket.tcp)()) local c = socket.try(create())
local h = base.setmetatable({ c = c }, metat) local h = base.setmetatable({ c = c }, metat)
-- create finalized try -- create finalized try
h.try = socket.newtry(function() h:close() end) h.try = socket.newtry(function() h:close() end)
-- set timeout before connecting -- set timeout before connecting
h.try(c:settimeout(_M.TIMEOUT)) h.try(c:settimeout(_M.TIMEOUT))
h.try(c:connect(host, port or PORT)) h.try(c:connect(host, port))
-- here everything worked -- here everything worked
return h return h
end end
@ -221,14 +226,13 @@ end
local function adjustheaders(reqt) local function adjustheaders(reqt)
-- default headers -- default headers
local headhost = reqt.host local host = reqt.host
local headport = tostring(reqt.port) local port = tostring(reqt.port)
local schemeport = tostring(SCHEMES[reqt.scheme].port) if port ~= tostring(SCHEMES[reqt.scheme].port) then
if headport ~= schemeport then host = host .. ':' .. port end
headhost = headhost .. ':' .. headport end
local lower = { local lower = {
["user-agent"] = _M.USERAGENT, ["user-agent"] = _M.USERAGENT,
["host"] = headhost, ["host"] = host,
["connection"] = "close, TE", ["connection"] = "close, TE",
["te"] = "trailers" ["te"] = "trailers"
} }
@ -267,8 +271,13 @@ local function adjustrequest(reqt)
local nreqt = reqt.url and url.parse(reqt.url, default) or {} local nreqt = reqt.url and url.parse(reqt.url, default) or {}
-- explicit components override url -- explicit components override url
for i,v in base.pairs(reqt) do nreqt[i] = v end for i,v in base.pairs(reqt) do nreqt[i] = v end
if nreqt.port == "" then nreqt.port = PORT end -- default to scheme particulars
if not (nreqt.host and nreqt.host ~= "") then local schemedefs, host, port, method
= SCHEMES[nreqt.scheme], nreqt.host, nreqt.port, nreqt.method
if not nreqt.create then nreqt.create = schemedefs.create(nreqt) end
if not (port and port ~= '') then nreqt.port = schemedefs.port end
if not (method and method ~= '') then nreqt.method = 'GET' end
if not (host and host ~= "") then
socket.try(nil, "invalid host '" .. base.tostring(nreqt.host) .. "'") socket.try(nil, "invalid host '" .. base.tostring(nreqt.host) .. "'")
end end
-- compute uri if user hasn't overriden -- compute uri if user hasn't overriden
@ -285,8 +294,10 @@ local function shouldredirect(reqt, code, headers)
if not location then return false end if not location then return false end
location = string.gsub(location, "%s", "") location = string.gsub(location, "%s", "")
if location == "" then return false end if location == "" then return false end
local scheme = string.match(location, "^([%w][%w%+%-%.]*)%:") local scheme = url.parse(location).scheme
if scheme and not SCHEMES[scheme] then return false end if scheme and (not SCHEMES[scheme]) then return false end
-- avoid https downgrades
if ('https' == reqt.scheme) and ('https' ~= scheme) then return false end
return (reqt.redirect ~= false) and return (reqt.redirect ~= false) and
(code == 301 or code == 302 or code == 303 or code == 307) and (code == 301 or code == 302 or code == 303 or code == 307) and
(not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD")
@ -306,10 +317,16 @@ end
local trequest, tredirect local trequest, tredirect
--[[local]] function tredirect(reqt, location) --[[local]] function tredirect(reqt, location)
-- the RFC says the redirect URL has to be absolute, but some
-- servers do not respect that
local newurl = url.absolute(reqt.url, location)
-- if switching schemes, reset port and create function
if url.parse(newurl).scheme ~= reqt.scheme then
reqt.port = nil
reqt.create = nil end
-- make new request
local result, code, headers, status = trequest { local result, code, headers, status = trequest {
-- the RFC says the redirect URL has to be absolute, but some url = newurl,
-- servers do not respect that
url = url.absolute(reqt.url, location),
source = reqt.source, source = reqt.source,
sink = reqt.sink, sink = reqt.sink,
headers = reqt.headers, headers = reqt.headers,
@ -397,4 +414,5 @@ _M.request = socket.protect(function(reqt, body)
else return trequest(reqt) end else return trequest(reqt) end
end) end)
_M.schemes = SCHEMES
return _M return _M