Статьи

HTTP и HTTP / S Прокси с Jetty

Вступление

Я  говорил  ранее  о Jetty как о встроенном контейнере сервлетов. Jetty также включает в себя некоторые полезные реализации служебных сервлетов, одна из которых ProxyServlet.

ProxyServlet это способ создать HTTP или HTTP / S прокси в несколько строк кода. Несмотря на то, что он является частью проекта Jetty, он является модульным и не зависит от сервера Jetty, поэтому его можно использовать даже в тех случаях, когда сервлет не будет работать в Jetty.

мотивация

Зачем вам нужен прокси сервлет? Одной из причин является решение вопросов, поднятых той  же политикой происхождения . Как правило, скрипт, загруженный с одного сайта, не может делать запросы с другого сайта. Хотя можно обойти это (например, с помощью  JSONP ), я склонен считать, что прокси-сервер является более элегантным решением, поскольку он не требует использования дыры для загрузки и оценки произвольного JavaScript.

Прокси-сервер также может быть полезен для предоставления пользователю доступа к веб-службе без предоставления всей информации, необходимой для доступа к ней. В нашем примере мы предоставим прокси-сервер для API Google Адресов, не отправляя ключ Google API в браузер.

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

пример

Этот пример является частью  приложения Spring WebMVC, которое  я использую для представления WebMVC и REST для класса Java. Я добавил  PlacesProxyServletи простую HTML-страницу, чтобы продемонстрировать выборку результатов поиска Google Адресов и их использование в jQuery.

Maven POM

Для начала нам нужно  jetty-proxy по нашему  pom.xml. До Jetty 9 ProxyServlet класс жил  jetty-servlets, но он был перенесен, возможно, для уменьшения других зависимостей Jetty, которые должны быть задействованы.

<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-proxy</artifactId>
    <version>${jetty.version}</version>
</dependency>

Джава

Далее мы создаем класс, который расширяется  ProxyServlet. Нам нужно знать правильный URI для использования в Google Адресах, и нам нужен ключ Google API. Лучший способ справиться с этим — разрешить их передачу из контекста сервлета с помощью  init-param, но я также хотел бы разрешить их переопределение с помощью системных свойств Java. Начнем с переопределения  init() метода:

public void init() throws ServletException {
    super.init();
    ServletConfig config = getServletConfig();
    placesUrl = config.getInitParameter("PlacesUrl");
    apiKey = config.getInitParameter("GoogleApiKey");
    // Allow override with system property
    try {
        placesUrl = System.getProperty("PlacesUrl", placesUrl);
        apiKey = System.getProperty("GoogleApiKey", apiKey);
    } catch (SecurityException e) {
    }
    if (null == placesUrl) {
        placesUrl = "https://maps.googleapis.com/maps/api/place/search/json";
    }
}

Чтобы на самом деле прокси запросы, ключевой метод  rewriteURI. Опять же, это ново для Jetty 9; ранее существовал метод,  proxyHttpURI который выполнял почти ту же функцию.

protected URI rewriteURI(HttpServletRequest request) {
    String query = request.getQueryString();
    return URI.create(placesUrl + "?" + query + "&key=" + apiKey);
}

Этот метод возвращает «реальный» URI, который будет вызывать сервлет прокси Jetty. Все данные из запроса клиента доступны. В этом случае нам просто нужны параметры запроса браузера, чтобы мы могли передать их в Google Places.

Tweaks

Чтобы заставить его работать с API Google Адресов, потребовалось еще несколько изменений. Во-первых, API Places обеспечивает HTTP / S. Обратите внимание, что это не означает, что наш клиент должен подключаться к нашему сервлету прокси, используя HTTP / S; Обычный HTTP отлично подходит для этого соединения, потому что наш прокси-сервлет устанавливает совершенно новое HTTP / S-соединение (используя HttpClientкласс Jetty  ). Тем не менее, это означает, что мы должны сказать Jetty,  HttpClient что все в порядке, чтобы использовать HTTP / S. Мы делаем это путем переопределения метода, который ProxyServlet класс использует для создания нового  HttpClient:

protected HttpClient newHttpClient() {
    SslContextFactory sslContextFactory = new SslContextFactory();
    HttpClient httpClient = new HttpClient(sslContextFactory);
    return httpClient;
}

Во-вторых, Google Places не понравился тот факт, что прокси-сервлет Jetty добавляет Host к запросу заголовок с именем исходного сервера. С этим заголовком сервер Google Адресов возвращает 404 в ответ на запрос. К счастью, это легко исправить; мы просто должны удалить этот заголовок, прежде чем запрос будет отправлен. Мы можем сделать это, переопределив  customizeProxyRequestметод, который  ProxyServlet вдумчиво предусматривает именно такую ​​проблему:

protected void customizeProxyRequest(Request proxyRequest,
        HttpServletRequest request) {
    proxyRequest.getHeaders().remove("Host");
}

Обновления для web.xml

Чтобы запустить и запустить этот сервлет, нам нужно добавить его в  web.xml. В случае примера приложения это потребовало обновления до Servlet 3.0, так как сервлет прокси Jetty хочет использовать асинхронные соединения. Это хорошо с точки зрения увеличения числа одновременных запросов, которые может обрабатывать прокси-сервлет, но для этого необходимо включить эту функцию в  web.xml:

<servlet>
    <servlet-name>PlacesProxy</servlet-name>
    <servlet-class>org.anvard.webmvc.server.PlacesProxyServlet</servlet-class>
    <init-param>
      <param-name>GoogleApiKey</param-name>
      <param-value>YOUR_KEY_HERE</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
</servlet>

<servlet-mapping>
    <servlet-name>PlacesProxy</servlet-name>
    <url-pattern>/places</url-pattern>
</servlet-mapping>

async-supported Тег имеет важное значение; сервлет прокси не будет работать без него.

Интерфейс браузера

На стороне браузера нам нужен способ сделать запрос и затем отобразить результаты. Я каннибализировал некоторые примеры HTML и JavaScript, которые у меня были, которые делали нечто похожее с CometD. (К сожалению, я не могу найти исходный источник для предоставления обратной ссылки.) Соответствующая часть jQuery выглядит следующим образом:

$.getJSON("/places?location=39.016249,-77.122993&radius=1000&types=food&sensor=false",
    function ( data ) {
        console.log( data );
        for (i = 0; i < data.results.length; i++) {
            result =data.results[i];
            $('<li>').html(result.name + '<br>' + result.vicinity).appendTo('#contentList');
        }
    })
    .fail(function() {
        console.log( "error" );
    })
    .always(function() {
        $("#status").text("Complete.");
    });

JQuery выполняет AJAX-вызов сервлета-посредника, который затем вызывает Google Places. Полученные данные ответа JSON отправляются как есть. Затем вызывается (анонимная) функция «success». Он просматривает возвращаемые результаты, добавляя  <li> теги в существующий список для каждого найденного результата.

Вывод

Конечно, прокси-сервлет не нужно использовать для сайтов в Интернете. Одним из мотивов создания  примера приложения  было показать, как легко было включить REST для существующего автономного приложения Java. Многие системы, использующие Java, имеют несколько автономных приложений Java, каждое из которых выполняет некоторую независимую функцию. Это затруднит создание единого унифицированного веб-интерфейса, в то же время позволяя каждому приложению определять собственный REST API. Прокси-сервлеты могут помочь, создав видимость единой конечной точки для всех различных API, при этом не требуется никакой логики, которая знает о содержимом интерфейсов.