URL エンコーディングの定義と、それを扱うための .NET Framework のライブラリを検証しました。
パーセント エンコーディングと URL エンコーディング
パーセント エンコーディングとは、文字列を UTF-8 でエンコードし、各バイトをパーセント記号 %
とその 16 進数を用いて表すことです。
例えば、"/"
は "%2F"
に、"あ"
は "%E3%81%82"
に変換されます。
URL エンコーディングとは、URI の中で使われている記号と混在しないように一部の文字列をパーセント エンコーディングにより
変換することです。両者の言葉を区別せずに使うこともあります。
URL エンコーディング
RFC 3986 では、文字は次のように分類されます。
- 非予約文字
- エンコードしなくても利用できる文字
- アルファベット、数字、および 4 種類の記号
-._~
- 予約文字
- URI で意味を持つ記号
- 18 種類の記号
!#$&'()*+,/:;=?@[]
- その他の文字
- エンコードが必要な文字
- 11 種類の記号
" %<>\^`{|}
、その他のすべての文字 (日本語など)
URL エンコーディングは、主に次の 2 通りで利用されます。
- URI の各セグメント (クエリ文字列を除く)
https://tempuri.org/messages/Hello%20World%21
のmessages
やHello%20World%21
の部分- 非予約文字以外をパーセント エンコーディング
- ただし、Web フレームワーク個別の仕様により、パーセント エンコーディングしても使用を制限されることがある
- URI のクエリ文字列や、POST などで送信するときの本文 (フォーム)
key=value&message=Hello+World%21
のkey
やHello+World%21
の部分- 非予約文字以外をパーセント エンコーディングし、さらに
%20
(スペース) を+
に変換 - MIME タイプ
application/x-www-form-urlencoded
と定義されている
.NET Framework のライブラリ
.NET Framework では、URL エンコーディングのために次の方法が用意されています。
- System.Uri.EscapeDataString メソッド
- RFC 3986 に従って非予約文字以外をパーセント エンコーディング
- System.Uri.EscapeUriString メソッド
- RFC 3986 に従って非予約文字・予約文字以外をパーセント エンコーディング
- 既に全体が URI の形式になっているときに利用する
- クエリ文字列も同様の規則で変換される。
application/x-www-form-urlencoded
には変換されない
- クエリ文字列も同様の規則で変換される。
- System.Uri インスタンスの AbsoluteUri プロパティ
- 基本的に Uri.EscapeUriString メソッドと同じだが、下記の点が異なる
%XX
の形式になっているかどうかで扱いが異なるhttps://tempuri.org/%2
はhttps://tempuri.org/%252
にhttps://tempuri.org/%25
はhttps://tempuri.org/%25
のまま
- クエリ文字列でない部分の
\
は/
に変換される
- System.Net.WebUtility.UrlEncode メソッド
- RFC 2396 (旧版) に近い仕様で非予約文字以外をパーセント エンコーディングし、さらに
%20
(スペース) を+
に変換
- RFC 2396 (旧版) に近い仕様で非予約文字以外をパーセント エンコーディングし、さらに
- System.Web.HttpUtility.UrlEncode メソッド
- System.Net.WebUtility.UrlEncode メソッドと同じだが、小文字になる
- System.Net.Http.FormUrlEncodedContent クラス
- key-value データをまとめて
application/x-www-form-urlencoded
に変換
- key-value データをまとめて
.NET では System.Uri.EscapeDataString メソッド、System.Uri.EscapeUriString メソッド、
System.Net.Http.FormUrlEncodedContent クラスを使えばよいでしょう。
アプリケーションから HTTP 接続をするために System.Net.Http.HttpClient クラスを使うことが多いと思いますが、
接続先の URI を string で渡しても、HttpClient の内部では Uri インスタンスで扱われます。
したがって、URI を HttpClient に渡す前に、セグメントもクエリ文字列も URL エンコーディングしておくのがよさそうです。
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Net.Http; | |
using System.Threading.Tasks; | |
namespace UnitTest.Client | |
{ | |
public static class HttpHelper | |
{ | |
public static string AddQuery(this string uri, IDictionary<string, string> data) => | |
$"{uri}?{data.ToFormUrlEncoded()}"; | |
public static string ToFormUrlEncoded(this IDictionary<string, string> data) | |
{ | |
using (var content = new FormUrlEncodedContent(data)) | |
return content.ReadAsStringAsync().GetAwaiter().GetResult(); | |
} | |
async public static Task<T> GetAsync<T>(string uri) | |
{ | |
using (var http = new HttpClient()) | |
{ | |
var response = await http.GetAsync(uri); | |
response.EnsureSuccessStatusCode(); | |
return await response.Content.ReadAsAsync<T>(); | |
} | |
} | |
async public static Task<T> PostAsFormAsync<T>(string uri, IDictionary<string, string> data) | |
{ | |
using (var http = new HttpClient()) | |
{ | |
var response = await http.PostAsync(uri, new FormUrlEncodedContent(data)); | |
response.EnsureSuccessStatusCode(); | |
return await response.Content.ReadAsAsync<T>(); | |
} | |
} | |
} | |
} |
using System; | |
using System.Collections.Generic; | |
using Microsoft.VisualStudio.TestTools.UnitTesting; | |
namespace UnitTest.Client | |
{ | |
[TestClass] | |
public class UriQueryTest | |
{ | |
[TestMethod] | |
public void Get_Segment() | |
{ | |
// http://localhost:1961/api/uriquery/Hello~%2C%20World%21 | |
var uri = $"http://localhost:1961/api/uriquery/{Uri.EscapeDataString("Hello~, World!")}"; | |
var result = HttpHelper.GetAsync<string>(uri).GetAwaiter().GetResult(); | |
} | |
[TestMethod] | |
public void Get_Query() | |
{ | |
var data = new Dictionary<string, string> | |
{ | |
{ "id", "Hello, the \"World+\"." }, | |
}; | |
// http://localhost:1961/api/uriquery?id=Hello%2C+the+%22World%2B%22. | |
var uri = "http://localhost:1961/api/uriquery".AddQuery(data); | |
var result = HttpHelper.GetAsync<string>(uri).GetAwaiter().GetResult(); | |
} | |
[TestMethod] | |
public void Post_Form() | |
{ | |
var data = new Dictionary<string, string> | |
{ | |
{ "name", "Hello, the \"World+\"." }, | |
}; | |
var uri = "http://localhost:1961/api/uriquery"; | |
var result = HttpHelper.PostAsFormAsync<string>(uri, data).GetAwaiter().GetResult(); | |
} | |
} | |
} |
作成したサンプル
バージョン情報
- .NET Framework 4.5
参照