В посте Отображение на сервлет всех запросов к web-приложению мы рассмотрели шаблон проектирования Front Controller и научились перенаправлять все запросы к web-приложению через этот контроллер. Но что если сервлет-контроллер, получающий все запросы, захочет при определенных условиях направить поступивший запрос на специализированную обработку другому сервлету? Если все остальные отображения на сервлеты удалены из web.xml, а шаблон URL в стиле invoker (/servlets/*) также отображен на контроллер, то даже сам сервлет-контроллер не сможет передать запрос другому сервлету! Как обойти это ограничение?
Решение заключается во введении в файле web.xml персональных отображений на сервлеты. Для этого используются элементы security-constraint, предотвращающие возможность обращения с запросами к сервлетам-неконтроллерам со стороны пользователей. Если вы еще не знакомы с ограничениями безопасностями, то прочитайте пост Ограничение запросов к определенным сервлетам. Когда сервлет-контроллер хочет перенаправить запрос к другому сервлету, он испоьзует объект, реализующий интерфейс javax.servlet.RequestDispatcher (диспетчер запросов). Диспетчеры запросов (RequestDispatcher) не ограничены в возможности передавать запросы (с помощью метода RequestDispatcher.forward(request, response)) на шаблоны URL, заданные в элементах security-constraint. В следующем примере сервлет с именем Controller использует диспетчер запросов для передачи запроса другому сервлету.
package ru.topcode.servletconstrainttest; import java.io.IOException; import java.rmi.ServerException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * * @author dmitrii.leontiev */ public class Controller extends HttpServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { RequestDispatcher dispatcher = null; String param = request.getParameter("go"); if (param == null) { throw new ServerException("Параметр не задан"); } else if (param.equals("weather")) { dispatcher = request.getRequestDispatcher("/weather"); } else if (param.equals("maps")) { dispatcher = request.getRequestDispatcher("/maps"); } else { throw new ServerException("Неправильный параметр"); } if (dispatcher != null) { dispatcher.forward(request, response); } else { throw new ServerException("Dispather is NULL"); } } }
Сервлет Weather:
package ru.topcode.servletconstrainttest; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * * @author dmitrii.leontiev */ public class Weather extends HttpServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("Cp1251"); // Устанавливаем MIME response.setContentType("text/html;charset=Windows-1251"); PrintWriter out = response.getWriter(); out.println("<html><head>"); out.println("<title>Прогноз погоды</title></head><body>"); out.println("<h1>Сервлет-контроллер перенаправил запрос сервлету Weather.</h1>"); out.println("</body></html>"); } }
Сервлет Controller проверяет значение параметра go. Запрос к нему может выглядеть так: http://localhost:8080/servlet-constraint-test?go=weather. Сервлет Controller из приведенного примера, с помощью отображения, настроен на получение всех запросов у web-приложению servlet-constraint-test. Другими словами, его элемент servlet-mapping в web.xml содержит шаблон URL /*.
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>servlet-constraint-test</display-name> <servlet> <servlet-name>Controller</servlet-name> <servlet-class>ru.topcode.servletconstrainttest.Controller</servlet-class> </servlet> <servlet-mapping> <servlet-name>Controller</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>Controller</servlet-name> <url-pattern>/servlets/*</url-pattern> </servlet-mapping> </web-app>
Для передачи запроса в зависимости от параметра go, сервлет Controller создает объект RequestDispatcher с тем или иным URL назначения. Сначала сервлет получает объект RequestDispatcher, для чего вызывает метод getRequestDispatcher(String path) объекта request. Значение параметра path может быть задано относительно корневого каталога контекста web-приложения, но оно не должно выходить за пределы текущего контекста сервлета. Предположим, шаблон URL /weather отображен на зарегистрированное имя сервлета Weather.
<servlet-mapping> <servlet-name>Weather</servlet-name> <url-pattern>/weather</url-pattern> </servlet-mapping>
В этом случае путь, передаваемый методу getRequestDispatcher выглядит так: getRequestDispatcher(“/weather”). Если параметр go задан неверно или вообще не задан, Controller вызывает исключение ServletException с соответствующим сообщением. Сервлет Weather не доступен web-пользователям, поскольку доступ к нему ограничен элементом security-constraint, однако на метод RequestDispatcher.forward(request, response) эти ограничения не распространяются.
Теперь нужно защитить сервлет с именем Weather от прямого доступа пользователей. Для этого необходимо изменить дескриптор развертывания web.xml следующим образом:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>servlet-constraint-test</display-name> <servlet> <servlet-name>Controller</servlet-name> <servlet-class>ru.topcode.servletconstrainttest.Controller</servlet-class> </servlet> <servlet> <servlet-name>Weather</servlet-name> <servlet-class>ru.topcode.servletconstrainttest.Weather</servlet-class> </servlet> <servlet-mapping> <servlet-name>Controller</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>Controller</servlet-name> <url-pattern>/servlets/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>Weather</servlet-name> <url-pattern>/weather</url-pattern> </servlet-mapping> <security-constraint> <web-resource-collection> <web-resource-name>Weather</web-resource-name> <url-pattern>/weather</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>nullrole</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint> <security-role> <role-name>nullrole</role-name> </security-role> </web-app>
Следующий шаг по защите сервлета Weather – убедиться, что в файле tomcat-user.xml роль nullrole не назначена никому из пользователей.
В web-приложениях, сконфигурированных подобным образом, на любой запрос к шаблону URL /weather будет возвращен ответ типа “HTTP статус 403 – в доступе к запрашиваемому ресурсу отказано”. Однако сервлет-контроллер, испоьзуя метод RequestDispatcher.forward(request, response), по-прежнему может направлять запросы к URL /weather на обработку.
Для получения объекта RequestDispatcher можно также использовать метод javax.servlet.ServletContext.getNamedDispatcher(String name). При использовании этого метода не нужно включать никаких элементов servlet-mapping для сервлетов назначения. Метод getNamedDispatcher() принимает в качестве параметра зарегистрированное в web.xml имя сервлета. В следующем примере показан тот же сервлет, но с использованием метода getNamedDispatcher() и зарегистрированного имени сервлета.
package ru.topcode.servletconstrainttest; import java.io.IOException; import java.rmi.ServerException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * * @author dmitrii.leontiev */ public class Controller extends HttpServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { RequestDispatcher dispatcher = null; String param = request.getParameter("go"); if (param == null) { throw new ServerException("Параметр не задан"); } else if (param.equals("weather")) { // dispatcher = request.getRequestDispatcher("/weather"); dispatcher = getServletContext().getNamedDispatcher("Weather"); } else if (param.equals("maps")) { // dispatcher = request.getRequestDispatcher("/maps"); dispatcher = getServletContext().getNamedDispatcher("Maps"); } else { throw new ServerException("Неправильный параметр"); } if (dispatcher != null) { dispatcher.forward(request, response); } else { throw new ServerException("Dispather is NULL"); } } }
Если ServletContext вернет null из-за того, что кто-то пропустил в web.xml требуемый XML-элемент, то doGet() вызовет исключение ServletException с объяснением, что объект dispatcher пуст.
Сервлет Maps я описывать в этом посте не буду, т.к. он полностью аналогичен сервлету Weather. Если хотите, то можете скопировать сервлет Weather, переименовать его в Maps и создать для него соответствующий маппинг со всеми ограничениями в дескрипторе развертывания.
В следующих постах мы рассмотрим альтернативную стратегию – использование слушателей (listener) для проверки запроса до того как он найдет путь к сервлету.



#1 by Alexey on 15 Ноябрь 2011 - 3:46
Quote
Благодарю! Очень полезно и вовремя.