Как получить доступ к статическим ресурсам при отображении сервлета глобального фронт-контроллера в /*

Я сопоставил диспетчер Spring MVC с сервлетом глобального фронт-контроллера /*,

<servlet>       
  <servlet-name>home</servlet-name>         
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>     
</servlet>  
<servlet-mapping>       
  <servlet-name>home</servlet-name>         
  <url-pattern>/*</url-pattern>     
</servlet-mapping>

Однако это отображение останавливает доступ к статическим файлам, таким как CSS, JS, изображения и т. Д., Которые находятся в /res/ папка.

Как я могу получить к ним доступ в любом случае?

17 ответов

Решение

Я столкнулся с этим также и никогда не находил отличного решения. Я закончил отображать свой сервлет на один уровень выше в иерархии URL:

<servlet-mapping>       
  <servlet-name>home</servlet-name>             
  <url-pattern>/app/*</url-pattern>     
</servlet-mapping>

И теперь все в базовом контексте (и в вашем каталоге /res) может обслуживаться вашим контейнером.

Сопоставить сервлет контроллера с более конкретной url-pattern лайк /pages/*поместите статический контент в определенную папку, например /static и создать Filter слушая /* который прозрачно продолжает цепочку для любого статического контента и отправляет запросы сервлету контроллера для другого контента.

В двух словах:

<filter>
    <filter-name>filter</filter-name>
    <filter-class>com.example.Filter</filter-class>
</filter>
<filter-mapping>
    <filter-name>filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<servlet>
    <servlet-name>controller</servlet-name>
    <servlet-class>com.example.Controller</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>controller</servlet-name>
    <url-pattern>/pages/*</url-pattern>
</servlet-mapping>

со следующим в фильтре doFilter():

HttpServletRequest req = (HttpServletRequest) request;
String path = req.getRequestURI().substring(req.getContextPath().length());

if (path.startsWith("/static")) {
    chain.doFilter(request, response); // Goes to default servlet.
} else {
    request.getRequestDispatcher("/pages" + path).forward(request, response);
}

Нет, это не заканчивается /pages в адресной строке браузера. Это полностью прозрачно. Вы можете при необходимости сделать "/static" и / или "/pages" init-param фильтра.

С Spring 3.0.4.RELEASE и выше вы можете использовать

<mvc:resources mapping="/resources/**" location="/public-resources/"/>

Как видно из Spring Reference.

Что вы делаете, это добавляете файл приветствия в ваш web.xml

<welcome-file-list>
    <welcome-file>index.html</welcome-file>
</welcome-file-list>

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

<servlet-mapping>
    <servlet-name>MainActions</servlet-name>
    <url-pattern>/main</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>MainActions</servlet-name>
    <url-pattern>/index.html</url-pattern>
</servlet-mapping>

Конечный результат: Вы посещаете /Application, но вам представлен сервлет /Application/MainActions без прерывания любых других корневых запросов.

Возьми? Таким образом, ваше приложение все еще находится на дополнительном URL-адресе, но автоматически отображается, когда пользователь переходит в корень вашего сайта. Это позволяет вам /images/bob.img по-прежнему переходить в обычное место, но "/" - это ваше приложение.

Обслуживание статического контента с соответствующим суффиксом в нескольких определениях отображения сервлетов решило проблему безопасности, которая упоминается в одном из комментариев в одном из опубликованных ответов. Цитируется ниже:

Это была дыра в безопасности в Tomcat (содержимое WEB-INF и META-INF доступно таким образом), и она была исправлена ​​в 7.0.4 (и будет также портирована на 5.x и 6.x). - BalusC 2 ноября 2010 года в 22:44

что мне очень помогло. И вот как я это решил:

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.htm</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.html</url-pattern>
</servlet-mapping>

Если вы используете Tomcat, вы можете сопоставить ресурсы сервлету по умолчанию:

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/static/*</url-pattern>
</servlet-mapping>

и получить доступ к вашим ресурсам с помощью URL http://{context path}/static/res/...

Также работает с Jetty, не уверен насчет других контейнеров сервлетов.

Начиная с версии 3.0.4 вы можете использовать mvc:resources в комбинации с mvc:default-servlet-handler как описано в весенней документации для достижения этой цели.

http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html

Кажется, причина конфликта заключается в том, что по умолчанию корень контекста "/" должен обрабатываться org.apache.catalina.servlets.DefaultServlet. Этот сервлет предназначен для обработки запросов на статические ресурсы.

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

Если вы прочитаете документы tomcat, они отметят, что True Apache (httpd) лучше, чем Apache Tomcat, для обработки статического содержимого, поскольку он специально создан для этого. Я предполагаю, что Tomcat по умолчанию использует org.apache.catalina.servlets.DefaultServlet для обработки статических запросов. Поскольку все это заключено в JVM, а Tomcat задуман как контейнер Servlet/JSP, они, вероятно, не написали этот класс как супероптимизированный обработчик статического содержимого. Это здесь. Это делает работу. Достаточно хорошо.

Но это то, что обрабатывает статический контент, и он живет в "/". Так что, если вы положите туда что-нибудь еще, и эта штука не будет обрабатывать статические запросы, WHOOPS, ваши статические ресурсы уйдут.

Я искал один и тот же ответ, и ответ, который я получаю повсюду: "Если ты не хочешь, чтобы это делалось, не делай этого".

Короче говоря, ваша конфигурация заменяет обработчик статических ресурсов по умолчанию чем-то, что вообще не является обработчиком статических ресурсов. Вам нужно будет попробовать другую конфигурацию, чтобы получить результаты, которые вы ищете (как и я).

"Статические" файлы в App Engine не доступны напрямую вашему приложению. Вам нужно либо загрузить их дважды, либо обслуживать статические файлы самостоятельно, а не использовать статический обработчик.

Лучший способ справиться с этим - использовать переписывание URL-адресов. Таким образом, вы можете иметь чистые релаксирующие URL-адреса, а НЕ с какими-либо расширениями, например abc.com/welcom/register, в отличие от abc.com/welcome/resister.html.

Я использую Tuckey URL, который довольно крутой.

Там есть инструкции по настройке вашего веб-приложения. Я настроил его с помощью моего веб-приложения Spring MVC. Конечно, все было хорошо, пока я не хотел использовать аннотации для валидации Spring 3, например @Email или же @Null для доменных объектов.

Когда я добавляю Spring mvc директивы:

< mvc:annotation-driven  /> 
< mvc:default-servlet-handler />

.. это нарушает старый добрый код. По-видимому, < mvc:default-servlet-handler /> заменяет Tuckey, который я все еще пытаюсь решить.

Я обнаружил, что с помощью

<mvc:default-servlet-handler />

весной у меня работает файл определения bean-компонента сервлета MVC. Он передает любой запрос, который не обрабатывается зарегистрированным контроллером MVC, на оригинальный обработчик контейнера по умолчанию, который должен служить ему в качестве статического содержимого. Просто убедитесь, что у вас нет зарегистрированного контроллера, который обрабатывает все, и он должен работать просто отлично. Не уверен, почему @logixplayer предлагает переписать URL; Вы можете добиться того эффекта, который он ищет, просто адекватно используя Spring MVC.

Я бы порекомендовал по возможности использовать фильтр вместо сервлета по умолчанию.

Другие две возможности:

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

Создайте класс FileServlet, используемый Google App Engine и службой вызовов (запрос, ответ) для этого FileServlet, когда вам нужно предоставить статический файл по заданному URL-адресу.

Вы можете сопоставить /res/* с YourFileServlet или любым другим, чтобы исключить его из обработки DispatcherServlets, или вызвать его непосредственно из DispatcherServlet.

И я должен спросить, что в документации Spring говорится об этом столкновении? Я никогда не использовал это.

Я нашел более простое решение с фиктивным индексным файлом.

Создайте сервлет (или используйте тот, который вы хотите ответить на "/"), который сопоставляется с "/index.html" (решения, упомянутые здесь, используют сопоставление через XML, я использовал версию 3.0 с аннотацией @WebServlet) Затем создайте статический (пустой) файл в корне статического содержимого с именем "index.html"

Я использовал Jetty, и произошло то, что сервер распознал файл вместо того, чтобы перечислить каталог, но когда его спросили о ресурсе, мой сервлет взял на себя управление. Все остальные статические материалы остались без изменений.

После безуспешной попытки применения фильтра (по какой-то причине он не вошел в функцию doFilter()), я немного изменил настройки и нашел очень простое решение для проблемы с обслуживанием root:

Вместо того, чтобы использовать "/ *" в моем основном сервлете, я теперь слушаю только специальные языковые префиксы "EN", "EN/ *", "DE", "DE/ *"

Статический контент обслуживается сервлетом по умолчанию, а пустые корневые запросы отправляются в index.jsp, который вызывает мой основной сервлет с языком по умолчанию:

(на индексной странице нет другого содержимого.)

Добавьте папки, которые вы не хотите запускать обработку сервлета в <static-files> раздел вашего файла appengine-web.xml.

Я только что сделал это и похоже, что все начинает работать нормально. Вот моя структура:

/

/pages/<.jsp файлы>

/ CSS

Я добавил "/ pages / **" и "/ css / **" к <static-files> раздел, и теперь я могу переслать файл.jsp из сервлета doGet, не вызывая бесконечный цикл.

В Embedded Jetty мне удалось добиться чего-то подобного, добавив сопоставление для каталога "css" в web.xml. Явно говорю ему использовать DefaultServlet:

<servlet>
  <servlet-name>DefaultServlet</servlet-name>
  <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>DefaultServlet</servlet-name>
  <url-pattern>/css/*</url-pattern>
</servlet-mapping>

Что касается Tomcat, многое зависит от конкретной версии. Была исправлена ​​ошибка https://bz.apache.org/bugzilla/show_bug.cgi?id=50026 которая означает, что отображение сервлета (кроме '/') для сервлета по умолчанию ведет себя по-разному в Tomcat 6.0.29 (и раньше) по сравнению с более поздними версиями.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<mvc:default-servlet-handler/>
</beans>

и если вы хотите использовать конфигурацию на основе аннотаций, используйте приведенный ниже код

@Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

В разделе "12.2 Спецификация отображений" спецификации сервлетов говорится:

Строка, содержащая только символ '/', указывает сервлет приложения по умолчанию.

Итак, теоретически вы можете сопоставить свой сервлет с /* делать:

getServletContext().getNamedDispatcher("/").forward(req,res);

... если вы не хотели справляться с этим самостоятельно.

Однако на практике это не работает.

И в Tomcat, и в Jetty вызов getServletContext().getNamedDispatcher('/') возвращается null если есть сервлет, сопоставленный с '/*'

Другие вопросы по тегам