Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ compile:
@rebar compile

ct: compile test_deps
mkdir -p ./logs
@echo "Running common tests..."
-@ct_run -noshell -pa ebin \
-pa deps/*/ebin \
-pa test/deps/*/ebin \
-name test \
-sname test \
-logdir ./logs \
-env TEST_DIR ./test \
-spec ./test/test_specs.spec \
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ provided.
The figure below provides a high level overview of the application and the
main integration points.

![SOAP application](doc/soap architecture.png "Overview of the SOAP
application")
![Overview of the SOAP application](doc/soap%20architecture.png?raw=true)

The starting point can be a WSDL for which you need to
implement an Erlang SOAP client or server, or an existing Erlang function
Expand All @@ -33,7 +32,7 @@ The figure below shows how the application can generate the relevant modules,
either starting from a WSDL ("contract first") or starting from a set of
Erlang type specifications ("contract last").

![Process and artifacts](doc/soap process.png "Process and artifacts")
![Process and artifacts](doc/soap%20process.png?raw=true)


## Documentation overview
Expand Down
9 changes: 6 additions & 3 deletions doc/soap.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,17 @@ The following options are available:

- `{erlsom_options, proplists:proplist()}` - These options are passed on to
erlsom. This can for example be used to specify the options for dealing
with imported namespaces in the `types` (xsd) section of the WSDL. See
the documentation of erlsom for more details.
with imported namespaces in the `types` (xsd) section of the WSDL. The
default value for this option is `[{strict, true}]`.

- `{strict, boolean()}` - If `false`, the functions that decode and encode XML
will convert only integer (xsd:integer) and boolean values from the XML
to and from Erlang integer/boolean values (this is the standard Erlsom behaviour). All
other types will be represented as strings (without testing the
validity).
validity). (Note that this option can also be set as one of the
`erlsom_options`, see above. If the two settings conflict, the version
that is set explicitly prevails over the value that is part of the
`erlsom_options`).

If `true` (the default for the `soap` application) a number of
additional data-types will be converted (and checked for validity, hence
Expand Down
43 changes: 23 additions & 20 deletions doc/soap_client_tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ tutorial](soap_server_tutorial.md).

The [server tutorial](soap_server_tutorial.md) shows how the
client module that is generated by `soap:wsdl2soap()` can be used. It
explains how you can call a service, with or without SOAP headers.It does
explains how you can call a service, with or without SOAP headers. It does
_not_ explain how the generated client can be customized. This is necessary
in particular if you want to access information in the SOAP header of the
response message.
Expand Down Expand Up @@ -47,30 +47,33 @@ generating the client).
must be `[]`).

2. The second argument is a list of SOAP header blocks. If any are
specified, these will be included in the SOAP header.
specified, these will be included in the SOAP header.

There are 2 ways to specify a SOAP header block:
1. As an `iolist().`
This must be a valid XML snippet. It will simply be copied literally to the SOAP Header.
2. As a record that corresponds to a type from the WSDL.
This is comparable with the way the content of the SOAP body is passed.
The record will be translated to XML by the `soap` application.
There are 2 ways to specify a SOAP header block:
1. As an `iolist().`
This must be a valid XML snippet. It will simply be copied literally
to the SOAP Header.

2. As a record that corresponds to a type from the WSDL.
This is comparable with the way the content of the SOAP body is passed.
The record will be translated to XML by the `soap` application.

3. The third argument is a list of options. The following options are
available:

1. `{http_client, module()}` - use an HTTP
client that is different from what was specified when generating the
client module. The `Module` must be an Erlang module that implements the
interface that is described in [Integrating a HTTP client](integrating_a_http_client.md).
2. `{url, URL::string()}` - use a URL that is different from the URL
specified in the WSDL.
3. `{timeout, Value}`. This allows to set the timeout for the request, in milliseconds.
It defaults to the default of the used client (ibrowse: 3000 milliseconds, inets/httpc: infinity).

All other options used here are passed on to the application that is used
to implement the HTTP client (so the options that are available and their
effect depends on the selected HTTP server).
1. `{http_client, module()}` - use an HTTP
client that is different from what was specified when generating the
client module. The `Module` must be an Erlang module that implements the
interface that is described in [Integrating a HTTP client](integrating_a_http_client.md).
2. `{url, URL::string()}` - use a URL that is different from the URL
specified in the WSDL.
3. `{timeout, Value}`. This allows to set the timeout for the request,
in milliseconds. It defaults to the default of the used client
(ibrowse: 3000 milliseconds, inets/httpc: infinity).

All other options used here are passed on to the application that is used
to implement the HTTP client (so the options that are available and their
effect depends on the selected HTTP server).

4. (Only if the option `{attachments, true}` was used to generate the
client) - Attachments to be added to the request. See [SOAP
Expand Down
2 changes: 1 addition & 1 deletion doc/supported_standards_and_limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The following standards have been used as the starting points:
- [SOAP 1.1](https://www.w3.org/TR/2000/NOTE-SOAP-20000508/)
- [SOAP 1.2](https://www.w3.org/TR/soap/)
- [SOAP Messages with Attachments](https://www.w3.org/TR/SOAP-attachments)
- [WSDL 1.1](https://www.w3.org/TR/wsdl)
- [WSDL 1.1](https://www.w3.org/TR/2001/NOTE-wsdl-20010315)
- [WSDL 1.1 Binding extension for SOAP 1.2](https://www.w3.org/Submission/wsdl11soap12/)
- [WSDL 2.0](https://www.w3.org/TR/2007/REC-wsdl20-20070626/) - but only to a limited extent.

Expand Down
19 changes: 16 additions & 3 deletions src/soap.erl
Original file line number Diff line number Diff line change
Expand Up @@ -312,15 +312,28 @@ wsdl2erlang(File, Options) ->
Port_names),
Namespaces = soap_compile_wsdl:get_namespaces(File, Options),
Prefixes = get_prefixes(Namespaces, Options),
Strict = proplists:get_value(strict, Options, true),
%% The default for 'strict' differs between soap and erlsom.
%% For reasons of backwards compatibility the Erlsom `strict` option
%% can be set in 2 ways: as an `erlsom_option` and as a separate
%% explicit option (the explicit option "wins" if both variants are
%% provided).
ErlsomOpts = proplists:get_value(erlsom_options, Options, []),
Strict1 = proplists:get_value(strict, ErlsomOpts, true),
Strict2 = proplists:get_value(strict, Options, Strict1),
ErlsomOpts2 = [Option
|| {Key, _} = Option <- ErlsomOpts, Key /= strict],
Remove = [generate, http_server, http_client, port, service, namespaces,
client_name, server_name, hrl_name, strict, generate_tests],
client_name, server_name, hrl_name, erlsom_options, strict,
generate_tests],
Other_options = [Option || {Key, _} = Option <- Options,
not(lists:member(Key, Remove))],
Options2 = lists:flatten([{generate, Generate}, {namespaces, Prefixes},
{generate_tests, Generate_tests},
Http_server_options, Http_client_options,
{strict, Strict} | Other_options]),
{erlsom_options, [{namespaces, Prefixes},
{strict, Strict2}
| ErlsomOpts2]}
| Other_options]),
soap_compile_wsdl:file(File, Service, Port, Options2, Hrl_name).

%% Generate a module with test functions (using default values) for every
Expand Down
4 changes: 2 additions & 2 deletions src/soap.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
name :: string(),
operation :: atom(), %% name of function as used in handler module
soap_action = [] :: string(),
wrapper_ns = [] :: string(), %% namespace for the wrapper element (in
wrapper_ns = [] :: string() | undefined, %% namespace for the wrapper element (in
%% case of rpc style)
op_type :: notification | request_response,
in_type :: [{string(), atom()}] | atom(), %% the list type is only used
Expand All @@ -47,7 +47,7 @@
target_ns :: string(),
soap_ns :: string(),
style :: string() | undefined, %% "rpc" | "document"
decoders :: [{string(), module}],
decoders :: [{string(), module}] | undefined,
url :: string(),
port :: string(),
binding :: string(),
Expand Down
5 changes: 3 additions & 2 deletions src/soap_client_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
fault_parser :: fun(),
parser_state :: any(),
soap_headers = [] :: [string() | tuple()],
soap_body :: string(),
soap_body :: string() | tuple(),
is_fault = false :: boolean(),
namespaces = [] :: [{prefix(), uri()}]
}).
Expand Down Expand Up @@ -170,7 +170,8 @@ call_http(Http_body,
version = Version,
url = Url}, Http_headers, Content_type) ->
%%io:format("request: ~nheaders: ~p~nbody: ~s~n", [Http_headers, Http_body]),
Http_res = Client:http_request(Url, Http_body, Http_client_options,
Http_options = lists:keydelete(http_headers, 1, Http_client_options),
Http_res = Client:http_request(Url, Http_body, Http_options,
Http_headers, Content_type),
case Http_res of
{ok, Code, Response_headers, Response_body}
Expand Down
16 changes: 13 additions & 3 deletions src/soap_cowboy_1_protocol.erl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
-module(soap_cowboy_1_protocol).
%% -behaviour(cowboy_sub_protocol).

-export([upgrade/4]).
-export([upgrade/4, upgrade/5]).
-export([enrich_req/2]).
-export([respond/4]).

Expand All @@ -48,9 +48,19 @@
{Implementation_handler::module(), Options::any()}) ->
{ok, cowboy_req(), cowboy_env()}.
upgrade(Cowboy_req, Env, Soap_handler, {Handler, Options}) ->
soap_cowboy_protocol:upgrade(Cowboy_req, Env, Soap_handler,
{Handler, Options}, cowboy_1, ?MODULE).
{ok, Message, Cowboy_req2} = cowboy_req:body(Cowboy_req),
upgrade(Cowboy_req2, Env, Soap_handler, {Handler, Options}, Message).

%% There might exist middleware that reads body from the cowboy_req, in which
%% case it will be no longer available while calling upgrade/4. In this case
%% you are responsible for propogating Body directly to upgrade/5
-spec upgrade(Cowboy_req::cowboy_req(), Env::cowboy_env(),
Soap_handler::module(),
{Implementation_handler::module(), Options::any()}, Body::binary()) ->
{ok, cowboy_req(), cowboy_env()}.
upgrade(Cowboy_req, Env, Soap_handler, {Handler, Options}, Message) ->
soap_cowboy_protocol:upgrade(Cowboy_req, Env, Soap_handler,
{Handler, Options}, cowboy_1, ?MODULE, Message).

enrich_req(Cowboy_req, Soap_req) ->
{Method, Req2} = cowboy_req:method(Cowboy_req),
Expand Down
19 changes: 13 additions & 6 deletions src/soap_cowboy_2_protocol.erl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
-module(soap_cowboy_2_protocol).
%%-behaviour(cowboy_sub_protocol).

-export([upgrade/6]).
-export([upgrade/4, upgrade/5]).
-export([enrich_req/2]).
-export([respond/4]).

Expand All @@ -46,11 +46,18 @@
%% This callback is expected to behave like a middleware and to return an
%% updated req object and environment.
-spec upgrade(Cowboy_req::cowboy_req(), Env::cowboy_env(),
Soap_handler::module(), {Implementation_handler::module(), Options::any()},
Timeout::any(), Hibernate::any()) -> {ok, cowboy_req(), cowboy_env()}.
upgrade(Cowboy_req, Env, Soap_handler, {Handler, Options}, _, _) ->
soap_cowboy_protocol:upgrade(Cowboy_req, Env, Soap_handler,
{Handler, Options}, cowboy_2, ?MODULE).
Soap_handler::module(), {Implementation_handler::module(), Options::any()})
-> {ok, cowboy_req(), cowboy_env()}.
upgrade(Cowboy_req, Env, Soap_handler, {Handler, Options}) ->
{ok, Message, Cowboy_req2} = cowboy_req:read_body(Cowboy_req),
upgrade(Cowboy_req2, Env, Soap_handler, {Handler, Options}, Message).

%% There might exist middleware that reads body from the cowboy_req, in which
%% case it will be no longer available while calling upgrade/4. In this case
%% you are responsible for propogating Body directly to upgrade/5
upgrade(Cowboy_req, Env, Soap_handler, {Handler, Options}, Message) ->
soap_cowboy_protocol:upgrade(Cowboy_req, Env, Soap_handler,
{Handler, Options}, cowboy_2, ?MODULE, Message).

enrich_req(Cowboy_req, Soap_req) ->
Method = cowboy_req:method(Cowboy_req),
Expand Down
38 changes: 20 additions & 18 deletions src/soap_cowboy_protocol.erl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

-module(soap_cowboy_protocol).

-export([upgrade/6]).
-export([upgrade/7]).

-record(state, {
env :: cowboy_middleware:env(),
Expand All @@ -51,12 +51,12 @@
-spec upgrade(Cowboy_req::cowboy_req(), Env::cowboy_env(),
Soap_handler::module(), {Implementation_handler::module(), Options::any()},
Version::atom(),
Version_module::module()) -> {ok, cowboy_req(), cowboy_env()}.
upgrade(Cowboy_req, Env, _, {Handler, Options}, Version, Version_module) ->
Version_module::module(), Body::binary()) -> {ok, cowboy_req(), cowboy_env()}.
upgrade(Cowboy_req, Env, _, {Handler, Options}, Version, Version_module, Body) ->
Cowboy_state = #state{env = Env, handler = Handler},
case soap_server_handler:new_req(Handler, Version, Options, Cowboy_req) of
{continue, Soap_req} ->
check_conformance(Soap_req, Cowboy_req, Cowboy_state, Version_module);
check_conformance(Soap_req, Cowboy_req, Cowboy_state, Version_module, Body);
{ok, _StatusCode, _Headers, _Body, _Server_req} = Error ->
make_response(Error, Cowboy_state, Version_module)
end.
Expand All @@ -65,45 +65,47 @@ upgrade(Cowboy_req, Env, _, {Handler, Options}, Version, Version_module) ->
%%% Internal functions
%%% ============================================================================

check_conformance(Soap_req, Cowboy_req, Cowboy_state, Version_module) ->
check_conformance(Soap_req, Cowboy_req, Cowboy_state, Version_module, Body) ->
%% collect some information about the protocol, so that
%% conformance can be checked.
Soap_req2 = Version_module:enrich_req(Cowboy_req, Soap_req),
case soap_server_handler:check_http_conformance(Soap_req2) of
{continue, Soap_req3} ->
handle_xml(Soap_req3, Cowboy_state, Version_module);
handle_xml(Soap_req3, Cowboy_state, Version_module, Body);
{ok, _StatusCode, _Headers, _Body, _Server_req} = Error ->
make_response(Error, Cowboy_state, Version_module)
end.

handle_xml(Soap_req, Cowboy_state, Version_module) ->
Cowboy_req = soap_req:server_req(Soap_req),
{ok, Message, Cowboy_req2} = cowboy_req:body(Cowboy_req),
Soap_req2 = soap_req:set_server_req(Soap_req, Cowboy_req2),
Soap_req3 = soap_req:set_http_body(Soap_req2, Message),
Content_type = soap_req:content_type(Soap_req3),
handle_xml(Soap_req, Cowboy_state, Version_module, Message) ->
Soap_req2 = soap_req:set_http_body(Soap_req, Message),
Content_type = soap_req:content_type(Soap_req2),
%% get the soap message (Xml) from the request body
{Xml, Soap_req4} =
case string:to_lower(lists:sublist(Content_type, 17)) of
{Xml, Soap_req3} =
case maybe_content_type(Content_type) of
"multipart/related" ->
%% soap with attachments, the message is in the first part
try
[{Mime_headers, Body} | Attachments] =
mime_decode(Message, Content_type),
{Body,
soap_req:set_mime_headers(
soap_req:set_req_attachments(Soap_req3, Attachments),
soap_req:set_req_attachments(Soap_req2, Attachments),
Mime_headers)}
catch
_Class:_Type ->
{Message, Soap_req3}
{Message, Soap_req2}
end;
_ ->
{Message, Soap_req3}
{Message, Soap_req2}
end,
Handler_resp = soap_server_handler:handle_message(Xml, Soap_req4),
Handler_resp = soap_server_handler:handle_message(Xml, Soap_req3),
make_response(Handler_resp, Cowboy_state, Version_module).

maybe_content_type(undefined) ->
undefined;
maybe_content_type(Content_type) ->
string:to_lower(lists:sublist(Content_type, 17)).

mime_decode(Message, Content_type_header) ->
Mime_parameters = lists:nthtail(17, Content_type_header),
Parsed_parameters = soap_mime:parse_mime_parameters(Mime_parameters),
Expand Down
4 changes: 2 additions & 2 deletions src/soap_parse_wsdl.erl
Original file line number Diff line number Diff line change
Expand Up @@ -549,8 +549,8 @@ add_model({Xsd, Ns}, {CombinedModel, Namespaces, ErlsomOptions}) ->
{ok, Model} = erlsom:compile_xsd(Xsd,
[{include_any_attribs, false}
,{already_imported, Namespaces}
,{prefix, Prefix}
,{strict, true} | ErlsomOptions]),
,{namespaces, [{Ns, Prefix}]}
| ErlsomOptions]),
NewModel = case CombinedModel of
undefined ->
Model;
Expand Down
19 changes: 8 additions & 11 deletions src/soap_server_cowboy_1.erl
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,14 @@ start(Module) ->
start(Module, []).

start(Module, Options) ->
Port = proplists:get_value(port, Options, 8080),
Acceptors = proplists:get_value(nr_acceptors, Options, 100),
ok = application:ensure_started(crypto),
ok = application:ensure_started(ranch),
ok = application:ensure_started(cowlib),
ok = application:ensure_started(cowboy),
Dispatch = cowboy_router:compile([
{'_', [{'_', ?MODULE, {Module, Options}}]}]),
{ok, _} = cowboy:start_http(http, Acceptors, [{port, Port}], [
{env, [{dispatch, Dispatch}]}]).

Port = proplists:get_value(port, Options, 8080),
Acceptors = proplists:get_value(nr_acceptors, Options, 100),
{ok, _} = application:ensure_all_started(cowboy),
Dispatch = cowboy_router:compile([
{'_', [{'_', ?MODULE, {Module, Options}}]}]),
{ok, _} = cowboy:start_http(http, Acceptors, [{port, Port}],
[{env, [{dispatch, Dispatch}]}]).

stop() ->
cowboy:stop_listener(http),
application:stop(cowboy),
Expand Down
Loading