Эта статья первоначально появилась в декабрьском выпуске GroovyMag за 2012 год.
Некоторые различные способы, которыми Groovy упрощает взаимодействие с сетью
Одним из основных преимуществ Groovy является то, как он упрощает некоторые распространенные сценарии, с которыми мы имеем дело в Java. Сложный код с условными выражениями, обработкой ошибок и многими другими проблемами может быть выражен очень кратким и легко понятным способом. В этой статье будут рассмотрены некоторые удобные Groovy-измы, связанные с взаимодействием с контентом через HTTP. Сначала мы рассмотрим некоторые синтаксические символы, добавленные в стандартные классы Java, которые упрощают запросы GET и POST, а затем мы посмотрим, как
Модуль HTTPBuilder предоставляет DSL для использования библиотеки HttpClient .
Тестовый проект
Чтобы обеспечить среду для размещения веб-сайта и демонстрации различных HTTP-запросов, мы будем использовать плагин Gradle Jetty и несколько простых Groovlets . Полный исходный код доступен по адресу https://github.com/kellyrob99/groovy-http, и я надеюсь, что вы клонируете копию, чтобы присмотреться. Простая страница указателя содержит контент «hello world», показанный в листинге 1.
1
2
3
4
5
6
7
8
9
|
<! DOCTYPE html> < html > < head > < title >Groovy HTTP</ title > </ head > < body > < p >hello world</ p > </ body > </ html > |
Листинг 1. Наша индексная страница «Привет, мир», используемая для тестирования
Мы начнем с самых простых доступных методов взаимодействия с HTTP с использованием Groovy и без дополнительной поддержки библиотеки.
Groovy методы добавлены в строку и URL
Класс DefaultGroovyMethods предоставляет несколько очень удобных методов для улучшения работы по умолчанию классов String и URL. В частности, для String у нас есть новый метод toURL () и, для URL, свойство text. Кроме того, класс URL расширен удобными методами для работы со связанными InputStream и OutputStreams.
String.toURL ()
Это небольшой выигрыш, так как все, что вы действительно делаете, — это избегаете обращения к новому URL (спецификация String) . Разница в нажатиях клавиш невелика, но в сочетании с некоторыми другими преимуществами Groovy для MetaClass она может быть очень полезна для создания свободного и понятного кода.
URL.text ()
Это, казалось бы, небольшое дополнение к API класса URL абстрагирует от множества стандартных шаблонов, используемых для потоковой передачи контента по URLConnection . Под капотом находится очень разумная реализация, которая буферизует базовое соединение и автоматически обрабатывает закрытие всех ресурсов для вас. Для большинства случаев использования поведения по умолчанию, вероятно, будет достаточно, но, если нет, существуют перегруженные методы URL.text (String charset) и URL.text (параметры карты, String charset), которые позволяют изменять и обрабатывать больше специфических особенностей соединения конфигурации.
Однострочный вызов в листинге 2 демонстрирует, как загрузить html-страницу, возвращая необработанный html в виде строки.
1
|
|
Листинг 2. Один вкладыш для инициирования HTTP-запроса GET для html-страницы
По-прежнему многое может пойти не так, если использовать этот сокращенный синтаксис для HTTP-запроса, поскольку может быть выдано несколько исключений в зависимости от того, правильно ли отформатирован URL-адрес или если указанное содержимое не существует. Тест Спока, показанный в листинге 3, выполняет оба эти условия. Обратите внимание, что ответ 404 приведет к исключению FileNotFoundException.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@Unroll ( 'The url #url should throw an exception of type #exception' ) def 'exceptions can be thrown converting a String to URL and accessing the text' () { when: String html = url.toURL().text then: def e = thrown(exception) where: url | exception 'htp://foo.com' | MalformedURLException } |
Листинг 3: Тест Спока, показывающий некоторые возможные условия отказа для нашего запроса GET
Для сравнения давайте посмотрим, как выглядит тот же GET-запрос с использованием URL-адреса в Java, показанного в листинге 4.
01
02
03
04
05
06
07
08
09
10
11
|
URLConnection urlConnection = html.openConnection(); BufferedReader reader = new BufferedReader( new InputStreamReader(urlConnection.getInputStream())); StringBuffer response = new StringBuffer(); String inputLine; while ((inputLine = reader.readLine()) != null ) { response.append(inputLine) } reader.close(); |
Листинг 4: Java-версия чтения из URLConnection (на основе канонического примера из Oracle.com)
В версии Java до сих пор нет обработки ошибок, которая, очевидно, является гораздо более подробным способом загрузки тех же данных.
POST с потоками URL
Подобно упрощению запросов GET, выполнение POST с использованием Groovy может использовать некоторые из усовершенствований общих классов Java. В частности, упрощенная обработка потоков обеспечивает точное, корректное и выразительное кодирование. В листинге 5 показан тест Спока для настройки URLConnection, размещения некоторых данных и считывания результата соединения.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
private static final String POST_RESPONSE = 'Successfully posted [arg:[foo]] with method POST' def 'POST from a URLConnection' () { when: final HttpURLConnection connection = makeURL( 'post.groovy' ).toURL().openConnection() connection.setDoOutput( true ) connection.outputStream.withWriter { Writer writer -> writer << 'arg=foo' } String response = connection.inputStream.withReader { Reader reader -> reader.text } then: connection.responseCode == HttpServletResponse.SC_OK response == POST_RESPONSE } |
Листинг 5: POST-запрос с использованием Groovy и URLConnection
Обратите внимание, что нам не нужно явно приводить соединение к HttpUrlConnection , чтобы вернуть responseCode, и что нам не нужно явно закрывать какие-либо из используемых потоков. Кроме того, нам не нужно создавать локальные переменные для объекта Reader / Writer, как это было бы в Java; аналогичным образом, вызовы «new» не требуются, поскольку создание объектов скрыто за удобными методами. Эквивалентный код Java требует четырех вызовов new и двух close (), а также гораздо более сложного кода для извлечения результата. Канонический пример того, как сделать это в Java, можно увидеть на http://docs.oracle.com/javase/tutorial/networking/urls/readingWriting.html.
Обратите внимание, что вы также можете очень легко анализировать содержимое ответов, используя классы XmlSlurper / XmlParser и JsonSlurper, включенные в стандартный дистрибутив Groovy.
HttpClient и HTTPBuilder делают вещи еще проще
Реальность такова, что в большинстве современных приложений Java есть несколько хороших альтернатив прямой работе с объектами URL и URLConnection для работы с HTTP. Одна из наиболее популярных библиотек — это HttpClient и его преемник HttpComponents . Предоставляются оболочки для всех HTTP-глаголов, что упрощает настройку, выполнение и потребление ответов. В листинге 6 показан тест Спока с использованием HttpClient и зеркалирование наших предыдущих примеров GET.
01
02
03
04
05
06
07
08
09
10
|
def 'HttpClient example in Java' () { when: HttpClient httpclient = new DefaultHttpClient(); HttpGet httpget = new HttpGet(makeURL( 'helloWorld.groovy' )); ResponseHandler<String> responseHandler = new BasicResponseHandler(); String responseBody = httpclient.execute(httpget, responseHandler); then: responseBody == HELLO_WORLD_HTML } |
Листинг 6: Пример HttpClient GET
Это может быть дополнительно уменьшено, если нет необходимости хранить промежуточные переменные вокруг. Фактически, мы можем привести его к единственной строке, показанной в листинге 7.
1
|
String response = new DefaultHttpClient().execute( new HttpGet(makeURL( 'helloWorld.groovy' )), new BasicResponseHandler()) |
Листинг 7: HttpClient GET однострочный
Это, очевидно, намного проще для глаз и очень ясно в намерениях. Библиотека HttpClient также имеет удобные механизмы для объявления общего поведения между соединениями, API для предоставления пользовательских реализаций анализа ответов и автоматической обработки (большей части) базовых потоков ресурсов и соединений. Для тех из нас, кто использует Groovy, есть хорошая оболочка для HttpClient под названием HTTPBuilder, которая добавляет механизм конфигурации в стиле DSL и некоторые очень приятные функции с точки зрения обработки ошибок и анализа содержимого. В листинге 8 снова показан наш стандартный пример GET, на этот раз работающий с объектом с именем http, назначенным из нового HTTPBuilder (Object uri) . Обратите внимание, что мы используем функцию множественного назначения Groovy для возврата и назначения нескольких значений из нашего Closure.
01
02
03
04
05
06
07
08
09
10
|
def 'GET with HTTPBuilder' () { when: def (html, responseStatus) = http.get(path: 'helloWorld.groovy' , contentType: TEXT) { resp, reader -> [reader.text, resp.status] } then: responseStatus == HttpServletResponse.SC_OK html == HELLO_WORLD_HTML } |
Листинг 8: Тест Спока, показывающий поддержку GET Groovy HTTPBuilder
Если вы заметили в листинге 8, я явно установил запрос с помощью contentType: TEXT , потому что HTTPBuilder по умолчанию обеспечивает автоматическое обнаружение и анализ типа содержимого ответа. Поскольку я запрашиваю XML-документ, HTTPBuilder может автоматически анализировать результат с помощью Groovy XmlSlurper. HTTPBuilder также может обнаружить, что это html-страница, и сначала пропустить ответ через NekoHTML, чтобы убедиться, что вы работаете с правильно сформированным документом. В листинге 9 показано небольшое различие в том, как мы можем взаимодействовать с анализируемым содержимым ответа, и читатель в нашем закрытии из листинга 8 спокойно заменяется GPathResult, ссылающимся на проанализированный контент.
01
02
03
04
05
06
07
08
09
10
11
|
def 'GET with HTTPBuilder and automatic parsing' () { when: def (html, responseStatus) = http.get(path: 'helloWorld.groovy' ) { resp, reader -> [reader, resp.status] } then: responseStatus == HttpServletResponse.SC_OK html instanceof GPathResult html.BODY.P.text() == 'hello world' } |
Листинг 9: автоматическое обнаружение и анализ xml
Маловероятно, что вы будете таким образом анализировать много html, но с обилием доступных в настоящее время служб xml автоматизированный анализ может быть очень полезным. То же самое относится и к JSON, и если мы дадим подсказку относительно contentType, мы можем получить обратно проанализированный JSONObject при взаимодействии с такими сервисами, как показано в листинге 10.
01
02
03
04
05
06
07
08
09
10
11
|
def 'GET with HTTPBuilder and automatic JSON parsing' () { when: def (json, responseStatus) = http.get(path: 'indexJson.groovy' , contentType: JSON) { resp, reader -> [reader, resp.status] } then: responseStatus == HttpServletResponse.SC_OK json instanceof JSONObject json.html.body.p == 'hello world' } |
Листинг 10: автоматический анализ ответов JSON
Модуль HTTPBuilder также имеет несколько удобных методов для обработки условий сбоя. Позволяя указать как обработчики ошибок по умолчанию, так и определенное поведение для отдельных запросов, у вас есть много вариантов в вашем распоряжении. В листинге 11 показано, как определить обработчик ошибок по умолчанию, который просто перехватывает код ответа. Обратите внимание, что Closure, используемый для получения ответа GET, никогда не запускается, поскольку в этом случае запрашиваемая страница приводит к коду ответа HTTP 404 Not Found.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
def 'GET with HTTPBuilder and error handling' () { when: int responseStatus http.handler.failure = { resp -> responseStatus = resp.status } http.get(path: 'notThere.groovy' , contentType: TEXT) { resp, reader -> throw new IllegalStateException( 'should not be executed' ) } then: responseStatus == HttpServletResponse.SC_NOT_FOUND } |
Листинг 11: Определение обработчика ошибок с помощью HTTPBuilder
Размещать данные с помощью HTTPBuilder также очень просто, для этого требуется только дополнительный параметр тела, как показано в листинге 12.
01
02
03
04
05
06
07
08
09
10
|
def 'POST with HTTPBuilder' () { when: def (response, responseStatus) = http.post(path: 'post.groovy' , body: [arg: 'foo' ]) { resp, reader -> [reader.text(),resp.status] } then: responseStatus == HttpServletResponse.SC_OK response == POST_RESPONSE } |
Листинг 12: POST с использованием HTTPBuilder
HTTPBuilder также предоставляет некоторые более конкретные абстракции для работы с определенными сценариями. Есть RESTClient для работы с веб-сервисами RESTful в упрощенном виде, есть AsyncHTTPBuilder для асинхронного выполнения запросов и для Google App Engine, который не разрешает соединения на основе сокетов, есть HttpURLClient, который охватывает использование HttpUrlConnection.
Вывод
Надеемся, что это дало вам представление о том, что Groovy может сделать, чтобы помочь вам с HTTP-взаимодействиями, и дает вам некоторые идеи сделать свои собственные клиентские приложения HTTP немного Groovier.
Больше чтения
- http://docs.oracle.com/javase/tutorial/networking/urls/readingWriting.html
- http://stackoverflow.com/questions/2793150/how-to-use-java-net-urlconnection-to-fire-and-handle-http-requests
- Веб-сайт HTTPBuilder http://groovy.codehaus.org/modules/http-builder/
- Сайт HttpComponents http://hc.apache.org/index.html
- Исходный код, который идет вместе с этой статьей на github по адресу https://github.com/kellyrob99/groovy-http . Этот проект включает в себя оболочку Gradle, так что вы сможете просто клонировать репозиторий и начать использовать его без установки какого-либо дополнительного программного обеспечения (кроме Java, конечно). Вы можете запустить все тесты с помощью ./gradlew clean build и запустить веб-сервер с помощью ./gradlew jettyRun
Ссылка: Groovy и HTTP от нашего партнера по JCG Келли Робинсон в блоге The Kaptain на… материале .