Closed18

【C#】WebAPIがJSONではなく、HTMLのエラーを返すことがある

motonamotona

バックエンドがC#(.NET Framework 4.8.1)、フロントがReactのアプリを保守している。
WebAPIアプリのC#が、エラー(Exception?)発生時にJSONではなく、HTMLを返すことがあり、困る。
ログ作成処理はどれも正常だった。

表示されるエラーは以下

Unexpected token '<', "<!DOCTYPE "... is not valid JSON

response.json()でこのエラーが起きていることと、responseの中身がHTMLとなっていることは推測している。
通信がなぜHTMLを返すのかが分からない(C#だけではなく、CORS回りも原因の可能性?)

C#, Reactの実装は以下。

C#

System.Web.Http.HttpPostAttribute;

public class DefaultController : ApiController
{
    [HttpGet]
    public IHttpActionResult Get()
    {
        if (IsAuth())
        {
            // エラーログ作成
            return BadRequest("認証してください。");
        }

        // 正常ログ作成
        return Ok(new {
            // いろいろ
        });
    }


    [HttpPost]
    public IHttpActionResult Post(dynamic d)
    {
        if (IsAuth())
        {
            // エラーログ作成
            return BadRequest("認証してください。");
        }

        // 正常ログ作成
        return Ok(new {
            // いろいろ
        });
    }
}

React

function get() {
    fetch("https://localhost:12345/api/Default", {
        mode: 'cors', credentials: 'include',
    })
        .then(response => {
            if (!response.ok) {
                return response.json();
                    .then(e => {
                        throw new Error(e.Message);
                    });
            }
            return response;
        })
        .then(response => response.json())
        .then(json => {
            // いろいろ必要な処理
        })
        .catch(e => {
            console.error(e.message);
        });
}


function post() {
    fetch("https://localhost:12345/api/Default", {
        mode: 'cors', method: 'POST', credentials: 'include',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
        },
    })
        .then(response => {
            if (!response.ok) {
                return response.json();
                    .then(e => {
                        throw new Error(e.Message);
                    });
            }
            return response;
        })
        .then(response => response.json())
        .then(json => {
            // いろいろ必要な処理
        })
        .catch(e => {
            console.error(e.message);
        });
}
motonamotona

とりあえず、Request headerにAcceptを必ず追加するようにすればよい?

motonamotona

実際にUnexpected token '<', "<!DOCTYPE "... is not valid JSONのエラーが出た箇所を確認したが、'Accept': 'application/json'が書いてあったので関係なさそう

motonamotona

C#はreturn Ok();とかreturn BadRequest()の直前でログをDBに作る処理を入れているのだが、そのログをみると、エラーになっているHTTP Requestは問題なさそうなので、エラーの発生個所が

  1. return Ok(object);の箇所
  2. React側の受け取った時
  3. CORS回り
    が候補になる。1はあまり考えられないから2,3の方が可能性高いかな?
motonamotona

アドバイスをもらって、以下を調査することにした

  1. ログを作成してからreturn Ok()するまでに何か特殊なことをしていないか
  2. CORS、プロキシ設定周りで変なリダイレクト入っていないか?
  3. IISのログ確認
  4. 文字コード確認
motonamotona

1. ログを作成してからreturn Ok()するまでに何か特殊なことをしていないか

結果:特筆するようなことはなさそう

HTMLが返る状況は再現できていないが、JSONが返される状況で何の処理が動くかを確認。
POST, PATCHは、return Ok()で返しているデータのモデルのgetが動いていた。
GETは、常にではないがGlobal.asax.csMvcApplication_PostAuthenticateRequest()に入ったりしていた。POSTの直後に実行されるGETで多かった印象。

motonamotona

2. CORS、プロキシ設定周りで変なリダイレクト入っていないか

結果:多分入っていない

CORSの方は、same-originになるから変なことはあまりないだろうし、軽く調べて、CORSのリダイレクトはほぼほぼ動かないという情報を見たので、無いとする

プロキシ設定はよくわからないけど、本当にアクセスしたいところにアクセスする前に通るゲートみたいなもの?という印象。
プログラムの方は"proxy"という単語が全く引っかからなかったので無さそう。
AWSとかIISの設定はよくわからなかったけど、たぶんしてない。

motonamotona

3. IISのログ確認

結果:IISが原因かもしれない

C#で作成したログが200だったのに対し、IISのログが500のものがあった。
IISがなぜか勝手に500エラーとして、用意されていた500.htmlを返している可能性が高い?
理由は不明

motonamotona

4. 文字コード確認

結果:どちらもデフォルトがUTF-8っぽい

JavaScriptはデフォルトがUTF-16だとかUTF-8だとか、いろいろな話が書いてあった
VS Codeでファイルを開いたときはUTF-8だったから、今回はそれがデフォルト?

受け付ける文字コードの指定としてAccept-Charsetがある(あった?)ようだが、標準実装はされていないそう。なんでも通信で使う文字コードはUTF-8にしようという圧倒的支持があるよう?(リンク先のMDN Web Docの先が消えていた)
https://note.com/shinya_matsui/n/n282aedbd3dac


C#はよくわからなかったけど、VS CodeでControllerファイル開いたらUTF-8 with BOMだった。

軽く調べた感じだと、Responseの文字コード指定とかもありそう

https://learn.microsoft.com/ja-jp/dotnet/core/extensions/best-practices-for-developing-world-ready-apps

https://learn.microsoft.com/ja-jp/dotnet/api/system.web.configuration.globalizationsection.responseencoding?view=netframework-4.8.1
↑これを見ると、以下のように書かれている。

HTTP 応答のコンテンツ エンコーディング。 既定は UTF-8 です。

ということで、デフォルトはUTF-8なのでは?
でもMVCで返すHTMLファイルのエンコーディングの話かもしれない
「HTTP応答」にはWebAPIアプリのHTTP requestも含まれるのか?

https://stackoverflow.com/questions/42083263/c-sharp-restful-webapi-post-data-in-arabic-language-in-json-but-receiving
↑ここでWeb.configに文字コードを指定するコードがある。

motonamotona

断続的に発生する場合、負荷がかかりすぎている可能性があるよう

motonamotona

EC2なので、イベントビューアーのログも見てみたら、管理イベントのログにぽいものがあった。原因はMailKit?
見せない方が良さそうなものは削除。

Event code: 3005 
Event message: An unhandled exception has occurred.
Event time: (削除)
Event time (UTC): (削除)
Event ID: (削除)
Event sequence: 570 
Event occurrence: 5 
Event detail code: 0

Application information: 
    Application domain: (削除)
    Trust level: Full
    Application Virtual Path: /
    Application Path: (削除)
    Machine name: (削除)

Process information: 
    Process ID: 5084 
    Process name: (削除)
    Account name: (削除)

Exception information: 
    Exception type: IOException 
    Exception message: An existing connection was forcibly closed by the remote host
   at MailKit.Net.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 count) in (削除)
   at System.Net.FixedSizeReader.ReadPacket(Byte[] buffer, Int32 offset, Int32 count)
   at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult lazyResult)
   at MailKit.Net.Smtp.SmtpClient.<ConnectAsync>d__100.MoveNext() in (削除)

An existing connection was forcibly closed by the remote host
   at MailKit.Net.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 count) in (削除)

 

Request information: 
    Request URL: (削除)
    Request path: (削除)
    User host address: (削除)
    User: (削除)
    Is authenticated: True
    Authentication Type: (削除)
    Thread account name: (削除)

Thread information: 
    Thread ID: 21 
    Thread account name: (削除)
    Is impersonating: False
    Stack trace:    at MailKit.Net.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 count) in (削除)
   at System.Net.FixedSizeReader.ReadPacket(Byte[] buffer, Int32 offset, Int32 count)
   at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult lazyResult)
   at MailKit.Net.Smtp.SmtpClient.<ConnectAsync>d__100.MoveNext() in (削除)


Custom event details:

motonamotona

とりあえずメール送信全体をtry-catchで囲むことにした

このスクラップは2025/02/05にクローズされました