Итак, с некоторой помощью из понимания того, что может происходить через:
Этот вопрос о переполнении стека
Я понял, как кодируется мой URL.
Мой компьютер - японский, поэтому кодовая страница по умолчанию - 932.
После долгого возни с образцом консольного приложения и просмотра пакетов в Wireshark я понял, что независимо от того, что я сделал, значения по умолчанию HttpClient
и WebClient
всегда будут корректно UrlEncode моего URL независимо от того, что кодировку я использовал. IE не так кодирует свои URL.
Я покопался глубже и обнаружил, что в источнике для HttpClient
(и WebClient
) он использует класс Uri
, в котором есть конструктор с параметром: DontEscape
который я подумал "Эврика!" но оказывается, что этот конструктор устарел, и нет способа не сделать так, чтобы URL автоматически экранировали себя при использовании HttpClient
или WebClient
.
Поэтому мне пришлось использовать TcpClient
и вместо этого сделать свой собственный запрос. Который я украл отсюда:
/// <summary>
/// The initial request to search only works if the url is encoded using Shift-JIS, which means we cannot use any client library and must use a custom TCP message.
/// </summary>
/// <param name="serveripaddress"></param>
/// <param name="restoftheurl"></param>
/// <returns></returns>
private async Task<string> HttpRequestAsync(string serveripaddress, string restoftheurl)
{
Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
string result = string.Empty;
using (var tcp = new TcpClient(serveripaddress, 80))
using (var stream = tcp.GetStream())
{
tcp.SendTimeout = 500;
tcp.ReceiveTimeout = 1000;
Console.WriteLine("URL rest:" + restoftheurl);
// Send request headers
var builder = new StringBuilder();
builder.AppendLine("GET " + restoftheurl + " HTTP/1.1");
builder.AppendLine("Host: " + serveripaddress);
//builder.AppendLine("Content-Length: " + data.Length); // only for POST request
builder.AppendLine("Accept: text/html, application/xhtml+xml, */*");
builder.AppendLine("Accept-Language: ja-JP");
builder.AppendLine("User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko");
builder.AppendLine("Accept-Encoding: gzip, deflate");
builder.AppendLine("Connection: Close");
builder.AppendLine();
Console.WriteLine("Sending message:" + builder.ToString());
var header = Encoding.GetEncoding(932).GetBytes(builder.ToString());
await stream.WriteAsync(header, 0, header.Length);
// Send payload data if you are POST request
//await stream.WriteAsync(data, 0, data.Length);
// receive data
using (var memory = new MemoryStream())
{
await stream.CopyToAsync(memory);
memory.Position = 0;
var data = memory.ToArray();
var index = BinaryMatch(data, Encoding.ASCII.GetBytes("\r\n\r\n")) + 4;
var headers = Encoding.ASCII.GetString(data, 0, index);
memory.Position = index;
if (headers.IndexOf("Content-Encoding: gzip") > 0)
{
using (GZipStream decompressionStream = new GZipStream(memory, CompressionMode.Decompress))
using (var decompressedMemory = new MemoryStream())
{
decompressionStream.CopyTo(decompressedMemory);
decompressedMemory.Position = 0;
result = Encoding.UTF8.GetString(decompressedMemory.ToArray());
}
}
else
{
result = Encoding.UTF8.GetString(data, index, data.Length - index);
//result = Encoding.GetEncoding("gbk").GetString(data, index, data.Length - index);
}
}
//Debug.WriteLine(result);
return result;
}
}
private int BinaryMatch(byte[] input, byte[] pattern)
{
int sLen = input.Length - pattern.Length + 1;
for (int i = 0; i < sLen; ++i)
{
bool match = true;
for (int j = 0; j < pattern.Length; ++j)
{
if (input[i + j] != pattern[j])
{
match = false;
break;
}
}
if (match)
{
return i;
}
}
return -1;
}
}
Ключевая часть этого кода:
var header = Encoding.GetEncoding(932).GetBytes(builder.ToString());
Это заставляет строку кодироваться в моей кодовой странице, что требовало регистрации поставщика кодовых страниц, поэтому вверху:
Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
Использование простое:
await HttpRequestAsync("123.456.789.123", "/somepage.do?Search01=コード番号=123456&Search02=改訂番号=2");