soap-errorscontent-typeauthproxy

Content Type text/html; charset=utf-8 was not supported by service の正体

SOAPless Team8 min read

このエラーは SOAP のパース失敗に見えますが、実際にはクライアントが SOAP レスポンスを一切受け取っていないことを意味します。クライアントと SOAP サービスの間にある何かが、XML ではなく HTML を返しています。

Content Type text/html; charset=utf-8 was not supported by service

SOAP クライアントは text/xml または application/soap+xml を期待していましたが、返ってきたのは text/html です。ログインページ、エラーページ、ゲートウェイの警告画面、WAF のブロックページなどです。クライアントライブラリは HTML を SOAP Envelope として解析できないため、この Content-Type エラーを投げます。

原因別診断フロー

エンタープライズ環境で多い順に確認してください。

原因 1: エンドポイント URL の誤り

最も基本的な原因です。WSDL URL やドキュメントページに SOAP リクエストを送っているケースです。

誤り:  https://example.com/service.svc?wsdl
誤り:  https://example.com/services/
正解:  https://example.com/service.svc

正しい URL は WSDL から確認できます。

<wsdl:service name="UserService">
  <wsdl:port name="UserServiceSoap" binding="tns:UserServiceSoap">
    <soap:address location="https://example.com/service.svc" />
  </wsdl:port>
</wsdl:service>

<soap:address>location 属性が実際のサービスエンドポイントです。

原因 2: ログインページへの HTTP リダイレクト

エンタープライズ SSO ゲートウェイ(ADFS、Okta、Azure AD)の背後にある場合に非常に多いパターンです。SOAP クライアントが 302 リダイレクトに従い、ログインページの HTML を受け取ります。

# リダイレクトの確認
curl -v -o /dev/null https://example.com/service.svc 2>&1 | grep -E "< HTTP|< Location"

以下のような出力が出たら:

< HTTP/1.1 302 Found
< Location: https://sso.example.com/login?returnUrl=...

SOAP サービスが認証ゲートウェイの背後にあります。対処方法は以下のいずれかです。

  • ゲートウェイを通過する認証情報を提供する(Basic Auth、クライアント証明書、Bearer トークン)
  • ゲートウェイ設定でサービスエンドポイントをホワイトリストに追加する
  • サービス間通信用に SSO をバイパスする直接 URL を使用する

SSO ログインページの HTML 例:

<!DOCTYPE html>
<html>
<head><title>Sign In - Corporate SSO</title></head>
<body>
  <form action="/auth/login" method="POST">
    <input type="text" name="username" />
    <input type="password" name="password" />
    <button type="submit">Sign In</button>
  </form>
</body>
</html>

SOAP クライアントはこれを XML として解析しようとして Content-Type エラーを投げます。

原因 3: リバースプロキシやロードバランサーが HTML を返している

上流の SOAP サービスがダウンすると、プロキシ層が独自の HTML エラーページを返します。プロキシの種類によって HTML が異なります。

nginx (502 Bad Gateway):

<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.24.0</center>
</body>
</html>

F5 BIG-IP (接続エラー):

<html>
<head><title>BIG-IP Maintenance</title></head>
<body>The server is temporarily unable to service your request.</body>
</html>

AWS ALB (503 Service Unavailable):

<html>
<body><h1>503 Service Temporarily Unavailable</h1></body>
</html>

いずれの場合も、本当の問題は上流の可用性であり、SOAP リクエスト自体ではありません。

原因 4: WAF や API ゲートウェイがリクエストをブロック

WAF(Web Application Firewall)や API ゲートウェイが疑わしい SOAP リクエストをブロックし、HTML のブロックページを返すことがあります。

WAF ブロックの兆候:

  • HTML body 付きの HTTP 403
  • "Request blocked" や "Access denied" のページ
  • CAPTCHA チャレンジページ
  • Cloudflare や Akamai のチャレンジページ
# WAF が介入していないか確認
curl -s -D - \
  -H "Content-Type: text/xml; charset=utf-8" \
  -H 'SOAPAction: "http://tempuri.org/GetUser"' \
  -d @request.xml \
  https://example.com/service.svc | head -20

Server: cloudflareX-WAF-* ヘッダー、セキュリティベンダー名を含む HTML body がないか確認してください。

各言語でのデバッグ方法

curl (ベースライン検証)

クライアントライブラリの挙動とネットワーク問題を切り分けるため、まず curl で確認します。

curl -v \
  -H "Content-Type: text/xml; charset=utf-8" \
  -H 'SOAPAction: "http://tempuri.org/GetUser"' \
  -d '<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUser xmlns="http://tempuri.org/">
      <userId>1</userId>
    </GetUser>
  </soap:Body>
</soap:Envelope>' \
  https://example.com/service.svc

-v フラグで HTTP の全会話が見えます。リダイレクト、レスポンスヘッダー、body の最初の数行を確認してください。Content-Type が text/html なら、HTML body を読んで原因を特定します。

Python (requests)

import requests

url = "https://example.com/service.svc"
headers = {
    "Content-Type": "text/xml; charset=utf-8",
    "SOAPAction": '"http://tempuri.org/GetUser"',
}
body = """<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUser xmlns="http://tempuri.org/">
      <userId>1</userId>
    </GetUser>
  </soap:Body>
</soap:Envelope>"""

# リダイレクト追従を無効化して認証リダイレクトを検出
response = requests.post(url, data=body, headers=headers, allow_redirects=False)

print(f"Status: {response.status_code}")
print(f"Content-Type: {response.headers.get('Content-Type')}")
print(f"Location: {response.headers.get('Location', 'N/A')}")

if "text/html" in response.headers.get("Content-Type", ""):
    print("ERROR: XML ではなく HTML を受信")
    print(f"Body preview: {response.text[:500]}")

ポイント: allow_redirects=False を指定すると、クライアントがリダイレクトに従う前にリダイレクト先を確認できます。

Node.js (axios)

const axios = require("axios");

const url = "https://example.com/service.svc";
const soapBody = `<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUser xmlns="http://tempuri.org/">
      <userId>1</userId>
    </GetUser>
  </soap:Body>
</soap:Envelope>`;

axios
  .post(url, soapBody, {
    headers: {
      "Content-Type": "text/xml; charset=utf-8",
      SOAPAction: '"http://tempuri.org/GetUser"',
    },
    maxRedirects: 0, // リダイレクトを追従しない
    validateStatus: () => true, // すべてのステータスコードを受け入れる
  })
  .then((res) => {
    console.log(`Status: ${res.status}`);
    console.log(`Content-Type: ${res.headers["content-type"]}`);

    if (res.headers["content-type"]?.includes("text/html")) {
      console.log("ERROR: XML ではなく HTML を受信");
      console.log(`Body preview: ${res.data.substring(0, 500)}`);
    }
  });

ポイント: maxRedirects: 0 でリダイレクトの自動追従を防ぎます。

title タグによるショートカット

HTML レスポンスが返ってきた場合、<title> タグだけで原因が特定できることが多いです。

title タグの内容原因の可能性対処
"Sign In" / "Login"SSO リダイレクト認証情報の修正またはバイパス
"Access Denied" / "403 Forbidden"WAF または認可IP ホワイトリストや認証情報を確認
"404 Not Found"URL の誤りWSDL の <soap:address> を確認
"502 Bad Gateway"上流がダウンサービスの正常性を確認
"503 Service Unavailable"上流が過負荷待ってからリトライ
"Request Blocked"WAF ルールに抵触WAF ログを確認

SOAP クライアントの例外を解析するよりはるかに早いです。

SOAPless でこの問題を根本的に回避する

SOAPless では、SOAP リクエストは SOAPless のインフラからサーバーサイドで実行されます。これにより:

  • リクエストは呼び出し側の企業プロキシ、SSO ゲートウェイ、WAF を通過しない
  • SOAPless がダッシュボードに登録された SOAP サービスエンドポイントに直接接続
  • 上流が XML ではなく HTML を返した場合、SOAPless が Content-Type の不一致を検出し、何が起きたかを説明する明確な JSON エラーを返す
  • 下流の consumer は REST JSON エンドポイントを呼ぶだけで、text/html レスポンスに遭遇することがない

「自分と SOAP サービスの間にある何かが HTML を返した」という問題のクラス全体が、SOAPless がリクエスト経路から中間レイヤーを除去することで解消されます。