Статьи

Groovy и HTTP

Эта статья первоначально появилась в декабрьском выпуске 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
String html = 'http://localhost:8081/groovy-http'.toURL().text

Листинг 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
    'http://google.com/notThere' | FileNotFoundException
}

Листинг 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.

Больше чтения

Ссылка: Groovy и HTTP от нашего партнера по JCG Келли Робинсон в блоге The Kaptain на… материале .