문제

두 개의 다른 컨테이너 (Tomcat 및 Jetty)에 웹 앱을 배포하지만 정적 컨텐츠를 제공하기위한 기본 서블릿은 사용하려는 URL 구조를 처리하는 방법이 다릅니다 (세부).

그러므로 나는 자체 정적 컨텐츠 (이미지, CSS 등)를 제공하기 위해 WebApp에 작은 서블릿을 포함 시키려고합니다. 서블릿에는 다음과 같은 속성이 있어야합니다.

  • 외부 의존성이 없습니다
  • 간단하고 신뢰할 수 있습니다
  • 을지 지하다 If-Modified-Since 헤더 (예 : 커스텀 getLastModified 방법)
  • (선택 사항) GZIP 인코딩, Etags, ...

그런 서블릿이 어딘가에 사용할 수 있습니까? 내가 찾을 수있는 가장 가까운 것은입니다 예 4-10 서블릿 서적에서.

업데이트: 내가 사용하고 싶은 URL 구조는 단순히 다음과 같습니다.

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

따라서 모든 요청은 메인 서플렛으로 전달되어야합니다. static 길. 문제는 Tomcat의 기본 서플릿이 ServletPath를 고려하지 않는다는 것입니다 (따라서 기본 폴더의 정적 파일을 찾습니다). static 폴더).

도움이 되었습니까?

해결책 5

나는 내 자신을 굴려 갔다 StaticServlet. 지원합니다 If-Modified-Since, GZIP 인코딩 및 전쟁 파일의 정적 파일도 제공 할 수 있어야합니다. 그것은 매우 어려운 코드는 아니지만 완전히 사소한 것은 아닙니다.

코드를 사용할 수 있습니다. Staticservlet.java. 자유롭게 의견을 보내십시오.

업데이트: Khurram은 ServletUtils 참조 된 클래스 StaticServlet. 프로젝트에 사용한 보조 방법이있는 클래스입니다. 당신이 필요한 유일한 방법은입니다 coalesce (SQL 기능과 동일합니다 COALESCE). 이것은 코드입니다.

public static <T> T coalesce(T...ts) {
    for(T t: ts)
        if(t != null)
            return t;
    return null;
}

다른 팁

나는 약간 다른 해결책을 생각해 냈습니다. 약간 쇠약하지만 여기에 매핑이 있습니다.

<servlet-mapping>   
    <servlet-name>default</servlet-name>
    <url-pattern>*.html</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>*.png</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>*.js</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>myAppServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

기본적으로 모든 컨텐츠 파일을 기본 서블릿으로 확장하여 다른 모든 내용을 "MyAppServlet"으로 맵핑합니다.

부두와 Tomcat에서 작동합니다.

이 경우 기본 서블릿을 완전히 사용자 정의 할 필요가 없으므로이 간단한 서블릿을 사용하여 컨테이너의 구현에 요청을 래핑 할 수 있습니다.


package com.example;

import java.io.*;

import javax.servlet.*;
import javax.servlet.http.*;

public class DefaultWrapperServlet extends HttpServlet
{   
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        RequestDispatcher rd = getServletContext().getNamedDispatcher("default");

        HttpServletRequest wrapped = new HttpServletRequestWrapper(req) {
            public String getServletPath() { return ""; }
        };

        rd.forward(wrapped, resp);
    }
}

나는 좋은 결과를 얻었습니다 Fileservlet, 그것은 거의 모든 HTTP (Etags, Chunking 등)를 지원하기 때문에.

정적 리소스 서블릿에 대한 추상 템플릿

부분적으로 이 블로그 2007 년부터 캐싱을 제대로 다루는 서블릿을위한 현대적이고 재사용 가능한 추상 템플릿이 있습니다. ETag, If-None-Match 그리고 If-Modified-Since (그러나 GZIP 및 범위 지지대는 없으며 단순히 유지하기 만하면 필터 또는 컨테이너 구성을 통해 GZIP를 수행 할 수 있습니다).

public abstract class StaticResourceServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;
    private static final long ONE_SECOND_IN_MILLIS = TimeUnit.SECONDS.toMillis(1);
    private static final String ETAG_HEADER = "W/\"%s-%s\"";
    private static final String CONTENT_DISPOSITION_HEADER = "inline;filename=\"%1$s\"; filename*=UTF-8''%1$s";

    public static final long DEFAULT_EXPIRE_TIME_IN_MILLIS = TimeUnit.DAYS.toMillis(30);
    public static final int DEFAULT_STREAM_BUFFER_SIZE = 102400;

    @Override
    protected void doHead(HttpServletRequest request, HttpServletResponse response) throws ServletException ,IOException {
        doRequest(request, response, true);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doRequest(request, response, false);
    }

    private void doRequest(HttpServletRequest request, HttpServletResponse response, boolean head) throws IOException {
        response.reset();
        StaticResource resource;

        try {
            resource = getStaticResource(request);
        }
        catch (IllegalArgumentException e) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        if (resource == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        String fileName = URLEncoder.encode(resource.getFileName(), StandardCharsets.UTF_8.name());
        boolean notModified = setCacheHeaders(request, response, fileName, resource.getLastModified());

        if (notModified) {
            response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
            return;
        }

        setContentHeaders(response, fileName, resource.getContentLength());

        if (head) {
            return;
        }

        writeContent(response, resource);
    }

    /**
     * Returns the static resource associated with the given HTTP servlet request. This returns <code>null</code> when
     * the resource does actually not exist. The servlet will then return a HTTP 404 error.
     * @param request The involved HTTP servlet request.
     * @return The static resource associated with the given HTTP servlet request.
     * @throws IllegalArgumentException When the request is mangled in such way that it's not recognizable as a valid
     * static resource request. The servlet will then return a HTTP 400 error.
     */
    protected abstract StaticResource getStaticResource(HttpServletRequest request) throws IllegalArgumentException;

    private boolean setCacheHeaders(HttpServletRequest request, HttpServletResponse response, String fileName, long lastModified) {
        String eTag = String.format(ETAG_HEADER, fileName, lastModified);
        response.setHeader("ETag", eTag);
        response.setDateHeader("Last-Modified", lastModified);
        response.setDateHeader("Expires", System.currentTimeMillis() + DEFAULT_EXPIRE_TIME_IN_MILLIS);
        return notModified(request, eTag, lastModified);
    }

    private boolean notModified(HttpServletRequest request, String eTag, long lastModified) {
        String ifNoneMatch = request.getHeader("If-None-Match");

        if (ifNoneMatch != null) {
            String[] matches = ifNoneMatch.split("\\s*,\\s*");
            Arrays.sort(matches);
            return (Arrays.binarySearch(matches, eTag) > -1 || Arrays.binarySearch(matches, "*") > -1);
        }
        else {
            long ifModifiedSince = request.getDateHeader("If-Modified-Since");
            return (ifModifiedSince + ONE_SECOND_IN_MILLIS > lastModified); // That second is because the header is in seconds, not millis.
        }
    }

    private void setContentHeaders(HttpServletResponse response, String fileName, long contentLength) {
        response.setHeader("Content-Type", getServletContext().getMimeType(fileName));
        response.setHeader("Content-Disposition", String.format(CONTENT_DISPOSITION_HEADER, fileName));

        if (contentLength != -1) {
            response.setHeader("Content-Length", String.valueOf(contentLength));
        }
    }

    private void writeContent(HttpServletResponse response, StaticResource resource) throws IOException {
        try (
            ReadableByteChannel inputChannel = Channels.newChannel(resource.getInputStream());
            WritableByteChannel outputChannel = Channels.newChannel(response.getOutputStream());
        ) {
            ByteBuffer buffer = ByteBuffer.allocateDirect(DEFAULT_STREAM_BUFFER_SIZE);
            long size = 0;

            while (inputChannel.read(buffer) != -1) {
                buffer.flip();
                size += outputChannel.write(buffer);
                buffer.clear();
            }

            if (resource.getContentLength() == -1 && !response.isCommitted()) {
                response.setHeader("Content-Length", String.valueOf(size));
            }
        }
    }

}

정적 자원을 나타내는 아래 인터페이스와 함께 사용하십시오.

interface StaticResource {

    /**
     * Returns the file name of the resource. This must be unique across all static resources. If any, the file
     * extension will be used to determine the content type being set. If the container doesn't recognize the
     * extension, then you can always register it as <code>&lt;mime-type&gt;</code> in <code>web.xml</code>.
     * @return The file name of the resource.
     */
    public String getFileName();

    /**
     * Returns the last modified timestamp of the resource in milliseconds.
     * @return The last modified timestamp of the resource in milliseconds.
     */
    public long getLastModified();

    /**
     * Returns the content length of the resource. This returns <code>-1</code> if the content length is unknown.
     * In that case, the container will automatically switch to chunked encoding if the response is already
     * committed after streaming. The file download progress may be unknown.
     * @return The content length of the resource.
     */
    public long getContentLength();

    /**
     * Returns the input stream with the content of the resource. This method will be called only once by the
     * servlet, and only when the resource actually needs to be streamed, so lazy loading is not necessary.
     * @return The input stream with the content of the resource.
     * @throws IOException When something fails at I/O level.
     */
    public InputStream getInputStream() throws IOException;

}

주어진 추상 서블릿에서 확장하고 구현하는 것만으로도 필요한 것은 getStaticResource() Javadoc에 따른 방법.

파일 시스템에서 제공되는 구체적인 예 :

다음은 URL을 통해 제공하는 구체적인 예입니다. /files/foo.ext 로컬 디스크 파일 시스템에서 :

@WebServlet("/files/*")
public class FileSystemResourceServlet extends StaticResourceServlet {

    private File folder;

    @Override
    public void init() throws ServletException {
        folder = new File("/path/to/the/folder");
    }

    @Override
    protected StaticResource getStaticResource(HttpServletRequest request) throws IllegalArgumentException {
        String pathInfo = request.getPathInfo();

        if (pathInfo == null || pathInfo.isEmpty() || "/".equals(pathInfo)) {
            throw new IllegalArgumentException();
        }

        String name = URLDecoder.decode(pathInfo.substring(1), StandardCharsets.UTF_8.name());
        final File file = new File(folder, Paths.get(name).getFileName().toString());

        return !file.exists() ? null : new StaticResource() {
            @Override
            public long getLastModified() {
                return file.lastModified();
            }
            @Override
            public InputStream getInputStream() throws IOException {
                return new FileInputStream(file);
            }
            @Override
            public String getFileName() {
                return file.getName();
            }
            @Override
            public long getContentLength() {
                return file.length();
            }
        };
    }

}

데이터베이스에서 제공되는 구체적인 예 :

다음은 URL을 통해 제공하는 구체적인 예입니다. /files/foo.ext EJB 서비스 호출을 통해 데이터베이스에서 byte[] content 재산:

@WebServlet("/files/*")
public class YourEntityResourceServlet extends StaticResourceServlet {

    @EJB
    private YourEntityService yourEntityService;

    @Override
    protected StaticResource getStaticResource(HttpServletRequest request) throws IllegalArgumentException {
        String pathInfo = request.getPathInfo();

        if (pathInfo == null || pathInfo.isEmpty() || "/".equals(pathInfo)) {
            throw new IllegalArgumentException();
        }

        String name = URLDecoder.decode(pathInfo.substring(1), StandardCharsets.UTF_8.name());
        final YourEntity yourEntity = yourEntityService.getByName(name);

        return (yourEntity == null) ? null : new StaticResource() {
            @Override
            public long getLastModified() {
                return yourEntity.getLastModified();
            }
            @Override
            public InputStream getInputStream() throws IOException {
                return new ByteArrayInputStream(yourEntityService.getContentById(yourEntity.getId()));
            }
            @Override
            public String getFileName() {
                return yourEntity.getName();
            }
            @Override
            public long getContentLength() {
                return yourEntity.getContentLength();
            }
        };
    }

}

위의 예제에서 판단하면이 기사 전체는 Tomcat 6.0.29 이상의 버그가있는 동작을 기반으로한다고 생각합니다. 보다 https://issues.apache.org/bugzilla/show_bug.cgi?id=50026. Tomcat 6.0.30으로 업그레이드하면 (Tomcat | Jetty) 사이의 동작이 병합되어야합니다.

이 시도

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.js</url-pattern>
    <url-pattern>*.css</url-pattern>
    <url-pattern>*.ico</url-pattern>
    <url-pattern>*.png</url-pattern>
    <url-pattern>*.jpg</url-pattern>
    <url-pattern>*.htc</url-pattern>
    <url-pattern>*.gif</url-pattern>
</servlet-mapping>    

편집 : 이것은 Servlet 2.5 사양 및 UP에만 유효합니다.

나는 같은 문제가 있었고 Tomcat Codebase의 '기본 서블릿'코드를 사용하여 해결했습니다.

http://svn.apache.org/repos/asf/tomcat/trunk/java/org/apache/catalina/servlets/defaultservlet.java

그만큼 defaultservlet Tomcat의 정적 자원 (JPG, HTML, CSS, GIF 등)을 제공하는 서블릿입니다.

이 서블릿은 매우 효율적이며 위에서 정의한 속성이 있습니다.

이 소스 코드는 필요하지 않은 기능이나 피의를 시작하고 제거하는 좋은 방법이라고 생각합니다.

  • org.apache.naming.resources 패키지에 대한 참조는 java.io.file 코드로 제거하거나 교체 할 수 있습니다.
  • org.apache.catalina.util 패키지에 대한 참조는 아마도 소스 코드에서 복제 할 수있는 유틸리티 메소드/클래스 만 제안합니다.
  • org.apache.catalina.globals 클래스에 대한 참조는 인쇄되거나 제거 될 수 있습니다.

웹에서 일부 해결 방법에 대한 훌륭한 자습서를 찾았습니다. 간단하고 효율적이며 REST URL 스타일 접근 방식이있는 여러 프로젝트에서 사용했습니다.

http://www.kuligowski.pl/java/rest-style-urls-and-mapping-for-content-apache-tomcat,5

나는 Tomcat을 확장하여 이것을했다 defaultservlet (SRC) 및 getRelativePath () 메소드를 재정의합니다.

package com.example;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.apache.catalina.servlets.DefaultServlet;

public class StaticServlet extends DefaultServlet
{
   protected String pathPrefix = "/static";

   public void init(ServletConfig config) throws ServletException
   {
      super.init(config);

      if (config.getInitParameter("pathPrefix") != null)
      {
         pathPrefix = config.getInitParameter("pathPrefix");
      }
   }

   protected String getRelativePath(HttpServletRequest req)
   {
      return pathPrefix + super.getRelativePath(req);
   }
}

... 그리고 여기 내 서블릿 매핑이 있습니다

<servlet>
    <servlet-name>StaticServlet</servlet-name>
    <servlet-class>com.example.StaticServlet</servlet-class>
    <init-param>
        <param-name>pathPrefix</param-name>
        <param-value>/static</param-value>
    </init-param>       
</servlet>

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

Spring의 AbstractUrlBasedView는 /web-inf /jsp /*의 /favicon.ico뿐만 아니라 Spring 앱의 모든 요청을 제공하려면 JSP Servlet 및 Default Servlet을 다시 맵핑 할 수 있습니다.

  <servlet>
    <servlet-name>springapp</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>/WEB-INF/jsp/*</url-pattern>
  </servlet-mapping>

  <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/favicon.ico</url-pattern>
  </servlet-mapping>

  <servlet-mapping>
    <servlet-name>springapp</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

경로 패턴 '/ *'가 확장 맵핑을 확인하기 전에 일치하기 때문에 JSP 서틀의 표준 매핑에서 *.jsp url-pattern에 의존 할 수 없습니다. JSP 서블릿을 더 깊은 폴더에 매핑하면 먼저 일치 함을 의미합니다. 일치하는 '/favicon.ico'는 경로 패턴 일치 전에 정확히 발생합니다. 더 깊은 경로 일치가 작동하거나 정확히 일치하지만 연장 경기는 '/*'경로 일치를 지나갈 수 없습니다. 기본 서블릿에 '/'를 매핑하는 것은 작동하지 않습니다. 정확한 '/'는 SpringApp의 '/*'경로 패턴을 이길 것이라고 생각할 것입니다.

위의 필터 솔루션은 응용 프로그램의 전달/포함 JSP 요청에 대해 작동하지 않습니다. 작동하기 위해 필터를 SpringApp에 직접 적용해야했는데,이 시점에서 URL 패턴 매칭은 응용 프로그램으로 이동하는 모든 요청도 필터로 이동함에 따라 쓸모가 없었습니다. 그래서 나는 필터에 패턴 일치를 추가 한 다음 'JSP'서블릿에 대해 배웠고 기본 서블릿처럼 경로 접두사를 제거하지 않는 것을 보았습니다. 그것은 내 문제를 해결했는데, 그것은 정확히 동일하지는 않지만 충분히 일반적이었습니다.

Tomcat 8.x를 확인했습니다 : 정적 리소스가 작동합니다. 루트 서블릿이 ""에 맵을 맵핑하면 확인합니다. Servlet 3.x의 경우 수행 할 수 있습니다 @WebServlet("")

org.mortbay.jetty.handler.contexthandler를 사용하십시오. StaticServlet과 같은 추가 구성 요소가 필요하지 않습니다.

부두 주택에서

$ CD 컨텍스트

$ cp javadoc.xml static.xml

$ vi static.xml

...

<Configure class="org.mortbay.jetty.handler.ContextHandler">
<Set name="contextPath">/static</Set>
<Set name="resourceBase"><SystemProperty name="jetty.home" default="."/>/static/</Set>
<Set name="handler">
  <New class="org.mortbay.jetty.handler.ResourceHandler">
    <Set name="cacheControl">max-age=3600,public</Set>
  </New>
 </Set>
</Configure>

ContextPath의 값을 URL 접두사로 설정하고 ResourceBase의 값을 정적 컨텐츠의 파일 경로로 설정하십시오.

그것은 나를 위해 일했다.

JSOS의 staticfile을 참조하십시오. http://www.servletsuite.com/servlets/staticfile.htm

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top