From 06ece64cb0b2b490d986e1b1033fea9c42174e3a Mon Sep 17 00:00:00 2001 From: fjf2002 Date: Wed, 29 Jun 2022 17:11:15 +0200 Subject: [PATCH] HOW-TO for SOAP (#554) --- docs/how-tos/create-soap-endpoint.rst | 222 ++++++++++++++++++++++++++ docs/index.rst | 2 + 2 files changed, 224 insertions(+) create mode 100644 docs/how-tos/create-soap-endpoint.rst diff --git a/docs/how-tos/create-soap-endpoint.rst b/docs/how-tos/create-soap-endpoint.rst new file mode 100644 index 000000000..62f0d0412 --- /dev/null +++ b/docs/how-tos/create-soap-endpoint.rst @@ -0,0 +1,222 @@ +.. _create_soap_endpoint: + +Create a SOAP endpoint +====================== + +:author: `fjf2002 `_ + +PostgREST now has XML support. With a bit of work, SOAP endpoints become possible. + +Please note that PostgREST supports just ``text/xml`` MIME type in request/response headers ``Content-Type`` and ``Accept``. +If you have to use other MIME types such as ``application/soap+xml``, you could manipulate the headers in your reverse proxy. + + + +Minimal Example +--------------- +This example will simply return the request body, inside a tag ``therequestbodywas``. + +Add the following function to your PostgreSQL database: + +.. code-block:: postgres + + CREATE OR REPLACE FUNCTION my_soap_endpoint(xml) RETURNS xml AS $$ + DECLARE + nsarray CONSTANT text[][] := ARRAY[ + ARRAY['soapenv', 'http://schemas.xmlsoap.org/soap/envelope/'] + ]; + BEGIN + RETURN xmlelement( + NAME "soapenv:Envelope", + XMLATTRIBUTES('http://schemas.xmlsoap.org/soap/envelope/' AS "xmlns:soapenv"), + xmlelement(NAME "soapenv:Header"), + xmlelement( + NAME "soapenv:Body", + xmlelement( + NAME theRequestBodyWas, + (xpath('/soapenv:Envelope/soapenv:Body', $1, nsarray))[1] + ) + ) + ); + END; + $$ LANGUAGE plpgsql; + +Do not forget to refresh the :ref:`PostgREST schema cache `. + +Use ``curl`` for a first test: + +.. code-block:: bash + + curl http://localhost:3000/rpc/my_soap_endpoint \ + --header 'Content-Type: text/xml' \ + --header 'Accept: text/xml' \ + --data-binary @- < + + + + My SOAP Content + + + + XML + +The output should contain the original request body within the ``therequestbodywas`` entity, +and should roughly look like: + +.. code-block:: xml + + + + + + + + My SOAP Content + + + + + + +Unfortunately the ``Accept: text/xml`` header is currently mandatory concerning PostgREST, otherwise it will respond +with a ``Content-Type: application/json`` header and enclose the response with quotes. +(You can check the returned headers by adding ``-v`` to the curl call.) + +If your SOAP clients do not send the ``Accept: text/xml`` header, you can fix that in your nginx reverse proxy +by adding something like ... + +.. code-block:: nginx + + set $accept $http_accept; + if ($contentType ~ "^text/xml($|;)") { + set $accept "text/xml"; + } + proxy_set_header Accept $accept; + +to your ``location`` nginx configuration. +(The given example sets the ``Accept`` header for each request of Content-Type ``text/xml``.) + + +A more elaborate example +------------------------ + +Here we have a SOAP service that converts a fraction to a decimal value, +with pass-through of PostgreSQL errors to the SOAP response. +Please note that in production you probably should not pass through plain database errors +potentially disclosing internals to the client, but instead handle the errors directly. + + +.. code-block:: postgres + + -- helper function + CREATE OR REPLACE FUNCTION _soap_envelope(body xml) + RETURNS xml + LANGUAGE sql + AS $function$ + SELECT xmlelement( + NAME "soapenv:Envelope", + XMLATTRIBUTES('http://schemas.xmlsoap.org/soap/envelope/' AS "xmlns:soapenv"), + xmlelement(NAME "soapenv:Header"), + xmlelement(NAME "soapenv:Body", body) + ); + $function$; + + -- helper function + CREATE OR REPLACE FUNCTION _soap_exception( + faultcode text, + faultstring text + ) + RETURNS xml + LANGUAGE sql + AS $function$ + SELECT _soap_envelope( + xmlelement(NAME "soapenv:Fault", + xmlelement(NAME "faultcode", faultcode), + xmlelement(NAME "faultstring", faultstring) + ) + ); + $function$; + + CREATE OR REPLACE FUNCTION fraction_to_decimal(xml) + RETURNS xml + LANGUAGE plpgsql + AS $function$ + DECLARE + nsarray CONSTANT text[][] := ARRAY[ + ARRAY['soapenv', 'http://schemas.xmlsoap.org/soap/envelope/'] + ]; + exc_msg text; + exc_detail text; + exc_hint text; + exc_sqlstate text; + BEGIN + -- simulating a statement that results in an exception: + RETURN _soap_envelope(xmlelement( + NAME "decimalValue", + ( + (xpath('/soapenv:Envelope/soapenv:Body/fraction/numerator/text()', $1, nsarray))[1]::text::int + / + (xpath('/soapenv:Envelope/soapenv:Body/fraction/denominator/text()', $1, nsarray))[1]::text::int + )::text::xml + )); + EXCEPTION WHEN OTHERS THEN + GET STACKED DIAGNOSTICS + exc_msg := MESSAGE_TEXT, + exc_detail := PG_EXCEPTION_DETAIL, + exc_hint := PG_EXCEPTION_HINT, + exc_sqlstate := RETURNED_SQLSTATE; + RAISE WARNING USING + MESSAGE = exc_msg, + DETAIL = exc_detail, + HINT = exc_hint; + RETURN _soap_exception(faultcode => exc_sqlstate, faultstring => concat(exc_msg, ', DETAIL: ', exc_detail, ', HINT: ', exc_hint)); + END + $function$; + +Let's test the ``fraction_to_decimal`` service with illegal values: + +.. code-block:: bash + + curl http://localhost:3000/rpc/fraction_to_decimal \ + --header 'Content-Type: text/xml' \ + --header 'Accept: text/xml' \ + --data-binary @- < + + + + 42 + 0 + + + + XML + +The output should roughly look like: + +.. code-block:: xml + + + + + + 22012 + division by zero, DETAIL: , HINT: + + + + + +References +---------- +For more information concerning PostgREST, cf. + +- :ref:`s_proc_single_unnamed` +- :ref:`scalar_return_formats` +- :ref:`Nginx reverse proxy ` + +For SOAP reference, visit + +- the specification at https://www.w3.org/TR/soap/ +- shorter more practical advice is available at https://www.w3schools.com/xml/xml_soap.asp diff --git a/docs/index.rst b/docs/index.rst index db3a181d5..989c55f16 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -206,9 +206,11 @@ These are recipes that'll help you address specific use-cases. how-tos/working-with-postgresql-data-types how-tos/providing-images-for-img + how-tos/create-soap-endpoint - :doc:`how-tos/providing-images-for-img` - :doc:`how-tos/working-with-postgresql-data-types` +- :doc:`how-tos/create-soap-endpoint` Ecosystem ---------