понедельник, 14 октября 2013 г.

Передача Windows-1251 (cp1251) с помощью POST и AJAX на сторону сервера.

Внезапно, я обнаружил, что POST AJAX упорно передает мою строку в UTF-8, игнорируя все попытки указать ему кодировку. А серверные программы настроены принимать только windows-1251 и это вызывает некий конфликт. И выражается он в том, что русский текст становится текстом кракозябровым.
Некоторое время погуглив я обнаружил, что нормальное решение этой проблемы как-то не особо существует и основное мнение комьюнити показано, например, здесь. Краткое резюме:
это нельзя, потому что нельзя, используй перекодировку. Я же, со своей стороны решил, что вариант с удвоением трафика + потерей мощности меня не слишком радует.
Дальнейшие раскопки привели меня к причинам такого поведения AJAX: w3. Там написано, что строку надо передавать в UTF-8 и ничего больше не волнует. Но что более интересно, там сказано, что другие варианты можно передавать и не как UTF-8. Например: ArrayBuffer или Blob.
Покопав в этом направление, я нашел еще 1 статью. Этот вариант относится к новым возможностям, появившимся относительно недавно.

Идея проста:
Строка - это тот же массив символов. Просто ajax передаст ее массив 2вубайтовых чисел, а мы хотим передавать массив однобайтовых. Значит, чтобы передать строку на сервер в windows-1251 надо просто записать коды windows-1251 символов нашей строки в массив однобайтовых чисел. А на стороне сервера - просто прочитать из тела запроса как готовую строку в кодировке windows-1251.

Как это сделать:

1) Берем нашу строку data_str и какой-либо функцией меняем ее кодировку на нужную(в моем случае - это windows-1251). Такую функцию легко написать самому или найти на просторах сети, например, тут.

var converted_str = convertToWin1251(data_str);

2) Мы получили converted_str, которая все еще считается строкой в UTF-8, но содержит уже коды необходимой кодировки. Просто так послать ее нельзя, ибо AJAX все равно будет трактовать ее, как UTF-8 и попытается конвертировать, что окончательно ее испортит.
Поэтому, мы создаем массив 8мибайтовых неотрицательных чисел размером под нашу строку:

var send_arr = new Uint8Array(converted_str.length);

3) Теперь осталось только заполнить этот массив

for(var i = 0; i < converted_str .length; ++i)
send_arr[i] = converted_str.charCodeAt(i);

4) выставить правильную кодировку и отправить его:

ajaxVar.open('POST', 'http://FOO.BAR', 1);
ajaxVar.setRequestHeader('Content-Type', 'text/plain; charset=windows-1251');
ajaxVar.send(send_arr);

где ajaxVar - это созданный ajax-запрос.
Собственно, все.

Осталось только принять данные на строне сервера. Они передаются как RAW POST DATA. Неплохо было бы, чтобы принимающая сторона умела работать с ней. С++ С#/ASP.NET насколько я помню, проблем с этим не имеют, PHP это делает так.

P.S. Если кто-то не согласен, нашел способ лучше или просто есть что сказать по существу - добро пожаловать в комментарии =)