SOAP リクエストに対する HTTP 500 レスポンスは、最もデバッグが困難なエラーの一つです。400 や 401 とは異なり、何が間違っているかを具体的に教えてくれません。しかし SOAP の世界では、レスポンスボディにほぼ必ず SOAP Fault が含まれており、正しい読み方を知っていれば原因を特定できます。
この記事では、HTTP 500 エラーと SOAP Fault の重要な違いを解説し、レスポンスから真のエラーを抽出する方法、そして 6 つの一般的な原因とその解決策を紹介します。
HTTP 500 と SOAP Fault の重要な違い
SOAP サービスがリクエスト処理中にエラーを検出した場合、エラーの伝達方法は複数あります。
パターン A — HTTP 200 の中に SOAP Fault:
一部の SOAP サービスは、操作が失敗しても HTTP 200 (成功) を返し、エラーの詳細をレスポンスボディの SOAP Fault 要素に格納します。多くの SOAP 1.1 実装がこの動作をします。
パターン B — HTTP 500 の中に SOAP Fault:
SOAP 仕様に沿ったより正しい動作です。サーバーは HTTP 500 を返し、レスポンスボディに SOAP Fault エンベロープを含めます。
HTTP/1.1 500 Internal Server Error
Content-Type: text/xml; charset=utf-8
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:Server</faultcode>
<faultstring>Object reference not set to an instance of an object.</faultstring>
<detail>
<ExceptionDetail>
<Message>Object reference not set to an instance of an object.</Message>
<StackTrace>at MyService.GetOrder(Int32 orderId) in ...</StackTrace>
</ExceptionDetail>
</detail>
</soap:Fault>
</soap:Body>
</soap:Envelope>
パターン C — SOAP ボディなしの HTTP 500:
サーバーが HTTP 500 を返し、HTML エラーページまたは空のボディが返ってくるケースです。これは SOAP 処理パイプラインの前段で障害が発生したことを意味し、Web サーバーの設定エラー、アプリケーションクラッシュ、ミドルウェアの障害が原因です。
デバッグの最初のステップは常に レスポンスボディを読む ことです。
Step 1: レスポンス全体をキャプチャする
多くの HTTP クライアントや SOAP ライブラリは、500 ステータスコードを受信するとレスポンスボディを破棄します。明示的にキャプチャする必要があります。
curl を使用する場合:
curl -v -X POST \
-H "Content-Type: text/xml; charset=utf-8" \
-H "SOAPAction: http://example.com/IOrderService/GetOrder" \
-d @request.xml \
http://example.com/OrderService.svc \
2>&1 | tee response.txt
-v フラグでヘッダーを表示し、tee でレスポンスボディを含む完全な出力を保存します。ボディに <soap:Fault> が含まれているか、HTML エラーページかを確認してください。
Python (requests) を使用する場合:
import requests
headers = {
"Content-Type": "text/xml; charset=utf-8",
"SOAPAction": "http://example.com/IOrderService/GetOrder"
}
with open("request.xml", "r") as f:
body = f.read()
response = requests.post(
"http://example.com/OrderService.svc",
headers=headers,
data=body
)
print(f"Status: {response.status_code}")
print(f"Headers: {dict(response.headers)}")
print(f"Body:\n{response.text}")
500 ステータスコードであっても response.text にはレスポンスボディ全体が格納されており、SOAP Fault も含まれています。
Step 2: SOAP Fault を解析する
レスポンスに SOAP Fault が含まれている場合、以下のフィールドを抽出します。
SOAP 1.1 の Fault 構造
<soap:Fault>
<faultcode>soap:Server</faultcode> <!-- 責任の所在 -->
<faultstring>人間が読めるエラーメッセージ</faultstring>
<faultactor>http://example.com/svc</faultactor> <!-- 任意: どのノードか -->
<detail> <!-- 任意: 構造化された詳細情報 -->
<ErrorCode>ORD-4012</ErrorCode>
<ErrorDescription>Order not found</ErrorDescription>
</detail>
</soap:Fault>
SOAP 1.2 の Fault 構造
<soap:Fault>
<soap:Code>
<soap:Value>soap:Receiver</soap:Value>
<soap:Subcode>
<soap:Value>app:DatabaseTimeout</soap:Value>
</soap:Subcode>
</soap:Code>
<soap:Reason>
<soap:Text xml:lang="en">Database connection timed out</soap:Text>
</soap:Reason>
<soap:Detail>
<app:ErrorInfo>
<app:RetryAfter>30</app:RetryAfter>
</app:ErrorInfo>
</soap:Detail>
</soap:Fault>
Fault code はどちら側に責任があるかを示します。
| SOAP 1.1 | SOAP 1.2 | 意味 |
|---|---|---|
soap:Client | soap:Sender | リクエストに問題がある |
soap:Server | soap:Receiver | サーバーが有効なリクエストの処理に失敗した |
Fault code が Server であっても、クライアント側の問題を除外しないでください。多くのサーバーがすべてのエラーを誤って Server Fault として分類します。
一般的な原因 1: リクエスト XML の形式不正
SOAP 500 エラーの最も頻繁な原因は、WSDL コントラクトに一致しないリクエストです。サーバーの XML デシリアライザが失敗し、例外が 500 として返されます。
WSDL に対してリクエストを検証します:
# WSDL をダウンロード
curl -o service.wsdl "http://example.com/OrderService.svc?wsdl"
# 期待されるリクエスト構造を確認
# オペレーションの input メッセージとその要素定義を参照
よくある XML の問題:
- 名前空間の不一致。 オペレーション要素は WSDL の
targetNamespaceで定義された正確な名前空間を使用する必要があります。 - 必須要素の欠落。 REST API とは異なり、SOAP サービスはフィールドの省略を許容しないことがほとんどです。
- 要素の順序が不正。 XML Schema の sequence は要素の順序を強制します。
<City>が<State>の前に来る必要がある場合、入れ替えはできません。 - 型の不一致。 整数を期待するフィールドに文字列を送信するとデシリアライゼーションが失敗します。
名前空間の不一致による 500 エラーの例:
<!-- 誤り: 不正な名前空間を使用 -->
<GetOrder xmlns="http://wrong.namespace.com/">
<orderId>123</orderId>
</GetOrder>
<!-- 正解: WSDL の targetNamespace に一致 -->
<GetOrder xmlns="http://example.com/orders/2024">
<orderId>123</orderId>
</GetOrder>
一般的な原因 2: データベースまたはバックエンドシステムの障害
SOAP サービスが接続するデータベースや下流システムが利用不可になっているケースです。未処理の例外が汎用的な 500 エラーとして伝播します。
SOAP Fault からの手がかり:
faultstringに「connection」「timeout」「deadlock」、またはSqlExceptionのようなデータベース固有のエラーが含まれている- エラーが断続的で、動作する場合と失敗する場合がある
- リクエストを変更していないのにエラーが突然発生し始めた
クライアント側で修正できることはありません。サービス管理者に正確なタイムスタンプと Fault の詳細を提供してください。ヘルスチェックエンドポイントがある場合はポーリングできます。
# WCF サービスは操作が失敗してもメタデータ URL は応答することがある
curl -s "http://example.com/OrderService.svc?wsdl" -o /dev/null -w "%{http_code}"
WSDL エンドポイントが 200 を返すが操作が 500 を返す場合、サービスは稼働中だがバックエンドに問題があります。
一般的な原因 3: 認証・認可の失敗
一部の SOAP サービスは認証失敗時に 401/403 ではなく 500 を返します。これは特に WS-Security の実装で、セキュリティ処理が HTTP レベルではなく SOAP パイプライン内で行われる場合に発生します。
セキュリティヘッダーを確認してください:
<soap:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken>
<wsse:Username>myuser</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">mypassword</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
認証情報の有効期限切れ、ユーザー名の正確性、パスワード種別 (PasswordText と PasswordDigest) がサーバーの期待する形式と一致しているかを確認してください。
一般的な原因 4: SOAPAction ヘッダーの不一致
SOAPAction HTTP ヘッダーは WSDL で定義されたアクションと正確に一致する必要があります。不正なアクションはサーバーにリクエストを拒否させるか、誤ったオペレーションハンドラーにルーティングさせ、500 で失敗します。
# WSDL が期待する SOAPAction を確認する
# WSDL 内の <soap:operation soapAction="..." /> を参照
curl -X POST \
-H "Content-Type: text/xml; charset=utf-8" \
-H "SOAPAction: \"http://example.com/IOrderService/GetOrder\"" \
-d @request.xml \
http://example.com/OrderService.svc
ヘッダー値内のダブルクォートに注意してください。SOAP 仕様では SOAPAction の値は引用符で囲まれた文字列である必要があります。
一般的な原因 5: サーバー側の未処理例外
サービスコードがエラーハンドラーでキャッチされない例外をスローした場合、WCF ランタイムが汎用的な SOAP Fault にラップします。本番環境ではセキュリティ上の理由から例外の詳細が非表示にされ、以下のようなメッセージのみが返されます。
<faultstring>
The server was unable to process the request due to an internal error.
For more information about the error, either turn on
IncludeExceptionDetailInFaults on the server or use tracing.
</faultstring>
サーバーにアクセスできる場合は、一時的に詳細なフォルト情報を有効にします。
<serviceBehaviors>
<behavior>
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
本番環境でこの設定を有効にしたまま放置しないでください。 スタックトレースや内部パスがクライアントに漏洩します。
一般的な原因 6: メッセージサイズまたはタイムアウトの制限
サーバーが大きなリクエストを拒否したり、処理中にタイムアウトしたりしている場合があります。サーバーのエラーハンドリングがより具体的なステータスコードを生成しない場合、500 として返されます。
サーバー側の確認:
<binding name="LargeBinding"
maxReceivedMessageSize="10485760"
receiveTimeout="00:10:00"
sendTimeout="00:10:00">
<readerQuotas maxStringContentLength="2147483647" />
</binding>
クライアント側でタイミングを監視できます。
curl -X POST \
-H "Content-Type: text/xml; charset=utf-8" \
-H "SOAPAction: http://example.com/IOrderService/GetOrder" \
-d @request.xml \
-w "\n\nTime: %{time_total}s\n" \
http://example.com/OrderService.svc
リクエストが一貫して 30 秒や 60 秒付近で失敗する場合、原因はほぼ確実にタイムアウトです。
デバッグチェックリスト
SOAP サービスから HTTP 500 を受信した場合、以下のリストに沿って確認してください。
- レスポンスボディ全体をキャプチャする — SOAP Fault、HTML ページ、空のどれか?
- SOAP Fault がある場合:
faultcode、faultstring、detailを注意深く読む - リクエスト XML を WSDL スキーマに対して検証する
SOAPActionヘッダーが WSDL と一致しているか確認する- 認証情報とトークンの有効性を確認する
- エラーが断続的 (バックエンド障害) か一貫して発生するか (リクエスト問題) を判別する
- SoapUI や curl で全く同じリクエストを試してクライアントライブラリのバグを除外する
- アクセス可能であればサーバー側のログを確認する
SOAPless による解決
SOAP サービスからの HTTP 500 エラーのデバッグには、SOAP Fault 構造、XML 名前空間、WS-Security ヘッダー、SOAPAction ルーティングの理解が必要で、これらをすべて把握してようやく根本原因の特定に取りかかれます。
SOAPless はこれらの複雑さをすべて処理します。WSDL を登録すると、SOAPless がサービスコントラクトを解析し、正しいリクエスト構造を自動生成し、SOAP Fault を明確な JSON エラーレスポンスに変換します。上流サービスが 500 を返した場合、ダッシュボード上で Fault code、メッセージ、詳細が構造化された形で表示されます。XML の解析は不要です。
ダッシュボードからは任意のオペレーションを直接テストでき、認証は AES-256-GCM 暗号化で安全に管理されます。問題が発生した際に、リクエスト側の問題か上流サービスの問題かを即座に判別できます。SOAPless を使い始めて、生の SOAP Fault のデバッグから解放されましょう。