linusdm
Building a SOAP endpoint in Phoenix
I’m researching the possibility of reviving a project that makes use of ancient SOAP technology. Since hardware is involved, a switch to a newer technology stack is not option, unfortunately.
Has anybody been successful at serving SOAP requests somehow, in a Phoenix project? I see there are some SOAP client libraries on hex.pm, but no SOAP servers.
There was a prior discussion over here, but it concluded that it’s a no-go for Elixir. I hope to find some new evidence that it is possible.
I’m curious if this could be solved by accepting the HTTP requests as usual with Plug or in a Phoenix controller. But then call out to a lower level implementation (possibly C or C++) that does the legwork of parsing the request. Then act upon that request and formulate a response in plain Elixir. And then finally encode the Elixir response by passing it once more through the low-level library that generates the required XML that can be send back by the Plug connection.
I’ve seen libraries like Apache Axis and gSOAP that theoretically could do such magic. But I’m not sure what it would take to actually create this integration.
Other nudges toward better ideas are very much appreciated.
PS: the wsdl specification I’m interested in, if it is of any help, is located here and documented here. It’s a specification for distributing Daisy talking books (audiobooks).
Most Liked
Awlexus
Sorry for the late reply. We create a custom plug that handles the parsing of the incoming soap requests and some utilities for sending a reply.
Using this plug builds a model from the wsdl at compile time, parses and validates incoming requests, and; similar to a controller; executes a function with the same name.
# Example module
defmodule YourCoolSoapService.ServicePlug do
# Usage
# wsdl_file: path to your wsdl file. xsd files should be relative to the files too
# prefix: this prefix will be
use SoapService.Soap.Plug, wsdl_file: "your/wsdl/file/here.wsdl", prefix: "abc"
# Imagine you have an operation called playMusic with
# the request/response types playMusicRequest and playMusicReply
def play_music(conn, abc_playMusicRequest() = params) do
# play the music
case play_the_music(params) do
{:ok, result} -> soap_resp(conn, abc_playMusicResponse())
{:error, error} -> client_fault(conn, "That was your fault")
end
end
end
# Generic soap error
defmodule SoapService.SoapFaultError do
defexception [:detail, :message]
end
defmodule SoapService.Soap.Plug do
defmacro __using__(config) do
quote location: :keep do
# extracts the records from the wsdl
use Erlsom.Records, unquote(config)
# Expose utilities
import SoapService.Soap.Plug
alias SoapService.SoapFaultError
# extracts records for the wsdl terms
for record <- [:wsdl, :"soap:detail", :"soap:Fault", :"soap:Body", :"soap:Header", :"soap:Envelope"] do
record_name = Atom.to_string(record) |> String.replace(":", "") |> String.to_atom()
Record.defrecord(record_name, record, Record.extract(record, from_lib: "detergent/include/detergent.hrl"))
end
def init(opts), do: opts
def call(conn, _opts) do
# read the body, parse it and validate it against the schema
{:ok, body, conn} = Plug.Conn.read_body(conn)
# erlsom_model defined at compiletime by using Erlsom.Records
model = erlsom_model()
case :erlsom.scan(body, model) do
# Valid wsdl operation
{:ok, result, _rest} ->
soap_body = soapEnvelope(result, :Body)
# Extract the requested operation
case soapBody(soap_body, :choice) do
[soap_op] ->
action = record_to_action(soap_op)
# Apply the matching function for the requested operation
try do
apply(__MODULE__, action, [conn, soap_op])
rescue
e in SoapFaultError ->
client_fault(conn, e.message, e.detail)
e ->
require Logger
Logger.error(Exception.format(:error, e, __STACKTRACE__))
client_fault(conn, e.message)
end
_ ->
client_fault(conn, "No such operation")
end
{:error, error} ->
client_fault(conn, error)
end
end
# Helper function that wraps the response with the proper soap terms
def soap_resp(conn, response) do
envelope = soapEnvelope(Header: soapHeader(), Body: soapBody(choice: [response]))
SoapService.Soap.Plug.soap_resp(conn, envelope, erlsom_model())
end
# Helper function to build non-raising error
def client_fault(conn, message, detail \\ nil) do
detail =
if detail do
soap_detail("#any": [detail])
else
:undefined
end
fault =
soapFault(
faultcode: {:qname, 'http://schemas.xmlsoap.org/soap/envelope/', 'Client', 'soap', ''},
faultstring: to_charlist(message),
detail: detail
)
soap_resp(conn, fault)
end
end
end
# Transform the result into a proper soap response and send it
def soap_resp(conn, envelope, model) do
{:ok, xml} = :erlsom.write(envelope, model)
conn
|> Plug.Conn.put_resp_content_type("text/xml")
|> Plug.Conn.send_resp(200, :erlsom_ucs.to_utf8(xml))
|> Plug.Conn.halt()
end
def record_to_action(tuple) do
tuple
|> elem(0)
|> Atom.to_string()
|> remove_prefix()
|> Macro.underscore()
|> String.to_existing_atom()
end
defp remove_prefix(string) do
if new_string = do_remove_prefix(string) do
new_string
else
string
end
end
defp do_remove_prefix(""), do: nil
defp do_remove_prefix(":" <> rest), do: rest
defp do_remove_prefix(<<_, rest::binary>>), do: do_remove_prefix(rest)
end
Awlexus
If you have a wsdl file for the service, you might be able to use the plug from this library. soapex/lib/soapex/plugs/soap.ex at master · kim-company/soapex · GitHub. We are using this library with a wrapper around this plug to serve a production soap/wsdl api. I’d be happy to help if you have additional questions ![]()
dimitarvp
I’d say if the clients (hardware devices that can’t have their software changed, I take it?) do not utilize a central repository of WSDL and SOAP registries and hundreds of schemas then you’re better off just constructing the XML yourself manually. Both Erlang and Elixir have numerous libraries for this but if you struggle with it, we can help.
Ultimately it all boils down to this: WSDL / SOAP are just producing and parsing XML according to certain XML Schema files. That’s it really. Don’t be threatened. No need to shell out to other programming languages unless time is super tight.
Popular in Discussions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








