Меню Рубрики

Java charset windows 1251

How do I convert a Windows-1251 text to something readable?

I have a string, which is returned by the Jericho HTML parser and contains some Russian text. According to source.getEncoding() and the header of the respective HTML file, the encoding is Windows-1251.

How can I convert this string to something readable?

The variable bytes contains the data shown in my debugger, it’s the result of net.htmlparser.jericho.Element.getContent().toString().getBytes() . I just copy and pasted that array here.

This doesn’t work — readableString contains garbage.

How can I fix it, i. e. make sure that the Windows-1251 string is decoded properly?

Update 1 (30.07.2015 12:45 MSK): When change the encoding in the call in convertString to Windows-1251 , nothing changes. See the screenshot below.

Update 2: Another attempt:

Update 3 (30.07.2015 14:38): The texts that I need to decode correspond to the texts in the drop-down list shown below.

Update 4 (30.07.2015 14:41): The encoding detector (code see below) says that the encoding is not Windows-1251 , but UTF-8 .

3 Answers 3

(In the light of updates I deleted my original answer and started again)

The text which appears

is an accurate decoding of these byte values

(Padded at either end with 32, which is space.)

1) The text is garbage or

2) The text is supposed to look like that or

3) The encoding is not Windows-1215

This line is notably wrong

Extracting the bytes out of a string and constructing a new string from that is not a way of «converting» between encodings. Both the input String and the output String use UTF-16 encoding internally (and you don’t normally even need to know or care about that). The only times other encodings come into play are when text data is stored OUTSIDE of a string object — ie in your initial byte array. Conversion occurs when the String is constructed and then it is done. There is no conversion from one String type to another — they are all the same.

The fact that this

does the same as this

suggests that Windows-1251 is the platforms default encoding. (Which is further supported by your timezone being MSK)

Источник

Откуда в Java всплывают проблемы с кодировками и возможная причина падения марсианского зонда

Планета Марс уже не первый год населена роботами. То тут, то там появляются беспилотные электрокары и летающие дроны, а в программах, написанных на Java, с завидной регулярностью всплывают проблемы с кодировками.

Хочу поделиться своими мыслями о том, почему это происходит.

Предположим, у нас есть файл, в котором хранится нужный нам текст. Чтобы поработать с этим текстом в Java нам нужно загнать данные в String. Как это сделать?

Обратите внимание, что для чтения файла недостаточно просто знать его имя. Нужно еще знать, в какой кодировке в нем находятся данные. Двоичное представление символов в памяти Java-машины и в файле на жестком диске практически никогда не совпадает, поэтому нельзя просто взять и скопировать данные из файла в строку. Сначала нужно получить последовательность байт, а уже потом произвести преобразование в последовательность символов. В приведенном примере это делает класс InputStreamReader.

Код получается достаточно громоздким при том, что необходимость в преобразовании из байтов в символы и обратно возникает очень часто. В связи с этим логичным было бы предоставить разработчику вспомомогательные функции и классы, облегчающие работу по перекодировке. Что для этого сделали разработчики Java? Они завели функции, которые не требуют указания кодировки. Например, класс InputStreamReader имеет конструктор с одним параметром типа InputStream.

Стало чуть попроще. Но здесь разработчики Java закопали серьезные грабли. В качестве кодировки для преобразования данных они использовали так называемый «default character encoding».

Default charset устанавливается Java-машиной один раз при старте на основании данных взятых из операционной системы и сохраняется для информационных целей в системном свойстве file.encoding. В связи с этим возникают следующие проблемы.

  1. Кодировка по умолчанию — это глобальный параметр. Нельзя установить для одних классов или функций одну кодировку, а для других — другую.
  2. Кодировку по умолчанию нельзя изменить во время выполнения программы.
  3. Кодировка по умолчанию зависит от окружения, поэтому нельзя заранее знать, какая она будет.
  4. Поведение методов, зависящих от кодировки по умолчанию, нельзя надежно покрыть тестами, потому что кодировок достаточно много, и множество их значений может расширяться. Может выйти какая-нибудь новая ОС с кодировкой типа UTF-48, и все тесты на ней окажутся бесполезными.
  5. При возникновении ошибок приходится анализировать больше кода, чтобы узнать, какую именно кодировку использовала та или иная функция.
  6. Поведение JVM в случае изменения окружения после старта становится непредсказуемо.

Но главное — это то, что от разработчика скрывается важный аспект работы программы, и он может просто не заметить, что использовал функцию, которая в разном окружении будет работать по-разному. Класс FileReader вообще не содержит функций, которые позволяют указать кодировку, хотя сам класс логичен и удобен, поэтому он стимулирует пользователя на создание платформозависимого кода.

Из-за этого происходят удивительные вещи. Например, программа может неправильно открыть файл, который ранее сама же создала.

Или, скажем, есть у нас XML-файл, у которого в заголовке написано encoding=«UTF-8», но в Java-программе этот файл открывается при помощи класса FileReader, и привет. Где-то откроется нормально, а где-то нет.

Особенно ярко проблема file.encoding проявляется в Windows. В ней Java в качестве кодировки по умолчанию использует ANSI-кодировку, которая для России равна Cp1251. В самой Windows говорится, что «этот параметр задает язык для отображения текста в программах, не поддерживающих Юникод». При чем здесь Java, которая изначально задумывалась для полной поддержки Юникода, непонятно, ведь для Windows родная кодировка — UTF-16LE, начиная где-то с Windows 95, за 3 года до выхода 1-й Java.

Так что если вы сохранили при помощи Java-программы файл у себя на компьютере и отправили его вашему коллеге в Европу, то получатель при помощи той же программы может и не суметь открыть его, даже если версия операционной системы у него такая же как и у вас. А когда вы переедете с Windows на Mac или Linux, то вы уже и сами свои файлы можете не прочитать.

А ведь еще есть Windows консоль, которая работает в OEM-кодировке. Все мы наблюдали, как вплоть до Java 1.7 любой вывод русского текста в черном окне при помощи System.out выдавал крокозябры. Это тоже результат использования функций, основанных на default character encoding.

Я у себя проблему кодировок в Java решаю следующим образом:

  1. Всегда запускаю Java с параметром -Dfile.encoding=UTF-8. Это позволяет убрать зависимость от окружения, делает поведение программ детерминированным и совместимым с большинством операционных систем.
  2. При тестировании своих программ обязательно делаю тесты с нестандартной (несовместимой с ASCII) кодировкой по умолчанию. Это позволяет отловить библиотеки, которые пользуются классами типа FileReader. При обнаружении таких библиотек стараюсь их не использовать, потому что, во-первых, с кодировками обязательно будут проблемы, а во-вторых, качество кода в таких библиотеках вызывает серьезные сомнения. Обычно я запускаю java с параметром -Dfile.encoding=UTF-32BE, чтобы уж наверняка.

Это не дает стопроцентной гарантии от проблем, потому что есть же еще и лаунчеры, которые запускают Java в отдельном процессе с теми параметрами, которые считают нужными. Например, так делали многие плагины к анту. Сам ант работал с file.encoding=UTF-8, но какой-нибудь генератор кода, вызываемый плагином, работал с кодировкой по умолчанию, и получалась обычная каша из разных кодировок.

По идее, со временем код должен становиться более качественным, программы более надежными, форматы более стандартизованными. Однако этого не происходит. Вместо этого наблюдается всплеск ошибок с кодировками в Java-программах. Видимо, это связано с тем, что в мир Java иммигрировали люди, не привыкшие решать проблему кодировок. Скажем, в C# по умолчанию применяется кодировка UTF-8, поэтому разработчик, переехавший с C#, вполне разумно считает, что InputStreamReader по умолчанию использует эту же кодировку, и не вдается в детали его реализации.

Недавно наткнулся на подобную ошибку в maven-scr-plugin.

Но настоящее удивление пришлось испытать при переезде на восьмерку. Тесты показали, что проблема с кодировкой затесалась в JDK.

На девятке не воспроизводится, видимо, там уже починили.

Поискав по базе ошибок, я нашел еще одну недавно закрытую ошибку, связанную с теми же самыми функциями. И что характерно, их даже исправляют не совсем правильно. Коллеги забывают, что для стандартных кодировок, начиная с Java 7, следует использовать константы из класса StandardCharsets. Так что впереди, к сожалению, нас ждет еще масса сюрпризов.

Запустив grep по исходникам JDK, я нашел десятки мест, где используются платформозависимые функции. Все они будут работать некорректно в окружении, где родная кодировка, несовместима с ASCII. Например, класс Currency, хотя казалось бы, уж этот-то класс должен учитывать все аспекты локализации.

Когда некоторые функции начинают создавать проблемы, и для них существует адекватная альтернатива, давно известно, что нужно делать. Нужно отметить эти функции как устаревшие и указать, на что их следует заменить. Это хорошо зарекомендовавший себя механизм deprecation, который даже планируют развивать.

Я считаю, что функции, зависящие от кодировки по умолчанию, надо обозначить устаревшими, тем более, что их не так уж и много:

Функция На что заменить
Charset.defaultCharset() удалить
FileReader.FileReader(String) FileReader.FileReader(String, Charset)
FileReader.FileReader(File) FileReader.FileReader(File, Charset)
FileReader.FileReader(FileDescriptor) FileReader.FileReader(FileDescriptor, Charset)
InputStreamReader.InputStreamReader (InputStream) InputStreamReader.InputStreamReader (InputStream, Charset)
FileWriter.FileWriter(String) FileWriter.FileWriter(String, Charset)
FileWriter.FileWriter(String, boolean) FileWriter.FileWriter(String, boolean, Charset)
FileWriter.FileWriter(File) FileWriter.FileWriter(File, Charset)
FileWriter.FileWriter(File, boolean) FileWriter.FileWriter(File, boolean, Charset)
FileWriter.FileWriter(FileDescriptor) FileWriter.FileWriter(FileDescriptor, Charset)
OutputStreamWriter.OutputStreamWriter (OutputStream) OutputStreamWriter.OutputStreamWriter (OutputStream, Charset)
String.String(byte[]) String.String(byte[], Charset)
String.String(byte[], int, int) String.String(byte[], int, int, Charset)
String.getBytes() String.getBytes(Charset)

Да, а что там с космическим аппаратом на Марсе?

Часть программного обеспечения для марсианского зонда Скиапарелли написали на Java, на актуальной в то время версии 1.7. Запустили изделие весной, и путь к месту назначения составил полгода. Пока он летел, в Европейском космическом агентстве обновили JDK.

Ну а что? Разработка софта для нынешней миссии завершена, надо делать ПО уже для следующей, а мы все еще на семерке сидим. НАСА и Роскосмос уже давно на восьмерку перешли, а там лямбды, стримы, интерфейсные методы по умолчанию, новый сборщик мусора, и вообще.

Обновились и перед посадкой отправили на космический аппарат управляющую команду не в той кодировке, в которой он ожидал.

Источник

Сказ про кодировки и java

С кодировками в java плохо. Т.е., наоборот, все идеально хорошо: внутреннее представление строк – Utf16-BE (и поддержка Unicode была с самых первых дней). Все возможные функции умеют преобразовывать строку из маленького регистра в большой, проверять является ли данный символ буквой или цифрой, выполнять поиск в строке (в том числе с регулярными выражениями) и прочее и прочее. Для этих операций не нужно использовать какие-то посторонние библиотеки вроде привычных для php mbstring или iconv. Как говорится, поддержка многоязычных тестов “есть в коробке”. Так откуда берутся проблемы? Проблемы возникают, как только строки текста пытаются “выбраться” из jvm (операции вывода текста различным потребителям) или наоборот пытаются в эту самую jvm “залезть” (операция чтения данных от некоторого поставщика).

Сказка про капиталистов

Надо сказать, что unicode это не статическое образование, не принятый еще при царе горохе стандарт, который с тех пор безуспешно пытаются реализовать производители различных продуктов. Это динамический, постоянно развивающийся стандарт, с множество версий и соответствующих спецификаций. Полагаю, с тем, что для хранения текста написанного на различных языках (а для азиатов хватит и одного их родного языка) размера символа в один байт совершенно не достаточно, согласны все. Нет, чтобы взять и прикинуть, сколько там всего языков на всей планете, сколько в их алфавитах символов, сколько разных значков (нотных, графических) может потребоваться на ближайшие 100 лет. Взяли, прикинули: 1 байт – смешно, 2 уже лучше, но все равно маловато, 3 байта (примерно 16 миллионов символов уже хорошо), а если взять для представления символа все 4 байта (4 миллиарда с гаком) – то просто замечательно. Приняли бы такое решение, потом бы издали указ: мол, так и так с первого числа сего месяца начинается новые и улучшенные компьютерные времена, переделали бы все заводы по производству компьютеров, вызывали Била Гейтса на партсобрание, дали бы ценное указание и жизнь стала бы гораздо лучше. Увы, в этом жестком капиталистическом мире, на всей планете найдется всего несколько человек, которые согласятся ради возможности решить раз и навсегда все проблемы на то, чтобы объем их винчестеров и оперативной памяти уменьшится сразу в четыре раза (во сколько раз уменьшится производительность вычислений сказать тяжело – но все равно очень неслабо). Да, еще достижение всеобщего блага привело бы к полной потере всего ранее написанного софта, документов и т.д. Увы капитализм не захотел устроить всемирный субботник, а нам наследникам красного октября приходится это расхлебывать. Конечно, это шутка. Но во всякой шутке, как известно, есть доля шутки. Первая версия Unicode представляла собой кодировку, в которой каждый символ кодировался 2 байтами, для некоторых символов (наиболее часто используемых, а не всех возможных) были выделены определенные области (интервалы). Потом, с течением времени решили, что все же 64 тысяч символов будет маловато и необходим механизм хранения их большего числа. Кроме того, разработчики стандарта поняли, что в разных странах разное понятие о “букве” и все стало еще сложнее. В принципе все это написано на wikipedia, так что прекращаю рассказывать сказки, и перехожу к java, точнее к проблемам связанным с кодировками в java.

Типовые проблемы с которыми сталкиваются java-разработчики

Т.к. java приложения взаимодействуют с различными подсистемами, то и возникающие проблемы бывают разными. Хотя все, в общем случае, сводится к одной из двух проблем:

Проблема 1. Данные были успешно прочитаны, но на стадии отображения не нашлись нужные шрифты. В этом случае отсутствующие картинки шрифта заменяются на квадратики. Лечится проблема путем установки нужных шрифтов (например, при установке windows вас обычно спрашивают, хотите ли вы добавить поддержку шрифтов для азиатских языков). Есть два вида шрифтов: физические и логические. Физические шрифты – это те шрифты, файлы которых установлены либо в папку там_где_ваша_jre/lib/fonts, либо те шрифты которые установлены в стандартное место для вашей операционной системы (все версии jre обязаны поддерживать шрифты TrueType, остальные же форматы — необязательно). Логические шрифты (например, Serif, Sans-Serif, Monospaced, Dialog и DialogInput) – это правила отображения некоторых имен на реальные физические шрифты. Например, для windows логический шрифт serif это ссылка на физический times new roman. Задаются эти правила в файлах fontconfig.properties.src, fontconfig.98.properties.src, fontconfig.Me.properties.src. Для swing приложений, мы можем не только работать с идущими в самой операционной системе шрифтами, но и носить файл шрифта вместе со своим приложением, так чтобы полностью не зависеть от того, где оно будет запущено. В составе класса java.awt.GraphicsEnvironment есть несколько методов позволяющих получить информацию о том, какие шрифты доступны на вашем компьютере.

Надо сказать, что в качестве параметра второму методу можно передать в качестве параметра объект Locale (сведения о географическом местоположении страницы, ее языке, денежных единицах …). В этом случае будут возвращены шрифты, локализованные для именно этого языка. Если же никакого параметра при вызове не указать, то вы получите список шрифтов привязанных к текущей (по-умолчанию) локали.

Для того, чтобы создать шрифт на основании некоторого файла ttf, необходимо вызвать статический метод createFont из класса Font. В качестве параметров для него следует указать файл, который содержит определение шрифта, а также указать тип этого файла (Font.TRUETYPE_FONT или Font.TYPE1_FONT). Созданный объект шрифта можно “настроить” указав для него размер или стиль (plain, italic, bold). Используйте для этого метод deriveFont.

final JFrame jf = new JFrame ( «barra» ) ;
jf.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE ) ;

JPanel pa = new JPanel ( new GridLayout ( 0 , 1 )) ;

JLabel lab_1 = new JLabel ( «Гравитационные волны» ) ;
JLabel lab_2 = new JLabel ( «Гравитационные волны» ) ;
JLabel lab_3 = new JLabel ( «Гравитационные волны» ) ;
JLabel lab_4 = new JLabel ( «Гравитационные волны» ) ;
// используем стандартные шрифты: в первом случае логический шрифт, а во
// втором физический.
// обратите внимание, что на картинке они выглядят одинаково
lab_1.setFont ( new Font ( «Serif» , Font.PLAIN, 24 )) ;
lab_2.setFont ( new Font ( «Times New Roman» , Font.PLAIN, 24 )) ;

// теперь пробуем загрузить шрифт из внешнего файла
Font f_ye = Font.createFont ( Font.TRUETYPE_FONT, new File ( «yermak.ttf» )) ;
lab_3.setFont ( f_ye.deriveFont ( Font.PLAIN, 24.0f )) ;
// и еще один шрифт из внешнего файла
Font f_inv = Font.createFont ( Font.TRUETYPE_FONT,new File ( «invest.ttf» )) ;
lab_4.setFont ( f_inv.deriveFont ( Font.PLAIN, 24.0f )) ;

// получим и выведем в виде JComboBox список всех шрифтов
pa.add ( new JLabel ( «getAllFonts» )) ;
Font [] allFonts = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment () .getAllFonts () ;
pa.add ( new JComboBox ( allFonts )) ;
pa.add ( new JLabel ( «count fonts = » + allFonts.length )) ;
// список названий всех шрифтов доступных для текущей локали
pa.add ( new JLabel ( «getAvailableFontFamilyNames» )) ;
String [] locFontNames = java.awt.GraphicsEnvironment.
getLocalGraphicsEnvironment () .getAvailableFontFamilyNames () ;
pa.add ( new JComboBox ( locFontNames )) ;
pa.add ( new JLabel ( «count fonts = » + locFontNames.length )) ;

pa.add ( lab_1 ) ;
pa.add ( lab_2 ) ;
pa.add ( lab_3 ) ;
pa.add ( lab_4 ) ;

jf.setContentPane ( pa ) ;
jf.pack () ;

SwingUtilities.invokeLater (
new Runnable () <
public void run () <
jf.setVisible ( true ) ;
>
>
) ;

Теперь вернемся к русским буквам и java.

Вторая наиболее часто встречающаяся проблема — это неправильное преобразование кодировки. Например, вы хотите прочитать текстовый файл в кодировке windows-1251. Но при создании объекта InputStreamReader вы указали неверную кодировку (или положились на значение по-умолчанию).

В результате при чтении файла символы будут рассматриваться как принадлежащие определенной кодовой странице. Но вовсе не факт что некоторый код символа корректный для кодировки A будет также корректен для кодировки B. В случае корректности кодов, мы увидим то, что некоторые из символов были заменены на какие-то другие символы. А вот, если код является некорректным (например, зарезервирован на будущее), то такой символ будет заменен на знак “?”.

Для получения списка всех доступных кодировок вы можете использовать следующий код (вызов статического метода Charset.availableCharsets):

В папке lib есть архив charsets.jar (размером в гадкие 9 мегабайт), в котором находятся классы, управляющие преобразованием из одной кодировки в другую (например, sun\io\ByteToCharCp949.class). Логично, что наиболее частой причиной второго вида ошибок являются не отсутствующие (неизвестные java кодировки), а то, что мы просто не знаем то, в какой из кодировок пришли данные. Эту проблему можно решать различными путями, как техническими, так и административными. И хорошо еще, если мы в состоянии повлиять на тот фрагмент кода, который выполняет чтение данных из файла (конфигурационной переменной или вызовом особого метода, вроде, setCharset …). А что, если проблемный кусок кода зашит где-то глубоко внутри сторонней библиотеки, к которой у нас нет исходников или нет специалистов, способных найти “то самое глючное место и исправить его так, чтобы не рухнуло все остальное”. В том случае, если данные были прочитаны неверно, но мы знаем в какой кодировке они реально пришли и в какой кодировке их ошибочно прочитали. Тогда можно сделать попытку восстановить исходный набор символов. Основан алгоритм восстановления на двух последовательных преобразованиях: преобразовании “неправильной” строки в массив байтов (по правилам ошибочной кодировки), так мы получим данные до преобразования, а затем мы выполним правильное преобразование (в нужную кодировку).

Здесь “utf-8” правильная кодировка, а “windows-1251” – неправильная.

Но предупреждаю сразу – это плохой, очень плохой способ “починить примус”. Помните, что в ходе преобразований возможна потеря символов (из-за несовместимых кодировок). Так что если вы прочитали данные из файла в неверной кодировке, то отсутствующие символы были заменены на значки вопросов. Следовательно попытка восстановить оригинальный массив байтов будет безуспешной.

Java и web.

Web – это то самое место где сталкиваются множество людей работающих под разными версиями операционных систем использующие разные браузеры и написанный нами сайт должен работать всегда и везде.

Давайте рассмотрим, как данные поступают на вход браузеру от веб-сервера и то, как браузер отправляет информацию серверу (точнее веб-приложению исполняющемуся на нем). Есть два метода для отправки запросов от браузера к веб-серверу: get и post. Остальные методы, такие как put, delete, options, доступны при использовании ajax-вызовов, а внутри компонента XmlHttpRequest выполняется конвертация отправляемых данных в “utf-8”, что сразу решает ряд проблем с неизвестными кодировками.

Самая идеальная ситуация — когда отправка идет с помощью метода POST. В этом случае браузер кодирует данные в такой же кодировке, как и в той, что была сформирована веб-страница. За кодировку возвращаемых данных отвечают либо указанная вверху jsp-файла директива:

Первая из этих опций (contentType) указывает на кодировку выходного документа, а вторая (pageEncoding) на кодировку собственно файла в котором находится код jsp-страницы.

Либо, если вы создаете сервлет, то первым шагом нужно указать выходную кодировку документа:

Классно, значит, если мы сформировали страницу в кодировке utf-8, то данные из формы придут к нам в формате “utf-8”. Классно, то классно, но кто сказал, что ваш веб-сервер правильно эти данные сможет раскодировать? Теоретически, когда браузер делает запрос к серверу, то отправляется не только сведения о том какой документ хочет видеть клиент, не только данные из формы, но и сведения об браузере, об поддерживаемых кодировках, об предпочитаемых языках документа, и прочее и прочее и прочее. Может там найдется сведения о кодировке? Давайте проверим. При создании тега form вы должны указать значение не только метода отправки (GET или POST), но и значение атрибута enctype. Его возможные значения: «multipart/form-data» или «application/x-www-form-urlencoded». В первом случае форма будет способна отправлять не только текстовые данные, но и, например, файлы (кто бы мне сказал, почему sun-овцы не могли реализовать парсинг подобного запроса самостоятельно или внести в стандарт для любого servlet-контейнера, а отдали на откуп посторонним?). Рассмотрим как кодируются данные в случае «multipart/form-data»? Ниже пример подобного запроса:

Как видите, запрос разбит на секции с помощью некоторой уникальной комбинации символов. Итак, где в этом запросе есть указание на то, в какой кодировке пришли данные от браузера? Нигде, нет их. Может быть, кодировка указывается при запросе «application/x-www-form-urlencoded»? Ладно, вот пример еще одного запроса:

Способ кодирования информации отличен: прежде всего, заметьте, как были переданы русские буквы. В первом случае они передаются как есть, т.е. в той кодировке в которой была сформирована и сама страница. Во втором случае буквы превратились во множество значков процента, цифр и букв. Кодировка «application/x-www-form-urlencoded» применяется также и в случае отправки данных методом GET (передается в адресной строке).

Символы, которые не могут быть отправлены по сети (все кроме латиницы, цифр и ряда знаков), предварительно кодируются с помощью алгоритма x-www-form-urlencoded. Байты кодировки заменяются на последовательности вида %XX. Вместо XX подставляются две шестнадцатеричные цифры (http://www.faqs.org/rfcs/rfc1738). Стоп. Самый главный вопрос: x-www-form-urlencoded – это кодировка или нет? Традиционно под кодировкой понимают комбинацию набора символов и схемы кодирования. Например, когда говорят utf-8, то подразумевают схему кодирования utf-8 и набор символов Unicode, ровно, как и для utf-16. А если вы слышите windows-1251, то здесь название кодировки дано по названию набора символов. Т.к. как такового отдельного алгоритма кодирования цифры, под которой в данном наборе фигурирует, например, буква “Ы” — нет. Просто 8 бит – бери и пиши их в файл как есть, без каких-либо дополнительных преобразований. Так что x-www-form-urlencoded – это не кодировка, это способ отправить те самые байты, в которые было выполнено преобразование строки текста согласно некоторой “настоящей” кодировке (точнее схеме кодирования). Следовательно, если я открываю адрес вида: http : //Мой-сайт.ru/ящики_с_пивом

То, в зависимости от используемой кодировки, данные будут отправлены либо так:

%FF%F9%E8%EA%E8_%F1_%EF%E8%E2%EE%EC — так выглядит слово “ящики с пивом” в кодировке windows-1251

%D1%8F%D1%89%D0%B8%D0%BA%D0%B8_%D1%81_%D0%BF%D0%B8%D0%B2%D0%BE%D0%BC – а так выглядит это слово в кодировке utf-8.

Возвращаясь к анализу двух примеров запроса данных, мы нигде не видим указания на то какая кодировка используется для отправки данных. Может, у меня не правильный браузер, и какие то другие, правильные, браузеры указывают кодировку отправляемых данных? Увы, ни internet explorer 6,7 ни firefox 2,3 ни opera 9.5 не указывают сведений о кодировке.

Автоматически определить кодировку tomcat не может, а раз не может, то будет выполнять преобразование поступивших данных из кодировки (по-умолчанию) ISO8859-1. Несколько раз мне встречались в сети рекомендации делать что-то вроде:

Это очень плохой совет. Помните, что неправильные операции преобразования могут приводить к потере данных и, притом, необратимому. Значит, нужно подсказать tomcat-у, как правильно выполнить декодирование (а иначе, без нашего указания, он такого на раскодирует … не исправить).

Когда вызывается ваш сервлет (или jsp, что суть одно и то же). То вы можете узнать в какой кодировке к вам пришли данные, например, так:

Если значение кодировки null (а оно равно этой величине почти всегда), тогда tomcat решает, что входные данные были в формате ISO8859-1 и пытается именно так выполнить парсинг строки. Существует народное поверье, что если создать специальный сервлет-фильтр, который будет вызываться до того, как будет выполнено первое обращение к списку передаваемых параметров и установит значение правильной кодировки, то все заработает без проблем, например:

Теперь при первом же обращении к какому-либо из входных параметров:

Равно как и для jstl:

Будет выполнено раскодирование входных данных с учетом указанной вами кодировки.

Наверное, форсировать установку значения для кодировки не всегда правильно. С другой стороны, если ваше веб-приложение содержит страницы, формируемые в разных кодировках (непонятно, правда, зачем вам это понадобилось), то можно тонко настроить шаблон для тех адресов jsp-страниц, которые будут обслуживаться этим фильтром:

Можно обойтись и меньшей кровью, выполнив эту команду внутри вашего сервлета самой первой строкой кода (нужно только быть уверенным в том, что никакой другой код не пытался получить значения переменных до вас):

Или, если вы создаете jsp-файл с использованием jstl-тегов, то такую команду:

Однако для того, чтобы указанное значение кодировки было применено к параметрам переданным методом GET (применительно к tomcat) нужно выполнить правку конфигурационного файла server.xml и добавить для элемента Connector атpибут useBodyEncodingForURI равный значению “true”. В этом случае разбор параметров будет выполнен с такой кодировкой, которую вы установили с помощью вызова request.setCharacterEncoding(«utf-8»).

URIEncoding This specifies the character encoding used to decode the URI bytes, after %xx decoding the URL. If not specified, ISO-8859-1 will be used.
useBodyEncodingForURI This specifies if the encoding specified in contentType should be used for URI query parameters, instead of using the URIEncoding. This setting is present for compatibility with Tomcat 4.1.x, where the encoding specified in the contentType, or explicitely set using Request.setCharacterEncoding method was also used for the parameters from the URL. The default value is false.

Проще говоря, либо вы указывате явно значение кодировки для всех входных запросов с помощью параметра URIEncoding (а-га, вот как будто бы всегда и для всех приложений на этом хостинге только такая кодировка является допустимой). Либо устанавливаете вторую перменную |useBodyEncodingForURI равной значению true (по-умолчанию ее значение false).

Единственная проблема в том, что мы выполнять правку файла server.xml мы можем лишь, в случае если имеем прямой доступ к каталогу, где установлен tomcat. Согласитесь, что в случае типового виртуального хостинга мы можем управлять приложением только с помощью файлов web.xml и еще META-INF/context.xml – а это не то. Также, если ваше приложение запущено под другим веб-сервером, то вам нужно будет разбираться с его специфическим настройками.

Некоторое время назад я пытался разобраться с настройками для resin. В FAQ написано, что на разбор данных оказывают влияние следующие значения:

Тег character-encoding, может быть дочерним по отношению к следующим уровням настройки: resin, server, host-default, host, web-app-default, web-app (на уровне приложения, а значит мы можем настроить свое приложение даже на самом обычном виртуальном хостинге).

Обратите внимание на схему, которая регламентирует содержимое web.xml в следующем примере (традиционная )

То такой пример не будет работать: т.к. тег character-encoding является специфическим именно для resin.

Если же значение кодировки явно не указано, то для чтения данных используется кодировка по-умолчанию для jvm (file.encoding). К сожалению, мои попытки запустить resin с указанием входной кодировки ничем хорошим не закончились (после установки значения кодировки в web.xml переданные рускоязычные символы превращались в черт его знает что). Так что пришлось обходиться привычным request.setCharacterEncoding (‘utf-8’); Тогда у меня было мало времени разбираться в особенностях поведения resin, также я не исключаю что это был хитрый баг, так что если у кого-то есть заметки по этому поводу, то прошу поделиться с общественностью.

Как вывод: если данные передаются методом POST, то проблем нет. Если методом GET то проблемы есть; и особенно большие проблемы в случае, если кодировки для метода GET и POST отличаются друг от друга, или некоторые GET запросы приходят в одной кодировке, а некоторые – в другой. Думаете, такого не может быть? Может. Ситуация была такова: есть сайт в кодировке utf-8 на его страницах находится множество ссылок ссылающихся на разделы этого же сайта, и, внимание, в тексте ссылок содержались русскоязычные названии (как некое подобие wikipedia). Если человек жмет на такую ссылку то адресная строка перед кодированием ее с помощью x-www-form-urlencoded, подвергалась кодированию в utf-8 (кодировку страницы). Однако если такую ссылку вводили в адресную строку браузера руками, то кодировка была windows-1251 (это для русскоязычной windows). Для linux машины, на которой стояла fedora, кодировка была utf-8. как решили проблему? Как обычно: матюгами и напильником.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

  • Jarvis для windows 7 с голосом
  • James bond 007 blood stone windows 10
  • Jakarta driver windows 7 x64
  • Jailbreak tool windows rt
  • Jagged alliance 2 windows 10 тормозит