lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Hi Keith,

On 7/17/08, Keith Nicholas <keith.nicholas@gmail.com> wrote:
> Is there any code out there for talking to web services using Lua?
>
> There seems to be vauge smatterings of half baked stuff from ages ago.
>
> specifically I'm wanting to talk to a webservice created using WCF....
>

I don't know about WCF.

I have had dealings with a few SOAP services that I needed to talk to
and there appear to be quite a few incompatibilites between server
implementations. I ended up forgoing any general solution and now code
interface specific modules.

I'm attaching a simple wrapper using luasocket and luaexpat as an
example of how to access the Google Search SOAP API. It reasonably
"templated" so that making a new interface to something else is
workable.

This works for unauthenticated servers or ones using BASIC auth. My
brain is too small to comprehend how the more complicated auth methods
work.

Hope this may point you in the right direction.

Robby
-- Example use of Google Search SOAP

local ltn12 = require "ltn12"
local http = require "socket.http"
local lom = require "lxp.lom"


local URL = "http://api.google.com/search/beta2";
local NAMESPACE = "urn:GoogleSearch"
local REQUEST = [[
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/";
		soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/";>
	<soap:Body>
		<$FUN xmlns="]] .. NAMESPACE .. [[">$ARGS</$FUN>
	</soap:Body>
</soap:Envelope>
]]

--[[ Example of BASIC auth if needed:
local AUTH = "BASIC " .. trim(encode_base64(user .. ":" .. password))
]]


-- skip_soap() is used to extract a valid SOAP result from a LOM structure, or error.

local function get_first_sub_tree(t)
	local i = 1
	while type(t[i]) == "string" do
		i = i + 1
	end
	return t[i]
end
local function get_soap_faultstring(t)
	for _, part in ipairs(t) do
		if type(part) == "table" and part.tag == "faultstring" then
			return tconcat(part)
		end 
	end
end
local function skip_soap(t)
	-- look for Envelope
	if type(t) ~= "table" then
		return nil, "Bad SOAP Result: No XML content found."
	end
	if not t.tag:match("Envelope$") then
		return nil, "Bad SOAP Result: No SOAP Envelope found."
	end

	-- look for Body
	t = get_first_sub_tree(t)
	if type(t) ~= "table" then
		return nil, "Bad SOAP Result: No SOAP Envelope content found."
	end
	if not t.tag:match("Body$") then
		return nil, "Bad SOAP Result: No SOAP Body found."
	end

	-- look for possible Fault
	t = get_first_sub_tree(t)
	if t.tag:match("Fault$") then
		return nil, "Faulty SOAP Result: " .. (get_soap_faultstring(t) or "No faultstring content found.")
	elseif type(t) == "table" then
		return t
	else
		return nil, "Bad SOAP Result: No SOAP Body content found."
	end
end

-- lom_unparse() translates a LOM structure into an XML string.

local tinsert, tconcat = table.insert, table.concat
local replacements = { ["<"] = "&lt;", ["&"] = "&amp;", [">"] = "&gt;", ['"'] = "&quot;", ["'"] = "&apos;", }
local specials = "["
for c in pairs(replacements) do specials = specials .. "%" .. c end
specials = specials .. "]"
local function xml_specials(s)
	 -- only return the result string, not the number of substitutions
	return (s:gsub(specials, replacements))
end
local function lom_unparse_internal(lom, t)
	tinsert(t, '<'); tinsert(t, lom.tag)
	if lom.attr then
		for a, v in pairs(lom.attr) do
			tinsert(t, ' '); tinsert(t, a)
			tinsert(t, '="'); tinsert(t, xml_specials(v)); tinsert(t, '"')
		end
	end
	tinsert(t, '>')
	for _, v in ipairs(lom) do
		if type(v) == "table" then
			lom_unparse_internal(v, t)
		else
			tinsert(t, xml_specials(tostring(v)))
		end
	end
	tinsert(t, '</'); tinsert(t, lom.tag); tinsert(t, '>')
end
local function lom_unparse(loms)
	local t = { "" }
	for _, v in ipairs(loms) do
		lom_unparse_internal(v, t)
	end
	return tconcat(t)
end


function soap_request(fun, args)
	local req = REQUEST:gsub("%$(%w+)", {FUN = fun, ARGS = lom_unparse(args)})
	local headers = {
			["Content-type"] = "text/xml; charset=utf-8",
			["Content-length"] = req:len(),
			["SOAPAction"] = [["]] .. NAMESPACE .. [[/]] .. fun .. [["]],
			["Authorization"] = AUTH,
		}
	local t = {}
	local result, statuscode, hdrs, statusline = http.request {
		url = URL,
		method = "POST",
		headers = headers,
		source = ltn12.source.string(req),
		sink = ltn12.sink.table(t),
	}
	local body = table.concat(t)

	if tonumber(statuscode) == 200 then
		return skip_soap(lom.parse(body))
	else
		return nil, (statusline or statuscode) .. "\n" .. body
	end
end

--[[ Usage:
result = soap_request("doGoogleSearch",
		{	{ tag="key", VALID_GOOGLE_KEY },
			{ tag="q", "Lua" },
			{ tag="start", 0 },
			{ tag="maxResults", 10 },
			{ tag="filter", false },
			{ tag="restrict", "" },
			{ tag="safeSearch", false },
			{ tag="lr", "" },
			{ tag="ie", "" },
			{ tag="oe", "" },
		})
]]