Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
SOAP.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#ifndef _Stroika_Framework_WebService_SOAP_h_
5#define _Stroika_Framework_WebService_SOAP_h_ 1
6
7#include "Stroika/Frameworks/StroikaPreComp.h"
8
9#include <functional>
10#include <stdexcept>
11
13#include "Stroika/Foundation/Common/KeyValuePair.h"
14#include "Stroika/Foundation/Containers/Sequence.h"
17#include "Stroika/Foundation/Execution/Exceptions.h"
18#include "Stroika/Foundation/IO/Network/Transfer/Connection.h"
21
22#include "Stroika/Frameworks/WebServer/Message.h"
23
24/**
25 * \file
26 *
27 * \note Code-Status: <a href="Code-Status.md#Alpha">Alpha</a>
28 */
29
30/**
31 * SOAP 1.1 (https://www.w3.org/TR/2000/NOTE-SOAP-20000508/) RPC: a client calls a method - a Request, POSTed to the server's URL
32 * - and the server answers with the method's out arguments, a Response, or says why it could not, a Fault. Each written
33 * (Serialize) and read (DeSerialize) here, and sent (Invoke) and answered (HandleRequest), so neither side writes or reads XML of
34 * its own.
35 *
36 * Only what RPC with simple arguments uses: arguments as text, a fault's detail as XML text - no SOAP encoding of types, no
37 * headers, no SOAP 1.2. UPnP's control (Frameworks::UPnP::SOAP) is built on it.
38 *
39 * Read and written with the XML DOM: in a build without one (qStroika_Foundation_DataExchange_XML_SupportDOM), Serialize and
40 * DeSerialize - and so Invoke and HandleRequest - throw, and there is no EnvelopeSchema.
41 *
42 * \par Example Usage
43 * \code
44 * // a client: call GetPrice
45 * SOAP::Response r = SOAP::Invoke (URI{"http://example.com/stock"sv}, "\"urn:example:stock#GetPrice\""sv,
46 * SOAP::Request{.fNamespace = "urn:example:stock"sv, .fMethod = "GetPrice"sv, .fArguments = {{"Symbol"sv, "IBM"sv}}});
47 * optional<String> price = r.LookupArgument ("Price"sv);
48 *
49 * // the server: its URL's route
50 * Route{HTTP::MethodsRegEx::kPost, "stock"_RegEx, [] (Message& m) {
51 * SOAP::HandleRequest (m, [] (const SOAP::Request& request) {
52 * if (request.fMethod != "GetPrice"sv) {
53 * Execution::Throw (SOAP::FaultException{SOAP::Fault{.fFaultCode = SOAP::Fault::kClient, .fFaultString = "no such method"sv}});
54 * }
55 * return SOAP::Response{.fNamespace = request.fNamespace, .fMethod = request.fMethod, .fArguments = {{"Price"sv, "34.5"sv}}};
56 * });
57 * }}
58 * \endcode
59 */
61
62 using namespace Stroika::Foundation;
63
68 using IO::Network::URI;
69
70 /**
71 * What a SOAP 1.1 message is sent as: text/xml (section 6.1.1), UTF-8
72 */
73 inline const InternetMediaType kContentType{"text/xml; charset=\"utf-8\""sv};
74
75 /**
76 * A method's arguments, each its name and value - in order, the order they are sent in (section 7.1). Values as text.
77 */
79
80 /**
81 * \brief A call of a method (section 7.1): the Body's one element, named the method, in its namespace - its in arguments
82 * its elements
83 */
84 struct Request {
85 /**
86 * The method's namespace (a URI)
87 */
89 String fMethod;
90 /**
91 * Its in arguments
92 */
94
95 /**
96 * The value of the argument called name; nullopt if none is
97 */
98 nonvirtual optional<String> LookupArgument (const String& name) const;
99
100 bool operator== (const Request&) const = default;
101
102 /**
103 * @see Characters::ToString ();
104 */
105 nonvirtual String ToString () const;
106 };
107
108 /**
109 * \brief A call's answer, the method having been done (section 7.1): the Body's one element, named the method then Response
110 * - its out arguments its elements
111 */
112 struct Response {
113 /**
114 * The method's namespace, as in the request
115 */
117 /**
118 * The method, as in the request (written with Response after it)
119 */
121 /**
122 * Its out arguments
123 */
125
126 /**
127 * The value of the argument called name; nullopt if none is
128 */
129 nonvirtual optional<String> LookupArgument (const String& name) const;
130
131 bool operator== (const Response&) const = default;
132
133 /**
134 * @see Characters::ToString ();
135 */
136 nonvirtual String ToString () const;
137 };
138
139 /**
140 * \brief A call's answer, the method having not been done: why not (section 4.4) - sent in an HTTP response whose status is
141 * 500 (section 6.2).
142 */
143 struct Fault {
144 /**
145 * The fault codes SOAP 1.1 gives (section 4.4.1): Client, the call's fault - it should not be made again as it is; Server,
146 * the server's - it may succeed later; ...
147 */
148 static inline const String kVersionMismatch{"VersionMismatch"sv};
149 static inline const String kMustUnderstand{"MustUnderstand"sv};
150 static inline const String kClient{"Client"sv};
151 static inline const String kServer{"Server"sv};
152
153 /**
154 * One of those - or one of them followed by its own, dotted: Client.Authentication, say. Without the envelope's prefix.
155 *
156 * \pre an XML name (XML::IsNCName: letters, digits, '.', '-', '_'), since Serialize writes it as a QName's local part.
157 * DeSerialize reads one that is not leniently, as its longest leading part that is a name ("Server Error" as
158 * Server), or Server - so a Fault read can always be written back; checked (Options::fValidateReadAgainst),
159 * the schema refuses it
160 */
162 /**
163 * For a person
164 */
166 /**
167 * The detail element's content - XML, as the application gives it (UPnP's UPnPError, say); nullopt for no detail.
168 * Written as given; read as the XML of its elements (a namespace prefix it uses but declares outside them is not
169 * carried: declare it within).
170 */
171 optional<String> fDetail;
172
173 bool operator== (const Fault&) const = default;
174
175 /**
176 * @see Characters::ToString ();
177 */
178 nonvirtual String ToString () const;
179 };
180
181 /**
182 * \brief A Fault, thrown: Invoke throws the one a server answers with, and a HandleRequest handler throws one to answer with it.
183 */
184 class FaultException : public Execution::Exception<runtime_error> {
185 public:
186 /**
187 */
188 FaultException (const Fault& fault);
189
190 public:
191 /**
192 */
193 nonvirtual Fault GetFault () const;
194
195 private:
196 Fault fFault_;
197 };
198
199 /**
200 * \brief Where Serialize writes SOAP's encodingStyle attribute (http://schemas.xmlsoap.org/soap/encoding/) - which SOAP
201 * 1.1's prose says MAY go on any element, and on which its schema, its examples and the WS-I Basic Profile disagree
202 *
203 * o eOmitted - the default: legal by SOAP's schema, where the attribute is optional wherever it may go, and what
204 * the WS-I Basic Profile, which most stacks follow, requires (1.1, section 3.1.6: R1005 forbids it on SOAP's own
205 * elements, R1006 on a Body's children). The message then claims no encoding, and SOAP 1.1 defines no default
206 * (section 4.1.1), so its receiver goes by what it knows otherwise - a WSDL binding, or its protocol (UPnP 1.1
207 * takes a message with none to be SOAP-encoded)
208 * o eOnBody - legal by SOAP's schema, but not by the WS-I Basic Profile
209 * o eOnEnvelope - what SOAP 1.1's examples, and older RPC stacks, write, and what UPnP requires; SOAP's schema as
210 * published refuses it - EnvelopeSchema, corrected, does not - and so does the WS-I Basic Profile
211 */
213 eOnBody,
214 eOnEnvelope,
215 eOmitted,
216 };
217
218#if qStroika_Foundation_DataExchange_XML_SupportParsing and qStroika_Foundation_DataExchange_XML_SupportSchema and qStroika_Foundation_DataExchange_XML_SupportDOM
219 /**
220 * \brief SOAP 1.1's envelope schema, compiled by the provider o names - embedded, so nothing is fetched and no
221 * Resource::Resolver is wanted (it imports and includes nothing)
222 *
223 * What it checks is the ENVELOPE: that Body holds what a Body may, that a Fault's children are the ones SOAP names
224 * and unqualified, that encodingStyle is qualified. A Body's own content is xs:any to it, so a message's payload is
225 * the caller's business, not this schema's.
226 *
227 * By default CORRECTED, as SOAP 1.1 itself has it: its prose (section 4.1.1) says of encodingStyle "This attribute MAY
228 * appear on any element", and all six of its example messages put it on the Envelope - but its schema declares
229 * Envelope with <xs:anyAttribute namespace="##other">, which excludes SOAP's own namespace and so forbids exactly
230 * that. The correction is an <xs:attribute ref="tns:encodingStyle"/> on Envelope; nothing else is touched. So it
231 * accepts encodingStyle wherever a message carries it (@see EncodingStyleAttribute); allowEncodingStyleOnEnvelope
232 * false gives the schema as published, which refuses it on the Envelope.
233 *
234 * Compiled once per provider and allowEncodingStyleOnEnvelope, the first time each is asked for, and kept - so a call
235 * after the first is cheap.
236 *
237 * It lives in a translation unit of its own, so its 6K is linked only into a program that calls it.
238 *
239 * \note The schema is http://schemas.xmlsoap.org/soap/envelope/ - verbatim, but for that correction - under the W3C
240 * Software License, whose notice travels with it - @see Schemas/soap-envelope-1.1.xsd
241 */
242 DataExchange::XML::Schema::Ptr EnvelopeSchema (const DataExchange::XML::Options& o = {}, bool allowEncodingStyleOnEnvelope = true);
243#endif
244
245 /**
246 * \brief What SOAP must be told that has a sensible default - taken by Serialize and DeSerialize, defaulted, so a
247 * caller says only what it wants different
248 *
249 * One Options for both, each field saying which it governs: fXMLOptions both, fValidateReadAgainst reading only,
250 * fEncodingStyleAttribute writing only.
251 */
252 struct Options {
253 /**
254 * \brief How XML is read and written - by default, with Stroika's default provider; nearly always left as is (@see
255 * XML::Options)
256 */
258
259 /**
260 * \brief Check a message being read against this schema - EnvelopeSchema - throwing where it does not hold;
261 * nullptr, the default, checks nothing
262 *
263 * \pre compiled by the provider that reads: EnvelopeSchema (fXMLOptions)
264 *
265 * \code
266 * // told early rather than half understanding a message
267 * const SOAP::Options kStrict_{.fValidateReadAgainst = SOAP::EnvelopeSchema ()};
268 * \endcode
269 *
270 * Off by default: a reader that checked would refuse traffic other stacks send - each departs from SOAP in ways of
271 * its own - and whether to is the application's choice, not the library's. Check with EnvelopeSchema as it is by
272 * default, corrected: stacks follow SOAP 1.1's prose and examples in putting encodingStyle on the Envelope, which its
273 * schema as published forbids - Tests/55's SOAP_Others_, written from what other stacks emit, is two such messages,
274 * in two prefix styles, one carrying Microsoft's urn:schemas-microsoft-com:datatypes attributes.
275 *
276 * Reading only - nothing checks what Serialize writes, because a message's data barely reaches what the schema
277 * checks. An envelope's structure - Envelope, Body, a Fault's children, where encodingStyle goes (@see
278 * fEncodingStyleAttribute) - is fixed by this code, and the schema leaves a message's content to its sender: Body
279 * and a Fault's detail are xs:any to it.
280 * The one exception is a Fault's code, written as a QName - so one that is not an XML name (with a space, say)
281 * writes a faultcode the schema refuses. Otherwise a run-time check would only re-check this code, which Tests/57's
282 * SOAP_ValidatesAgainstItsSchema_ does once.
283 *
284 * \note A schema named here, rather than a flag that reaches for one, is what lets a program that never validates
285 * not carry it: nothing inside SOAP mentions EnvelopeSchema, so its 6K is linked only by a caller that asks
286 * (@see EnvelopeSchema).
287 */
289
290 /**
291 * \brief Where Serialize writes encodingStyle - nowhere, the default, so what is written holds to SOAP's schema as
292 * published and to the WS-I Basic Profile (@see EncodingStyleAttribute). Writing only
293 *
294 * DeSerialize reads a message whatever it carries - encodingStyle anywhere, or none; whether it may be where it
295 * is is for a schema to say (fValidateReadAgainst).
296 */
297 EncodingStyleAttribute fEncodingStyleAttribute{EncodingStyleAttribute::eOmitted};
298 };
299
300 /**
301 * \brief The SOAP message - its XML, UTF-8 - which sends it: a call, its answer, or a fault
302 */
303 Memory::BLOB Serialize (const Request& request, const Options& options = {});
304 Memory::BLOB Serialize (const Response& response, const Options& options = {}); ///< \brief The SOAP message - its XML, UTF-8 - which sends it
305 Memory::BLOB Serialize (const Fault& fault, const Options& options = {}); ///< \brief The SOAP message - its XML, UTF-8 - which sends it
306
307 /**
308 * \brief The SOAP message read: what Serialize writes, and any other's. Throws DataExchange::BadFormatException if it is not
309 * one - a Response read from a fault, say; and throws in a build with no XML parser.
310 */
311 void DeSerialize (const Memory::BLOB& b, Request* request, const Options& options = {});
312 void DeSerialize (const Memory::BLOB& b, Response* response, const Options& options = {}); ///< \brief The SOAP message read
313 void DeSerialize (const Memory::BLOB& b, Fault* fault, const Options& options = {}); ///< \brief The SOAP message read
314
315 /**
316 * \brief Call a method: POST request to url, with soapAction (in quotes, as SOAPAction headers are: section 6.1.1) - and
317 * return its answer. Throws FaultException if answered with a fault; another failed HTTP response as
318 * IO::Network::Transfer::Exception; an answer that is not SOAP as DataExchange::BadFormatException.
319 *
320 * Sent over connection, where one is given: the caller's - set up as it wants (a timeout, certificate checking), kept
321 * to reuse across calls, or leased from a Transfer::ConnectionPool. Else over a new connection, with the defaults.
322 *
323 * \par Example Usage
324 * \code
325 * // calls to one server, over a connection of the caller's: answered within 5 seconds, or a timeout thrown
326 * IO::Network::Transfer::Connection::Ptr c = IO::Network::Transfer::Connection::New ();
327 * c.SetTimeout (5s);
328 * SOAP::Response price = SOAP::Invoke (c, url, "\"urn:example:stock#GetPrice\""sv, getPriceRequest);
329 * SOAP::Response volume = SOAP::Invoke (c, url, "\"urn:example:stock#GetVolume\""sv, getVolumeRequest);
330 * \endcode
331 */
332 Response Invoke (const URI& url, const String& soapAction, const Request& request, const Options& options = {});
333 Response Invoke (IO::Network::Transfer::Connection::Ptr connection, const URI& url, const String& soapAction, const Request& request,
334 const Options& options = {}); ///< \brief Call a method, over the caller's connection
335
336 /**
337 * \brief Answer a call, at its URL's route: 415 (Unsupported Media Type) if its body is not text/xml; else its Request read
338 * and answered with handler's Response - or, status 500, with the Fault handler throws (as FaultException), or a Client
339 * fault if the request is not a SOAP call (nor, with options.fValidateReadAgainst, a valid one).
340 */
341 void HandleRequest (WebServer::Message& m, const function<Response (const Request&)>& handler, const Options& options = {});
342
343}
344
345#endif /*_Stroika_Framework_WebService_SOAP_h_*/
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:219
A Fault, thrown: Invoke throws the one a server answers with, and a HandleRequest handler throws one ...
Definition SOAP.h:184
const InternetMediaType kContentType
Definition SOAP.h:73
void DeSerialize(const Memory::BLOB &b, Request *request, const Options &options={})
The SOAP message read: what Serialize writes, and any other's. Throws DataExchange::BadFormatExceptio...
Definition SOAP.cpp:301
Memory::BLOB Serialize(const Request &request, const Options &options={})
The SOAP message - its XML, UTF-8 - which sends it: a call, its answer, or a fault.
Definition SOAP.cpp:249
Response Invoke(const URI &url, const String &soapAction, const Request &request, const Options &options={})
Call a method: POST request to url, with soapAction (in quotes, as SOAPAction headers are: section 6....
Definition SOAP.cpp:383
void HandleRequest(WebServer::Message &m, const function< Response(const Request &)> &handler, const Options &options={})
Answer a call, at its URL's route: 415 (Unsupported Media Type) if its body is not text/xml; else its...
EncodingStyleAttribute
Where Serialize writes SOAP's encodingStyle attribute (http://schemas.xmlsoap.org/soap/encoding/) - w...
Definition SOAP.h:212
How XML is read and written - for now, with which provider - taken last by each XML function (DOM::Do...
A call's answer, the method having not been done: why not (section 4.4) - sent in an HTTP response wh...
Definition SOAP.h:143
nonvirtual String ToString() const
Definition SOAP.cpp:215
What SOAP must be told that has a sensible default - taken by Serialize and DeSerialize,...
Definition SOAP.h:252
EncodingStyleAttribute fEncodingStyleAttribute
Where Serialize writes encodingStyle - nowhere, the default, so what is written holds to SOAP's schem...
Definition SOAP.h:297
DataExchange::XML::Schema::Ptr fValidateReadAgainst
Check a message being read against this schema - EnvelopeSchema - throwing where it does not hold; nu...
Definition SOAP.h:288
DataExchange::XML::Options fXMLOptions
How XML is read and written - by default, with Stroika's default provider; nearly always left as is (...
Definition SOAP.h:257
A call of a method (section 7.1): the Body's one element, named the method, in its namespace - its in...
Definition SOAP.h:84
nonvirtual optional< String > LookupArgument(const String &name) const
Definition SOAP.cpp:173
A call's answer, the method having been done (section 7.1): the Body's one element,...
Definition SOAP.h:112
nonvirtual optional< String > LookupArgument(const String &name) const
Definition SOAP.cpp:194