長い長い文字列のURLエンコード

はじめに

.NET Frameworkにおいて、長い文字列をURLエンコードすることを考えます。

素朴にやるなら、以下のようにUri.EscapeDataStringを使いますが、文字列が長いと、エラーが発生します。

// Uri.EscapeDataStringでエラーが出る例(C#)
var stringToEscape = "なが".PadRight(99996, 'ー') + "い文字列";
var result = Uri.EscapeDataString(stringToEscape);
Console.WriteLine(result);

手っ取り早く解決策

こんなメソッド作る。

// 長い文字列をURLエンコードする
private static string EscapeLongDataString(string stringToEscape)
{
    var sb = new StringBuilder();
    var length = stringToEscape.Length;

    // Uri.c_MaxUriBufferSize以上だとエラーになるため
    // Uri.c_MaxUriBufferSize - 1を上限とする
    var limit = 0xFFF0 - 1;

    // limitごとに区切って処理
    for (int i = 0; i < length; i += limit)
    {
        sb.Append(Uri.EscapeDataString(stringToEscape.Substring(i, Math.Min(limit, length - i))));
    }

    return sb.ToString();
}

使う。

// エラーを回避した例(C#)
var stringToEscape = "なが".PadRight(99996, 'ー') + "い文字列";
var result = EscapeLongDataString(stringToEscape);
Console.WriteLine(result);

解説

最初の失敗例のコードを実行すると、以下の例外が発生します。

System.UriFormatException が発生しました
  HResult=0x80131537
  Message=無効な URI: Uri の文字列が長すぎます。
  Source=System
  スタック トレース:
   場所 System.UriHelper.EscapeString(String input, Int32 start, Int32 end, Char[] dest, Int32& destPos, Boolean isUriString, Char force1, Char force2, Char rsvd)
   場所 System.Uri.EscapeDataString(String stringToEscape)
   場所 EscapeLongDataStringFailSample.Program.Main(String[] args)

例外が発生した場所は、System.UriHelper.EscapeStringであることがわかります。

じゃあSystem.UriHelper.EscapeStringの中で何が起きているのかというと、以下のページで参照できます。
UriHelper.cs(Microsoft Reference Source .NET Framework 4.7)

if (end - start >= Uri.c_MaxUriBufferSize)
    throw new UriFormatException(SR.GetString(SR.net_uri_SizeLimit));

長さがUri.c_MaxUriBufferSize以上だと例外を出すってことですね。
ソースを辿っていくと、この値が0xFFF0だとわかります。

というわけで、先ほどの例のように、0xFFF0 - 1文字で区切って処理するという流れになるわけです。

エラーの回避策は、以上です。

思うこと

自分で書いた記事を自分で否定するようですが、ちょっと思ったことがあるんです。

URLエンコードって、データ量が最大3倍まで膨れ上がります。

今回みたいな回避策が必要になるのって、主に、大きなデータをネットワークで送信するときだと思います。

大きなデータを、最大3倍まで膨らませて送信するのって、良くないんじゃないかなーって思うんです。

URLエンコードする場面はいろいろあるかと思うので一概には言えないですが、大きなデータで問題が起きるときは、小手先の回避策で何とかするんじゃなくて、データが膨らまないような送り方にするとか、送るデータ自体を最小化するとか、データを増やさない方向での解決を試みたほうが根本的な解決につながる気がします。

まとめ

サンプルコード

いずれもC#用。失敗パターンも入れてるから使うときは気を付けてね。