Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Architecture

Overview

soap-server is a pipeline of discrete stages that run for every incoming HTTP request. Each stage is implemented in its own module. The library never generates code at compile time (no proc-macros); all WSDL analysis happens at server startup inside .build().

HTTP request
    │
    ▼
[envelope]   parse_envelope — detect SOAP version, extract header children + body element
    │
    ▼
[dispatch]   DispatchTable::lookup — route body first-child QName to a DispatchEntry
    │
    ▼
[dispatch]   validate_request — XSD structural check against TypeRegistry
    │
    ▼
[server]     WS-Security check (if auth enabled and operation not in bypass set)
    │
    ▼
[handler]    SoapHandler::handle_with_headers — your application logic
    │
    ▼
[envelope]   serialize_envelope — wrap response bytes in a SOAP envelope
    │
    ▼
HTTP response

Modules

dispatch

Builds and holds the DispatchTable — a pair of HashMap keyed on body-element QName (primary) and SOAPAction string (fallback). The table is built once from a ResolvedWsdl during .build() and never mutated per-request, so lookups are O(1) and lock-free.

Each DispatchEntry carries the Arc<dyn SoapHandler>, an auth_required flag, the input_type QName used for routing, and a validation_type QName used for XSD structural validation. Both may be None for operations with empty or omitted input elements.

The build step rejects any operation name that was registered via .handler() but does not appear in the WSDL — this is the mechanism that makes unregistered-operation detection a build-time error rather than a runtime panic.

server

ServerBuilder and SoapService live here. ServerBuilder accumulates the WSDL source, handler map, auth closure, bypass set, and other configuration. .build() resolves the WSDL (including any imported XSDs), constructs the DispatchTable and TypeRegistry, and returns a SoapService.

SoapService::into_router() mounts the service’s HTTP handler at the URL derived from the WSDL <service><port address> element and returns a plain axum::Router. Merging multiple such routers is how multi-WSDL / multi-service deployments are composed:

#![allow(unused)]
fn main() {
use soap_server::ServerBuilder;
async fn example() -> Result<(), Box<dyn std::error::Error>> {
let svc_a = ServerBuilder::from_wsdl_file("a.wsdl").build()?.into_router();
let svc_b = ServerBuilder::from_wsdl_file("b.wsdl").build()?.into_router();

// Each router mounts at its own path; merge them into one axum app.
let app = svc_a.merge(svc_b);
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?;
axum::serve(listener, app).await?;
Ok(())
}
}

handler

Defines the SoapHandler trait and the FnHandler convenience wrapper.

SoapHandler has two methods:

  • handle(body: Bytes) -> Result<Bytes, SoapFault> — receives the body element fragment, returns response XML bytes.
  • handle_with_headers(body: Bytes, headers: &[Bytes]) -> Result<Bytes, SoapFault> — default implementation calls handle, ignoring header fragments. Override this to inspect WS-Addressing or other header-level protocols.

FnHandler<F> wraps any Fn(Bytes) -> impl Future<Output = Result<Bytes, SoapFault>> into a SoapHandler.

fault

SoapFault carries a FaultCode, a human-readable reason string, an optional plain-text detail string, and an optional detail_xml field for pre-formed XML detail content.

FaultCode variants map to both SOAP 1.1 and 1.2 fault code strings:

FaultCode variantSOAP 1.2SOAP 1.1
VersionMismatchenv:VersionMismatchenv:VersionMismatch
MustUnderstandenv:MustUnderstandenv:MustUnderstand
DataEncodingUnknownenv:DataEncodingUnknownenv:Server
Senderenv:Senderenv:Client
Receiverenv:Receiverenv:Server

The detail_xml field takes precedence over detail when both are set, and its content is emitted verbatim (not escaped) into the fault detail element.

envelope

parse_envelope and serialize_envelope handle the SOAP envelope layer for both protocol versions. detect_soap_version infers the version from the Content-Type header (text/xml → SOAP 1.1, application/soap+xml → SOAP 1.2). The parsed ParsedEnvelope exposes the SOAP version, a Vec<Bytes> of header child fragments, and the body first-child element as self-contained XML bytes (with ancestor namespace declarations re-emitted on the fragment root).

qname

QName is a lightweight (Option<String>, String) tuple representing an XML qualified name. It is the key type for DispatchTable’s element-based lookup map. QName::new creates a namespaced name; QName::local creates a no-namespace name.

xml_escape

escape_text and escape_attr are thin wrappers over quick_xml::escape::escape for escaping all five XML special characters. They are separate functions to communicate intent at call sites and to allow future divergence (e.g. skipping " in text-only contexts). Both functions are exported at the crate root.