Formatador de requisições SOAP
Formata e confere envelopes SOAP. Credenciais ficam na sua aba.
Tudo roda nesta aba. Nada do que você colar é enviado, registrado ou transmitido para lugar nenhum. Abra o painel de rede e confira.
Cole acima uma requisição ou resposta SOAP e ela é indentada conforme você digita, com os prefixos de espaço de nomes esmaecidos para que soap:Body se leia como Body com uma marca ao lado, e todo problema de boa formação relatado com a sua linha, a sua coluna e o que escrever no lugar. Ponha o controle de atributos em três e o Envelope deixa de ser uma linha de 300 caracteres: cada declaração xmlns ganha a própria linha.
O envelope que você está colando quase certamente saiu de um log: uma captura do Fiddler, uma saída de __getLastRequest() do SoapClient do PHP, um rastreamento de mensagens do WCF, uma aba raw do SoapUI, ou uma linha escrita às 3 da manhã por um logger que não quebra linhas. Nesse estado ele é ilegível, e você precisa dele legível antes de poder dizer se a culpa é sua ou deles.
Nada é enviado, e aqui isso é o ponto, não um diferencial. Cargas SOAP carregam tokens de WS-Security com senhas dentro, asserções SAML assinadas, números de conta e prontuários de pacientes. Um dos validadores que aparecem bem nessas buscas pede que você marque uma caixa confirmando que seus dados ficam guardados nos servidores deles; outro site que publica um formatador SOAP salva publicamente os documentos enviados, e o Google os indexou. Aqui o analisador e o formatador são JavaScript nesta aba.
O envelope, e a URI que identifica uma versão
Uma mensagem SOAP é um documento XML com uma forma externa fixa. A raiz é Envelope. Ela pode ter um Header, e se tiver o Header vem primeiro. Ela precisa ter um Body, contendo ou a carga da operação ou um Fault. Tudo abaixo do Body pertence ao serviço.
A versão é identificada pela URI do espaço de nomes, nunca pelo prefixo. O prefixo é arbitrário: soap, soapenv, SOAP-ENV e env estão todos em circulação. Se um servidor responde a uma requisição de aparência válida com um fault VersionMismatch, compare a URI caractere por caractere antes de olhar qualquer outra coisa.
- SOAP 1.1: espaço de nomes http://schemas.xmlsoap.org/soap/envelope/ (a barra final faz parte dele), Content-Type text/xml, e a operação num cabeçalho SOAPAction separado cujo valor precisa vir entre aspas, possivelmente como um par de aspas vazio.
- SOAP 1.2: espaço de nomes http://www.w3.org/2003/05/soap-envelope, Content-Type application/soap+xml com um parâmetro action, e nenhum cabeçalho SOAPAction. Um endpoint 1.2 que recebe um content type de 1.1 normalmente responde HTTP 415, o que faz a falha parecer um problema de transporte.
- O SOAP 1.1 tolerava elementos depois do Body. O SOAP 1.2 não: Header e Body são os únicos filhos de Envelope, e o Body vem por último.
Por que erros de prefixo são a quebra mais comum em SOAP
As declarações de espaço de nomes moram no elemento Envelope, e a parte que lhe interessa está quatro níveis abaixo. Copie o fragmento interessante de um log e você levou os prefixos deixando as declarações para trás. A mensagem então nomeia o prefixo em vez da causa: o libxml2 diz «Namespace prefix soap on Body is not defined», o .NET diz «'soap' is an undeclared prefix». A varredura daqui relata isso junto com a declaração a acrescentar.
O erro inverso é mais silencioso e pior. Colar um fragmento sem prefixo num Body que está sob um espaço de nomes padrão move todo elemento dele para esse espaço: o documento é analisado, o serviço aceita, e os campos voltam vazios. Ponha xmlns="" na raiz do fragmento colado para ficar de fora.
Uma regra pega até gente experiente: um espaço de nomes padrão se aplica a nomes de elemento, nunca a nomes de atributo. É por isso que mustUnderstand, actor e role precisam carregar o prefixo do envelope mesmo quando o espaço de nomes do envelope é o padrão.
mustUnderstand, e cabeçalhos que falham antes de a sua carga ser lida
Um bloco de cabeçalho marcado com mustUnderstand é um contrato: um receptor que desempenhe o papel visado precisa ou entender o bloco ou rejeitar a mensagem inteira com um fault MustUnderstand, sem processar mais nada. É por isso que uma requisição com um Body perfeitamente correto é recusada; o serviço nunca chegou ao Body.
O valor difere por versão, e confundi-los é uma falha silenciosa, não um erro. O SOAP 1.1 define os caracteres «1» ou «0», com «0» por padrão; o SOAP 1.2 o tipa como xs:boolean, então «true» e «false» também funcionam. Envie mustUnderstand="true" a uma pilha 1.1 rigorosa e o atributo é lido como ausente, tornando opcional um cabeçalho obrigatório. O direcionamento é a outra metade: o 1.1 usa actor com uma URI, enquanto o 1.2 o renomeia para role e define role/none, role/next e role/ultimateReceiver, este último sendo o padrão. A maioria das falhas nessa camada é um cabeçalho WS-Security marcado com mustUnderstand contra um servidor sem política de segurança configurada para aquela operação.
Ler um soap:Fault
Um Fault é um elemento comum dentro do Body, e quando está presente precisa ser o único filho do Body. A estrutura mudou completamente entre as versões, e é por isso que um tratamento de faults escrito para uma versão silenciosamente não casa com nada na outra.
No SOAP 1.1 os filhos de Fault não são qualificados: faultcode, faultstring, faultactor e detail ficam em nenhum espaço de nomes, mesmo estando o Fault no espaço do envelope, então //soap:Fault/soap:faultstring não devolve nada e tem de ser //soap:Fault/faultstring. O faultcode guarda um QName, normalmente soap:Client (sua mensagem estava errada) ou soap:Server (o lado deles falhou, uma nova tentativa pode funcionar).
O SOAP 1.2 qualifica e renomeia tudo: Code guarda um Value de uma lista fixa (Sender, Receiver, VersionMismatch, MustUnderstand, DataEncodingUnknown) com uma cadeia opcional de Subcode, Reason guarda elementos Text que exigem cada um xml:lang, e Node, Role e Detail substituem o resto. O status carrega informação também: o 1.1 devolve 500 para todo fault, o 1.2 devolve 400 para Sender e 500 para Receiver.
Enviar e ler SOAP em código
O envelope que você cola aqui normalmente veio de um destes. Cada exemplo envia uma requisição, verifica se há um Fault antes de supor sucesso, e analisa a resposta com segurança, já que é XML de uma parte remota e os padrões em Java, PHP e Python resolvem entidades externas.
// SOAP 1.1 over fetch. Note SOAPAction: it is a separate header and its
// value must be quoted, even when it is empty.
const envelope = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"',
' xmlns:ns="urn:example:orders">',
' <soap:Body>',
' <ns:GetOrder><ns:id>ORD-4471</ns:id></ns:GetOrder>',
' </soap:Body>',
'</soap:Envelope>',
].join('\n');
const response = await fetch('https://example.com/orders', {
method: 'POST',
headers: {
'Content-Type': 'text/xml; charset=utf-8',
SOAPAction: '"urn:example:orders/GetOrder"',
// SOAP 1.2 instead: no SOAPAction header, and
// 'Content-Type': 'application/soap+xml; charset=utf-8; action="urn:example:orders/GetOrder"'
},
body: envelope,
});
// A fault arrives with HTTP 500 in SOAP 1.1, so response.ok is false and the
// body still holds the answer. Never throw on the status alone.
const text = await response.text();
const doc = new DOMParser().parseFromString(text, 'application/xml');
const SOAP11 = 'http://schemas.xmlsoap.org/soap/envelope/';
const fault = doc.getElementsByTagNameNS(SOAP11, 'Fault')[0];
if (fault) {
// faultcode and faultstring are unqualified, even inside a qualified Fault.
const code = fault.getElementsByTagName('faultcode')[0]?.textContent;
const reason = fault.getElementsByTagName('faultstring')[0]?.textContent;
throw new Error(code + ': ' + reason);
}import requests
from defusedxml.ElementTree import fromstring # never the stdlib parser here
SOAP11 = 'http://schemas.xmlsoap.org/soap/envelope/'
NS = {'soap': SOAP11, 'ns': 'urn:example:orders'}
envelope = """<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns="urn:example:orders">
<soap:Body>
<ns:GetOrder><ns:id>ORD-4471</ns:id></ns:GetOrder>
</soap:Body>
</soap:Envelope>"""
response = requests.post(
'https://example.com/orders',
data=envelope.encode('utf-8'),
headers={
'Content-Type': 'text/xml; charset=utf-8',
'SOAPAction': '"urn:example:orders/GetOrder"',
},
timeout=30,
)
# Do not call raise_for_status(): a SOAP 1.1 fault is HTTP 500 and the body
# is the part you need.
root = fromstring(response.content)
fault = root.find('.//soap:Fault', NS)
if fault is not None:
code = fault.findtext('faultcode') # unqualified in SOAP 1.1
reason = fault.findtext('faultstring')
raise RuntimeError(f'{code}: {reason}')
# For a real client, zeep reads the WSDL and builds the envelope for you.
# This shape is for debugging one call, which is when you end up here.import jakarta.xml.soap.*; // javax.xml.soap before Jakarta EE 9
import java.io.ByteArrayOutputStream;
// SOAPConstants.SOAP_1_2_PROTOCOL for a 1.2 endpoint. The choice sets both
// the envelope namespace and the content type, so it is the one line that
// decides which version you are speaking.
MessageFactory factory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL);
SOAPMessage message = factory.createMessage();
SOAPEnvelope envelope = message.getSOAPPart().getEnvelope();
envelope.addNamespaceDeclaration("ns", "urn:example:orders");
SOAPBody body = envelope.getBody();
SOAPElement call = body.addChildElement("GetOrder", "ns");
call.addChildElement("id", "ns").addTextNode("ORD-4471");
// SOAPAction, quoted, as a MIME header. SOAP 1.2 does not use it.
message.getMimeHeaders().addHeader("SOAPAction", "\"urn:example:orders/GetOrder\"");
message.saveChanges();
// The raw bytes on the wire: this is what you paste into a formatter.
ByteArrayOutputStream sent = new ByteArrayOutputStream();
message.writeTo(sent);
System.out.println(sent.toString("UTF-8"));
SOAPConnection connection = SOAPConnectionFactory.newInstance().createConnection();
SOAPMessage response = connection.call(message, "https://example.com/orders");
if (response.getSOAPBody().hasFault()) {
SOAPFault fault = response.getSOAPBody().getFault();
throw new RuntimeException(
fault.getFaultCode() + ": " + fault.getFaultString());
}using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Xml;
using System.Xml.Linq;
const string Soap11 = "http://schemas.xmlsoap.org/soap/envelope/";
XNamespace soap = Soap11;
XNamespace ns = "urn:example:orders";
var envelope = new XDocument(
new XElement(soap + "Envelope",
new XAttribute(XNamespace.Xmlns + "soap", Soap11),
new XElement(soap + "Body",
new XElement(ns + "GetOrder",
new XElement(ns + "id", "ORD-4471")))));
using var http = new HttpClient();
var content = new StringContent(envelope.ToString(), Encoding.UTF8);
content.Headers.ContentType = new MediaTypeHeaderValue("text/xml")
{
CharSet = "utf-8",
};
// SOAP 1.2 instead: media type application/soap+xml with an action parameter,
// and no SOAPAction header.
content.Headers.Add("SOAPAction", "\"urn:example:orders/GetOrder\"");
var response = await http.PostAsync("https://example.com/orders", content);
var body = await response.Content.ReadAsStringAsync();
// A fault is HTTP 500 with a real body, so do not call
// EnsureSuccessStatusCode() before you have looked at it.
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null,
};
using var reader = XmlReader.Create(new StringReader(body), settings);
var doc = XDocument.Load(reader);
var fault = doc.Descendants(soap + "Fault").FirstOrDefault();
if (fault is not null)
{
// Unqualified children in SOAP 1.1: no namespace on the element name.
var code = fault.Element("faultcode")?.Value;
var reason = fault.Element("faultstring")?.Value;
throw new InvalidOperationException(code + ": " + reason);
}<?php
// trace => true is why this snippet exists: it is how you get the raw
// envelope to paste into a formatter and see what was actually sent.
$client = new SoapClient('https://example.com/orders?wsdl', [
'trace' => true,
'exceptions' => true,
'soap_version' => SOAP_1_1, // SOAP_1_2 changes the namespace and the
// content type together
'cache_wsdl' => WSDL_CACHE_NONE,
'stream_context' => stream_context_create([
'ssl' => ['verify_peer' => true, 'verify_peer_name' => true],
]),
]);
try {
$result = $client->GetOrder(['id' => 'ORD-4471']);
} catch (SoapFault $e) {
// faultcode is the QName from the envelope, e.g. "soap:Client".
fprintf(STDERR, "%s: %s\n", $e->faultcode, $e->getMessage());
} finally {
// Both are null unless trace was enabled before the call.
echo $client->__getLastRequest(), "\n";
echo $client->__getLastResponse(), "\n";
}# Capture a request and a response you can actually read. The SOAPAction
# value keeps its own quotes inside the header value.
curl -sS -D headers.txt \
-H 'Content-Type: text/xml; charset=utf-8' \
-H 'SOAPAction: "urn:example:orders/GetOrder"' \
--data-binary @request.xml \
https://example.com/orders \
| tee response.xml | xmllint --format --nonet -
# SOAP 1.2: no SOAPAction header, the action rides on the content type.
curl -sS \
-H 'Content-Type: application/soap+xml; charset=utf-8; action="urn:example:orders/GetOrder"' \
--data-binary @request.xml \
https://example.com/orders | xmllint --format --nonet -
# Was it a fault? Binding a namespace to xmllint --xpath is awkward, so match
# on the local name:
xmllint --nonet --xpath 'count(//*[local-name()="Fault"])' response.xml
# curl exits 0 on HTTP 500. Check the status line yourself:
head -1 headers.txtO erro recorrente em todos os seis é tratar um HTTP 500 como falha de transporte. Um fault de SOAP 1.1 é entregue com status 500 e um envelope completo no corpo, então raise_for_status(), EnsureSuccessStatusCode() e uma checagem crua de response.ok jogam fora a única descrição do que deu errado que você vai receber.
Perguntas frequentes
Meu envelope contém uma senha e um registro de cliente. Ele é enviado?
Não. O analisador e o formatador são JavaScript rodando nesta aba, num Web Worker. Não há endpoint para onde mandar nada, nem analytics com acesso ao editor, nem scripts de terceiros.
Verifique em vez de acreditar: abra a aba Rede, cole o envelope e formate. A página carrega os próprios recursos uma vez e depois silencia. Essa verificação importa mais aqui do que em qualquer outro lugar deste site, porque um cabeçalho WS-Security carrega um UsernameToken com um digest de senha ou, em muitos serviços internos, a própria senha. Sua entrada fica no localStorage deste navegador até você apagá-la.
Qual é a diferença entre SOAP 1.1 e SOAP 1.2?
Comece pela URI do espaço de nomes, porque todo o resto decorre dela: o 1.1 é http://schemas.xmlsoap.org/soap/envelope/ e o 1.2 é http://www.w3.org/2003/05/soap-envelope. O prefixo não lhe diz nada.
No fio, o 1.1 usa text/xml com um cabeçalho SOAPAction separado e entre aspas, e o 1.2 usa application/soap+xml com um parâmetro action e sem SOAPAction. Dentro da mensagem, o 1.2 reescreveu o Fault e qualificou cada parte dele, tipou mustUnderstand como booleano, renomeou actor para role, e proibiu elementos de aplicação depois do Body. A maioria dos serviços em produção continua no 1.1.
Por que eu recebo sempre erros de «prefixo não declarado»?
Porque as declarações xmlns moram no elemento Envelope e você copiou algo abaixo dele. Um prefixo só tem significado enquanto uma declaração que o vincule estiver no escopo, então um soap:Body colado sozinho não é nem XML bem formado, que dirá SOAP válido.
Acrescente o vínculo à raiz do que você colou: xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" para um fragmento 1.1. O erro inverso não levanta erro nenhum, então fique atento: um fragmento sem prefixo colado num Body sob um espaço de nomes padrão é silenciosamente movido para esse espaço, e os campos voltam vazios.
Isto valida meu envelope contra o esquema do SOAP?
Não, e fingir o contrário seria o tipo errado de ajuda. Esta página analisa o envelope, relata todo erro de boa formação com linha e coluna, verifica que cada prefixo está vinculado, formata sem tocar nos seus dados, e informa tamanho, número de linhas e número de elementos.
Ela não impõe o modelo de conteúdo do SOAP, então não vai reclamar se o Header vier depois do Body, se o Body faltar, ou se você montou um fault 1.1 dentro de um envelope 1.2. Essas são restrições de esquema: passe o envelope pelo esquema de envelope SOAP, publicado na URI do espaço de nomes, no validador de XSD daqui. Ela também não lê WSDL, não envia requisições e não verifica assinaturas.
De onde eu tiro o envelope cru para colar aqui?
Do cliente, e não do seu código, porque você quer os bytes que foram pelo fio, não o objeto que você entregou a uma biblioteca. Em PHP, construa o SoapClient com trace e chame __getLastRequest(). Em Java com SAAJ, chame message.writeTo(System.out) depois de saveChanges(). Em .NET, habilite o log de mensagens do WCF. Em Python com zeep, acople o HistoryPlugin e leia last_sent.
De fora do processo, curl com --data-binary e -D escreve a resposta e os cabeçalhos em arquivos, Fiddler e mitmproxy capturam tráfego ao vivo, e o SoapUI tem uma aba raw nos dois lados. De qualquer jeito que você o obtenha, ele chega como uma longa linha só, que é exatamente para o que esta página serve.