PowerShell: особенности использования командлета «Invoke-WebRequest»

Jan 26, 2023 02:42

Начало:
1. Общение с ЖЖ из скрипта по протоколу HTTP(S), ч.1: документация
2. Общение с ЖЖ из скрипта по протоколу HTTP(S), ч.2: URL-адрес входа, интерфейсы
3. Выбор веб-клиента для общения с ЖЖ из скрипта, PowerShell, моделирование

В предыдущем посте я описал, как можно просмотреть формируемый командлетом «Invoke-WebRequest» в программе-оболочке «PowerShell» (у меня - версия 7) HTTP-запрос с помощью небольшого скрипта на языке PHP со стороны веб-сервера (у меня - веб-сервер «IIS 10» в операционной системе «Windows 10»), установленного локально.

В этом посте я с помощью описанного способа покажу некоторые особенности работы командлета «Invoke-WebRequest», которые явились для меня неожиданностью. (Если вы перед использованием командлета внимательно прочтете всю документацию по нему, то описанное здесь не станет для вас сюрпризом, там всё это описано. Но я предпочитаю чередовать изучение скучной теории и практику, поэтому жизнь моя полна удивительных открытий, что иногда радует, а иногда не очень.)

Версия протокола HTTP

Протокол HTTP используется в интернете в разных версиях. В командлете «Invoke-WebRequest» для прямого указания версии протокола HTTP есть параметр -HttpVersion. В этом параметре можно указывать одно из следующих четырех значений: 1.0, 1.1, 2.0 и 3.0.

По факту сейчас в интернете в основном используется протокол HTTP версий 1.1 (опубликована в 1999 году) и 2.0 (опубликована в 2015 году). Я смотрел статистику, но разные источники расходятся в показаниях: одни выдают, что на сегодня соотношение использования этих версий 40% и 60%, другие источники показывают соотношение 50% и 50%, третьи показывают соотношение 60% и 40%. Версию 1.0 можно уже не учитывать, а версия 3.0 еще только разрабатывается.

Я пока что не буду пользоваться этим параметром. При этом по умолчанию командлет «Invoke-WebRequest» формирует HTTP-запрос по версии 1.1 протокола HTTP.

Запись в тело HTTP-запроса

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

Я знал, что при HTTP-методе «POST» можно писать в тело HTTP-запроса, но сомневался, можно ли это делать при HTTP-методе «GET». Поэтому проверим оба варианта. Ниже я буду сначала указывать команду в командной строке программы-оболочки «PowerShell», а затем - текст HTTP-запроса, полученного веб-сервером (он не выводится в окно программы-оболочки «PowerShell», а пишется скриптом на языке PHP в текстовый файл; я описывал эту схему подробно в предыдущем посте).

1. Запись строки в тело HTTP-запроса при методе «GET»:

PS C:\> $Response = Invoke-WebRequest -URI "https://localhost/test.php" -Body "qwerty"

GET /test.php HTTP/1.1
Content-Type:
Content-Length: 6
User-Agent: Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.19045; ru-RU) PowerShell/7.3.1
Host: localhost

qwerty

Как видно из кода выше, для указания содержимого тела HTTP-запроса в командлете «Invoke-WebRequest» есть параметр -Body. При формировании HTTP-запроса его тело (если оно есть) отделяется от HTTP-заголовков пустой строкой, это видно в блоке кода выше. Командлет «Invoke-WebRequest» автоматически записывает в HTTP-заголовок «Content-Length» размер тела (content) HTTP-запроса в байтах (в случае выше там указан размер в 6 байтов). По умолчанию в данном случае в HTTP-заголовок «Content-Type» ничего не было записано. Для работы с HTTP-заголовком «Content-Type» у командлета «Invoke-WebRequest» есть параметр -ContentType.

Для указания HTTP-метода у командлета «Invoke-WebRequest» есть параметр -Method. Этот параметр принимает одно из десяти возможных значений (для дополнительных HTTP-методов существует еще параметр -CustomMethod, но я его пока не буду использовать). По умолчанию командлет «Invoke-WebRequest» у меня формирует HTTP-запрос по методу «GET» (это видно в блоке кода выше). Хотя в документации сказано, что для этого параметра значения по умолчанию нет; поэтому, возможно, сто́ит указывать этот параметр всегда, чтобы не возникло каких-либо накладок.

2. Запись строки в тело HTTP-запроса при методе «POST»:

PS C:\> $Response = Invoke-WebRequest -URI "https://localhost/test.php" -Body "qwerty" -Method "POST"

POST /test.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 6
User-Agent: Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.19045; ru-RU) PowerShell/7.3.1
Host: localhost

qwerty

По сравнению с тестом 1 в тексте HTTP-запроса в блоке кода выше кроме названия HTTP-метода есть еще одно отличие - в заголовке «Content-Type». Смысл такого значения этого HTTP-заголовка рассмотрим в одном из следующих тестов.

Тип данных, передаваемых в параметр «Body»

В параметр -Body командлета «Invoke-WebRequest» можно поместить не только строку, как было показано выше, но также и, к примеру, хеш-таблицу (ассоциативный массив), то есть массив пар ключ-значение; это нам понадобится для общения с ЖЖ.

3. Запись хеш-таблицы в тело HTTP-запроса при методе «POST»:

PS C:\> $Response = Invoke-WebRequest -URI "https://localhost/test.php" -Body @{mode="login";user="test"} -Method "POST"

POST /test.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
User-Agent: Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.19045; ru-RU) PowerShell/7.3.1
Host: localhost

user=test&mode=login

Как видно из блока кода выше, при передаче через параметр -Body хеш-таблицы содержимое этой хеш-таблицы записывается в тело HTTP-запроса в виде строки формата, который я упоминал при описании интерфейса «flat» для общения с ЖЖ.

В блоке кода выше можно заметить, что в теле полученного HTTP-запроса заданные в хеш-таблице пары ключ-значение поменялись местами. Это следствие того, что в обычной хеш-таблице порядок пар ключ-значение не сохраняется (то есть пары ключ-значение могут поменяться местами, а могут и не поменяться). Но в программе-оболочке «PowerShell» при необходимости можно работать и с упорядоченными (ordered) хеш-таблицами, в которых порядок пар ключ-значение сохраняется. Например:

PS C:\> $body = [ordered]@{mode="login";user="test"}
PS C:\> $Response = Invoke-WebRequest -URI "https://localhost/test.php" -Body $body -Method "POST"

4. Попытка записи хеш-таблицы в тело HTTP-запроса при методе «GET»:

PS C:\> $Response = Invoke-WebRequest -URI "https://localhost/test.php" -Body @{mode="login";user="test"}

GET /test.php?user=test&mode=login HTTP/1.1
Content-Type:
Content-Length: 0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.19045; ru-RU) PowerShell/7.3.1
Host: localhost

Вот вам неожиданность. В коде выше мы передали данные в параметр -Body командлета «Invoke-WebRequest», а командлет вместо того, чтобы записать их в тело HTTP-запроса, записал их в параметры URL-адреса. Хотя, напомню, в тесте 1 нам удалось записать строку именно в тело HTTP-запроса с методом «GET».

Как видно из теста 4, командлет «Invoke-WebRequest» в зависимости от типа переданных в параметр -Body данных и типа HTTP-метода сам может решить, куда записать данные - в тело HTTP-запроса или в параметры URL-адреса. Это не ошибка, а особенность работы этого командлета (эта особенность описана в документации). Следует случай, показанный в тесте 4, иметь в виду при работе с этим командлетом.

Кодировка передаваемого текста

Насколько я понимаю, по умолчанию текст HTTP-запроса передается в кодировке ASCII (на самом деле, в документации к протоколу HTTP вопрос о кодировке разных частей HTTP-запроса имеет много тонкостей, которые я в этом посте рассматривать не буду). Меня пока что интересует вопрос о кодировке только текста, передаваемого в теле HTTP-запроса.

5. Попытка записи в тело HTTP-запроса русских букв (слово «Илья») при методе «POST»:

PS C:\> $Response = Invoke-WebRequest -URI "https://localhost/test.php" -Body "Илья" -Method "POST"

POST /test.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
User-Agent: Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.19045; ru-RU) PowerShell/7.3.1
Host: localhost

????

Как видно в блоке кода выше, веб-сервер вместо желаемого содержимого в теле HTTP-запроса получил что-то нечитаемое, скрипт на языке PHP со стороны веб-сервера записал в текстовый файл четыре знака вопроса вместо слова «Илья». Очевидно, что использовалась какая-то однобайтовая кодировка, а не UTF-8, так как в заголовке «Content-Length» указан размер тела HTTP-запроса - 4 байта. При использовании UTF-8 размер слова «Илья» в байтах составит 8 байтов.

Тут следует обратить внимание на значение заголовка «Content-Type» HTTP-запроса. Это значение я обещал разобрать позже, когда анализировал результат теста 2, и этот момент настал. Значение «application/x-www-form-urlencoded» подразумевает, что передаваться будут пары ключ-значение, разделяемые символом амперсанда &. В парах ключ-значение ключ и значение отделяются друг от друга символом знака «равно» =.

При этом в тексте названий ключей и значений ключей можно использовать буквы латинского алфавита и арабские цифры; для кодирования остальных символов (в нашем случае - букв русского алфавита) следует использовать так называемую «процентную кодировку» (по-английски «percent-encoding» или «URL encoding»).

Нам не нужно будет заниматься переводом текста в процентную кодировку, это может сделать командлет «Invoke-WebRequest», если он поймет, что это необходимо.

6. Передача в теле HTTP-запроса русских букв (слово «Илья») при методе «POST» с помощью хеш-таблицы (ассоциативного массива):

PS C:\> $Response = Invoke-WebRequest -URI "https://localhost/test.php" -Body @{mode="login";user="Илья"} -Method "POST"

POST /test.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 40
User-Agent: Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.19045; ru-RU) PowerShell/7.3.1
Host: localhost

mode=login&user=%D0%98%D0%BB%D1%8C%D1%8F

В блоке кода выше видно, что командлет «Invoke-WebRequest» при задаче параметров указанным выше образом смог разобраться, что слово «Илья» следует закодировать в процентной кодировке, и сделал это. То есть слово «Илья» в теле HTTP-запроса оказалось закодировано так:

Илья (4 символа)
%D0%98%D0%BB%D1%8C%D1%8F (в теле HTTP-запроса занимают 24 байта)
D0 98 D0 BB D1 8C D1 8F (представление без символа «знак процента»)

Люди, знакомые с кодировкой UTF-8, без труда распознают в этом коде шестнадцатеричные коды русских букв. Например, последовательность D0 98 обозначает в кодировке UTF-8 русскую букву «И», последовательность D0 BB - русскую букву «л» и так далее. Разница между процентной кодировкой и кодировкой UTF-8 в том, что в процентной кодировке для кодирования русских букв используется по 6 байтов, а в кодировке UTF-8 - по 2 байта.

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

Вернемся к результату теста 5. Как, всё же, можно передать текст в нужной кодировке в теле HTTP-запроса? Следует изменить нужным образом заголовок «Content-Type» HTTP-запроса.

7. Передача в теле HTTP-запроса русских букв (слово «Илья») при методе «POST», кодировка передаваемого в теле HTTP-запроса текста задается в заголовке (который, в свою очередь, задается в параметре командлета) «Content-Type»:

PS C:\> $Response = Invoke-WebRequest -URI "https://localhost/test.php" -Body "Илья" -Method "POST" -ContentType "text/plain; charset=UTF-8"

POST /test.php HTTP/1.1
Content-Type: text/plain; charset=UTF-8
Content-Length: 8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.19045; ru-RU) PowerShell/7.3.1
Host: localhost

Илья

Обратите внимание на значение заголовка «Content-Length» полученного HTTP-запроса - 8 байтов. В три раза меньше, чем при использовании процентной кодировки.

Выводы

В один пост не получается вместить все интересные тесты, которые можно выполнить с командлетом «Invoke-WebRequest». Но точно ясно, что этот командлет может быть прекрасным инструментом для изучения работы протокола HTTP(S) на практике.

Инструмент, Образование, Сайтостроение, Программирование, Английский язык

Previous post Next post
Up