Статьи

Groovy и HTTP-серверы

Эта статья первоначально появилась в январском выпуске GroovyMag за 2013 год.

Нельзя отрицать, что Всемирная паутина стала абсолютно неотъемлемой частью хранения и доставки информации. Существует более 600 миллионов сайтов, обслуживающих более 80 миллиардов отдельных страниц, и каждый день добавляется еще больше страниц и веб-сервисов ( http://news.netcraft.com/archives/2012/09/10/september-2012-web- server-survey.html ). И за каждым сайтом есть — вы уже догадались! Веб-сервер. В настоящее время у нас есть большое количество альтернатив веб-сервера JVM для обслуживания контента, а также некоторые серьезные претенденты на Groovy и полиглоты. В этой статье я подробно расскажу о некоторых альтернативах с акцентом на встраиваемые параметры и опишу, что Groovy может сделать, чтобы упростить работу по сравнению с традиционной реализацией и настройкой только на Java. Что ж

настройте несколько разных серверов для размещения одной службы, которая обращает параметр запроса и возвращает результат. Все эти решения могут быть встроены в программу Groovy и практически не требуют внешней настройки.

Примечание. Некоторые из описанных решений подходят для производственной среды, а другие больше подходят для небольших задач, таких как обслуживание документов исключительно для внутренней сети или предоставление простых сред тестирования.

Тестовый проект

Чтобы обеспечить среду для поддержки нескольких веб-серверов и демонстрации различных HTTP-запросов, мы будем использовать сборку Gradle и несколько простых тестов Спока. Полный исходный код доступен по адресу https://github.com/kellyrob99/groovy-http, и я надеюсь, что вы клонируете копию, чтобы присмотреться. Это тот же проект, который ранее использовался (GroovyMag декабрь 2012 г.) для детализации Groovy для работы с http-клиентами, но расширен и для рассмотрения возможностей на стороне сервера. Он включает в себя оболочку Gradle, так что вы сможете проверить ее и запустить все тесты с помощью простого вызова ./gradlew build .

Java 1.6 HttpServer

Простейшая альтернатива для обслуживания контента без зависимостей от внешних библиотек в Java — HttpServer, включенный начиная с Java 1.6. Поддерживать сервер чрезвычайно просто, не требуя никакой внешней настройки или чего-то еще на самом деле. Вы просто создаете сервер, объявляете некоторые контексты (которые соответствуют путям) и назначаете обработчик для каждого контекста. Весь код Groovy для настройки сервера для размещения нашего «обратного» сервиса показан в листинге 1.

1
2
3
4
5
6
7
8
9
//configuring a Java 6 HttpServer
InetSocketAddress addr = new InetSocketAddress(HTTP_SERVER_PORT)
httpServer = com.sun.net.httpserver.HttpServer.create(addr, 0)
httpServer.with {
    createContext('/', new ReverseHandler())
    createContext('/groovy/', new GroovyReverseHandler())
    setExecutor(Executors.newCachedThreadPool())
    start()
}

Листинг 1: Настройка HttpServer в Groovy

Поэтому мы привязываемся к порту для входящих запросов, назначаем обработчик для всех запросов в корневом пути контекста, настраиваем сервер с пулом потоков и запускаем его. Единственная часть, которую мы должны предоставить, — это обработчики, из которых версия Java, реализующая HttpHandler, показана в листинге 2. Все, что она делает, это возвращает единственный ожидаемый параметр ‘string’ в обратном порядке. Он также выполняет простую обработку ошибок в случае, если параметр отсутствует, и возвращает в этом случае код запроса HTTP 400 Bad.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ReverseHandler implements HttpHandler {
    @Override
    public void handle(HttpExchange httpExchange) throws IOException
    {
        String requestMethod = httpExchange.getRequestMethod();
        if (requestMethod.equalsIgnoreCase('GET')) {
            Headers responseHeaders = httpExchange.getResponseHeaders();
            responseHeaders.set('Content-Type', 'text/plain');
            OutputStream responseBody = httpExchange.getResponseBody();
 
            final String query = httpExchange.getRequestURI().getRawQuery();
            if (query == null || !query.contains('string')) {
                httpExchange.sendResponseHeaders(400, 0);
                return;
            }
 
            final String[] param = query.split('=');
            assert param.length == 2 && param[0].equals('string');
 
            httpExchange.sendResponseHeaders(200, 0);
            responseBody.write(new StringBuffer(param[1]).reverse().toString().getBytes());
            responseBody.close();
        }
    }
}

Листинг 2: Простой обработчик запросов HttpServer

Мы можем сделать это несколько менее многословным, кодируя обработчик в Groovy (см. GroovyReverseHandler в исходном коде), но API очень низкого уровня делает разницу в этом примере довольно небольшой. Что еще более важно, поскольку мы можем кодировать как реализацию HttpHandler, так и код сервера в одном скрипте Groovy, мы можем легко запустить простой веб-сервер из командной строки, например groovy server.groovy
Вы не захотите использовать его для размещения всего веб-сайта, но он идеально подходит для обслуживания небольших объемов контента, предоставления простых сервисов или, возможно, макетирования сервисов для тестирования клиентской реализации.

Встроенный причал

Это полное решение для включения всех возможностей Jetty в ваше приложение. Поскольку Jetty является контейнером сервлетов, мы можем немедленно использовать GroovyServlet, доступный в стандартном дистрибутиве Groovy, и обслуживать Groovlets, которые можно динамически создавать и изменять во время выполнения. Сначала давайте настроим сервер и контекст Jetty 8 для обслуживания файлов с помощью GroovyServlet, как показано в листинге 3.

01
02
03
04
05
06
07
08
09
10
11
12
//configuring Jetty 8 with GroovyServlet support
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS)
context.with {
    contextPath = '/'
    resourceBase = 'src/main/webapp'
    addServlet(GroovyServlet, '*.groovy')
}
jettyServer = new Server(JETTY_SERVER_PORT)
jettyServer.with {
    setHandler(context)
    start()
}

Листинг 3: Настройка Jetty 8 с помощью GroovyServlet

Это подаст любые файлы в каталоге src / main / webapp с суффиксом .groovy. Эти файлы компилируются на лету, и GroovyServlet определяет, были ли файлы изменены, чтобы он мог перекомпилировать их при необходимости. Для целей нашего простого «обратного» сервиса код в листинге 4 демонстрирует реализацию Groovlet. Поскольку в Groovlet наш вывод подключен к выходному потоку сервлета, простого println достаточно для записи ограниченного ответа. Это простые вещи, которые заставляют вас почти забыть, что вы кодируете сервлет, так как большая часть обычного шаблонного кода, используемого для кодирования, не требуется.

1
2
3
4
5
6
7
8
import javax.servlet.http.HttpServletResponse
 
final string = request.parameterMap.string
if (!string || string.size() != 1){
    response.setStatus(HttpServletResponse.SC_BAD_REQUEST)
    return
}
print URLDecoder.decode(string[0], 'UTF-8').reverse()

Листинг 4: Groovlet, который возвращает переданный параметр в обратном порядке

Обратите внимание, что в этом случае переданные параметры для нас не маршалируются и доступны в переменной request.parameterMap. И я надеюсь, что вы согласны с тем, что эта реализация значительно менее многословна и проще для понимания, чем HttpHandler, который мы определили ранее, чтобы делать то же самое в Java.
Возможно, еще важнее то, что весь этот веб-сервер можно определить и выполнить как скрипт Groovy с помощью одной аннотации @Grab. Полный скрипт показан в листинге 5.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@Grab('org.eclipse.jetty.aggregate:jetty-all-server:8.1.0.v20120127')
import org.eclipse.jetty.servlet.ServletContextHandler
import groovy.servlet.GroovyServlet
import org.eclipse.jetty.server.Server
 
int JETTY_SERVER_PORT = 8094
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS)
context.with {
    contextPath = '/'
    resourceBase = 'src/main/webapp'
    addServlet(GroovyServlet, '*.groovy')
}
jettyServer = new Server(JETTY_SERVER_PORT)
jettyServer.with {
    setHandler(context)
    start()
}

Листинг 5: Groovy-скрипт, запускающий веб-сервер Jetty менее чем в 20 строк

Jetty — это очень универсальная серверная платформа, и Groovy позволяет с легкостью вставать и работать. Полный набор возможностей определенно выходит за рамки этой статьи, но если вы можете сделать это с Jetty в простой среде Java, вы можете сделать это и в Groovy — просто с меньшим набором текста

А с Just-Java вам определенно нужно будет настроить некоторые дополнительные части, чтобы начать работу, тогда как Groovy может сделать это, используя только инструменты по умолчанию, которые поставляются вместе с дистрибутивом (и, конечно, интернет-соединение). Если вам нужен легкий веб-сервер для каких-либо целей, это будет моим первым предложением. Да, и в отличие от развертывания Jetty в автономном режиме, конфигурация XML не требуется — в моих книгах всегда есть бонус.

Рестлет и Groovy-Рестлет

Restlet — это платформа, специально разработанная для быстрого и надежного создания приложений RESTful. Я лично не очень знаком с ним, но, разработав и работая со многими различными API REST, я определенно ценю идею фреймворка, разработанного специально для варианта использования. Java-классы в API-интерфейсе Restlet напрямую связаны с концепциями REST, что упрощает реализацию требуемых сервисов, абстрагируя от любого конкретного протокола, используемого для связи между ресурсами. Проект Groovy-Restlet добавляет построенную DSL, расширяющую класс Groovy FactoryBuilderSupport к уравнению. В простейшем случае мы создаем объект GroovyRestlet и настраиваем нашу систему Restlet, используя внешний файл Groovy, выражающий DSL. В листинге 6 показан код, используемый для начальной загрузки приложения Restlet. Обратите внимание, как мы передаем переменную для порта, который будет использоваться во время оценки скрипта.

1
2
3
4
//configuring a Restlet Server and Client using an external dsl file
GroovyRestlet gr = new GroovyRestlet()
gr.builder.setVariable('port', RESTLET_SERVER_PORT)
(restletClient, restletServer) = gr.build(new File('src/test/resources/restlet/reverseRestlet.groovy').toURI()) as List

Листинг 6: Инициализация приложения Restlet в Groovy

Здесь мы используем функцию множественного назначения Groovy, чтобы мы могли возвращать дескрипторы как объектам org.restlet.Server, так и org.restlet.Client, созданным в сценарии. Сценарий DSL, показанный в листинге 7, показывает, как эти объекты инициализируются. Это наш «обратный» сервис, реализованный с использованием некоторых тонкостей Restlet, включая простой анализ параметров с использованием абстракции формы, простую обработку статуса HTTP и возможность немедленного создания соответствующего клиента для сервера.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
import org.restlet.data.*
 
def myPort = builder.getVariable('port')
def server = builder.server(protocol: protocol.HTTP, port: myPort) {
    restlet(handle: {Request req, Response resp ->
        Form form = req.resourceRef.queryAsForm
        if (form.isEmpty() || !form[0].name == 'string') {
            resp.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, 'Missing 'string' param')
        }
        else {
            resp.setEntity(form[0].value.reverse(), mediaType.TEXT_PLAIN)
        }
    })
}
server.start();
 
def client = builder.client(protocol: protocol.HTTP)
 
[client, server] //return a list so we can work with the client and eventually stop the server

Листинг 7: Конфигурация Groovy-Restlet DSL

Мы можем проверить поведение клиента на правильность выполнения и на наличие ошибок, используя Spock, как показано в листингах 8 и 9 соответственно.

1
2
3
4
5
6
7
def 'restlet reverse test'() {
    when: 'We use the Restlet Client to execute a GET request against the Restlet Server'
    String response = restletClient.get('http://localhost:$RESTLET_SERVER_PORT/?string=$TEST_STRING').entity.text
 
    then: 'We get the same text back in reverse'
    TEST_STRING.reverse() == response
}

Листинг 8: Выполнение GET-запроса с клиентом Restlet

Restlet также предоставляет несколько удобных методов для проверки состояния ошибок и обмена сообщениями в листинге 9.

01
02
03
04
05
06
07
08
09
10
11
def 'restlet failure with client error'() {
    when: 'We forget to include the required parameter to Restlet'
    org.restlet.data.Response response = restletClient.get('http://localhost:$RESTLET_SERVER_PORT')
 
    then: 'An exception is thrown and we get an HTTP 400 response indicated as a client error'
    response.status.isClientError()
    !response.status.isServerError()
    response.status.code == 400
    response.status.description == MISSING_STRING_PARAM
    null == response.entity.text
}

Листинг 9: Выполнение ошибочного GET-запроса с клиентом Restlet

Groovy-Restlet DSL позволяет довольно легко настроить приложение Restlet, но я не обязательно рекомендую его для использования в «реальном мире». С одной стороны, он работает только с более старой версией Рестлета, а с другой стороны, он не поддерживается активно. Доступна редкая документация, но это не проблема, если вы хотите проверить и прочитать очень маленькую реализацию и доступные примеры. Было бы неплохо увидеть этот проект обновленным, чтобы использовать последнюю версию Рестлета, и в этом случае было бы намного привлекательнее идти в ногу с этим. Тем не менее, вы все еще можете развернуть полный веб-сервер, используя все эти технологии в одном скрипте Groovy.

Встроенный vert.x

Проект vert.x (http://vertx.io/) описывает себя как:

«Vert.x — это платформа для следующего поколения асинхронных, легко масштабируемых, параллельных приложений.
Vert.x — это прикладная среда, управляемая событиями, которая работает на JVM — среда выполнения с реальным параллелизмом и непревзойденной производительностью. Затем Vert.x предоставляет API в Ruby, Java, Groovy, JavaScript и Python. Таким образом, вы выбираете, какой язык вы хотите использовать. Поддержка Scala и Clojure также включена в план ».

По сути, vert.x предоставляет серверную платформу на основе шаблонов Reactor, которая поддерживает программирование полиглотов на самом низком уровне. Это также рекламировалось как полиглот JVM, альтернативный Node.js. Вы можете установить vert.x локально, а затем использовать его программу командной строки vertx для загрузки спецификаций из файлов, написанных на Groovy, JavaScript, Ruby и других языках. Или вы можете встроить библиотеку в выбранную программу JVM и настроить ее непосредственно в коде. По крайней мере, для Groovy синтаксис практически идентичен и выглядит так, как показано в листинге 10 для настройки объекта org.vertx.groovy.core.http.HttpServer.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
Vertx vertx = Vertx.newVertx()
final org.vertx.groovy.core.http.HttpServer server = vertx.createHttpServer()
server.requestHandler { HttpClientRequest req ->
    if (req.params['string'] == null) {
        req.response.with {
            statusCode = 400
            statusMessage = MISSING_STRING_PARAM
            end()
        }
    }
    else {
        req.response.end(req.params['string'].reverse())
    }
 
}.listen(VERTX_PORT, 'localhost')

Листинг 10: Конфигурирование vert.x HttpServer в Groovy

Vert.x автоматически отменяет маршалинг параметров в Map для нас, а класс HttpClientRequest, используемый в обработчике, предоставляет доступ к множеству удобных методов взаимодействия с объектами запроса и ответа.
Создание объекта org.vertx.groovy.core.http.HttpClient еще проще и продемонстрировано в листинге 11.

1
def client = vertx.createHttpClient(port: VERTX_PORT, host: 'localhost')

Листинг 11: Один вкладыш для создания vert.x HttpClient в Groovy

Взаимодействие с этим клиентом очень простое и опять-таки предоставляет удобные методы для работы с ответом, включая буферизацию возвращаемых данных. Следует отметить, что в этом конкретном примере мы отрицаем это последнее преимущество, вызывая toString () для возвращаемого буфера для удобства. Утверждения встроены в код в листинге 12, так как я извлекаю его непосредственно из теста Спока, в котором используется клиент vert.x.

01
02
03
04
05
06
07
08
09
10
11
client.getNow('/') { resp ->
    400 == resp.statusCode
    MISSING_STRING_PARAM == resp.statusMessage
}
 
client.getNow('/?string=$TEST_STRING') { resp ->
    200 == resp.statusCode
    resp.dataHandler { buffer ->
        TEST_STRING.reverse() == buffer.toString()
    }
}

Листинг 12: Выполнение GET-запросов на прохождение и невыполнение условий с использованием клиента vert.x в тесте Спока

Это в значительной степени простейший возможный пример, и он не очень хорошо демонстрирует возможности vert.x. Платформа может похвастаться общедоступным репозиторием для совместного использования и доступа к модулям, встроенной шиной событий для взаимодействия внутри и снаружи и моделью параллелизма, которая позволяет вам забыть о синхронизации кода и сосредоточиться на бизнес-логике, среди прочего. Такие асинхронные серверы, как этот, и node.js почти наверняка будут продолжать играть большую роль в Интернете с огромным увеличением использования веб-сервисов. Они дают некоторые ответы на классические проблемы масштабирования и очень естественно соответствуют новым технологическим требованиям, таким как WebSockets.

Обратите внимание, что поскольку vert.x зависит от асинхронных функций NIO в Java 7, он будет работать только с версиями Java выше 1.7

Другие альтернативы

Вряд ли это исчерпывающий список платформ веб-серверов, которые в настоящее время поддерживают возможности Groovy и / или polyglot. Некоторые другие включают в себя:

  • Graffitti вдохновлен сервером Ruby Sinatra и полностью реализован в Groovy. Проект размещен по адресу https://github.com/webdevwilson/graffiti.
  • Ratpack снова является Groovy-портом сервера Sinatra. Проект размещен по адресу https://github.com/tlberglund/Ratpack.
  • Google App Engine можно использовать для обслуживания сервлетов / Groovlets, а платформа Gaelyk значительно упрощает взаимодействие с доступными службами Google. Gaelyk размещен на сайте https://github.com/gaelyk/gaelyk.
  • Gretty — очень многообещающая оболочка Groovy для сервера Java Netty ( https://netty.io/ ), предоставляющая DSL для упрощенного объявления и настройки компонентов Netty. К сожалению, этот проект выглядит в основном бездействующим и не работает с более новыми версиями Groovy. Код размещен по адресу https://github.com/groovypp/gretty . Vert.x также использует Netty под капотом, чтобы добиться цели.

Как обычно, все, что вы можете сделать в Java, можно сделать и в Groovy. Мы рассмотрели некоторые из доступных вариантов создания и взаимодействия с различными веб-серверами с открытым исходным кодом, которые работают на JVM. Все больше и больше поддерживается программирование полиглотов на JVM, и, надеюсь, эта статья дала вам несколько идей по использованию Groovy, чтобы помочь вам более продуктивно разрабатывать веб-серверы. В частности, там, где конкретная платформа предоставляет свободный интерфейс или DSL (как это делают Groovy-Restlet и vert.x), преимущества становятся очевидными. Лично для меня основные преимущества можно обобщить так:

  • меньше кода для поддержки из-за базового синтаксического кода Groovy для общих функций и доступности DSL для краткого создания выразительного кода
  • устранение необходимости в конфигурации XML, распространенной в большинстве сред развертывания веб-сервера
  • возможность инкапсулировать все функциональные возможности в один сценарий для развертывания, в зависимости только от наличия Groovy, доступного для запуска сценария

Пожалуйста, попробуйте некоторые из этих идей, и я хотел бы услышать от вас отзывы о вашем собственном опыте использования Groovy и HTTP.

Учить больше

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