정적 컨텐츠를 제공하기위한 서블릿


145

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

따라서 웹 응용 프로그램에 작은 서블릿을 포함시켜 자체 정적 콘텐츠 (이미지, CSS 등)를 제공하려고합니다. 서블릿에는 다음과 같은 속성이 있어야합니다.

  • 외부 의존성 없음
  • 간단하고 신뢰할 수있는
  • 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를 고려하지 않기 때문에 (주 폴더에서 정적 파일을 찾습니다) Jetty는 static폴더 에서 찾습니다 .


사용하려는 "URL 구조"에 대해 자세히 설명해 주시겠습니까? 연결된 예제 4-10을 기반으로 자신을 굴리는 것은 사소한 노력처럼 보입니다. 나는 그것을 여러 번 스스로 해왔다.
Stu Thompson

URL 구조를 구체화하기 위해 질문을 편집했습니다. 그리고 예, 나는 내 자신의 서블릿을 굴 리게되었습니다. 아래 답변을 참조하십시오.
Bruno De Fraine

1
정적 컨텐츠에 웹 서버를 사용하지 않는 이유는 무엇입니까?
Stephen

4
@Stephen : Tomcat / Jetty 앞에 항상 Apache가있는 것은 아닙니다. 별도의 구성 번거 로움을 피하기 위해. 하지만 당신 말이 맞아요, 그 옵션을 고려할 수 있습니다
Bruno De Fraine

이해가 안됩니다. 왜 <servlet-mapping> <servlet-name> default </ servlet-name> <url-pattern> / </ url-pattern> </ servlet-mapping과 같은 매핑을 사용하지 않은 이유는 무엇입니까? > 정적 콘텐츠를 제공하기 위해
Maciek Kreft

답변:


53

나는 약간 다른 해결책을 생각해 냈습니다. 약간 해킹이지만 매핑은 다음과 같습니다.

<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"에 매핑합니다.

Jetty와 Tomcat 모두에서 작동합니다.


13
실제로 당신은 servelet-mapping 안에 하나 이상의 url-pattern 태그를 추가 할 수 있습니다;)
Fareed Alnamrouti

5
Servlet 2.5 이상은 servlet-mapping 내부에서 여러 개의 URL 패턴 태그를 지원합니다
vivid_voidgroup

색인 파일 (index.html)은 서블릿보다 우선 할 수 있으므로주의하십시오.
Andres

나는 그것이 나쁜 아이디어 사용이라고 생각합니다 *.sth. 누군가가 URL example.com/index.jsp?g=.sth을 얻으면 jsp 파일의 소스를 얻습니다. 아니면 내가 틀렸어? (I 자바 EE의 새로운 해요) 나는 보통 URL 패턴 사용 /css/*등을
SemperPeritus

46

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


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);
    }
}

이 질문에는 필터를 사용하여 / 컨트롤러에 / 정적 콘텐츠를 정적 콘텐츠에 매핑하는 깔끔한 방법이 있습니다. 승인 된 답변 후 upvoted 답변 확인 : stackoverflow.com/questions/870150/…
David Carboni


30

FileServlet 으로 거의 모든 HTTP (etags, chunking 등)를 지원하기 때문에 좋은 결과를 얻었습니다 .


감사! 실패한 시도 나쁜 답변이 해결 내 문제의 시간
요시 Shasho

4
응용 프로그램 외부의 폴더에서 콘텐츠를 제공하기 위해 (디스크에서 폴더로 서버에 사용하는 경우 C : \ resources라고 함) this 행을 수정했습니다. this.basePath = getServletContext (). getRealPath (getInitParameter ( "basePath ")); 그리고 이것을 다음과 같이 교체했습니다 : this.basePath = getInitParameter ( "basePath");
요시 샤쇼

1
업데이트 된 버전에서 볼 수 showcase.omnifaces.org/servlets/FileServlet
koppor

26

정적 자원 서블릿에 대한 추상 템플릿

2007 년 부터이 블로그 를 부분적으로 기반으로 한 캐싱 ETag,, If-None-MatchIf-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에 따라 메소드를 됩니다.

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

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

@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();
            }
        };
    }

}

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

다음 /files/foo.ext은 EJB 서비스 호출을 통해 데이터베이스 와 같은 URL을 통해 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();
            }
        };
    }

}

1
친애하는 @BalusC 귀하의 접근 방식은 다음 요청을 보내는 파일 시스템을 탐색 할 수있는 해커에게 취약하다고 생각합니다 files/%2e%2e/mysecretfile.txt. 이 요청은을 생성합니다 files/../mysecretfile.txt. Tomcat 7.0.55에서 테스트했습니다. : 그들은 그것을 디렉토리 등반 전화 owasp.org/index.php/Path_Traversal
크리스티안 Arteaga

1
@Cristian : 예, 가능합니다. 이를 방지하는 방법을 보여주기 위해 예제를 업데이트했습니다.
BalusC

공감하지 않아야합니다. 이와 같이 Servlet을 사용하여 웹 페이지에 정적 파일을 제공하는 것은 재난 보안 현명한 방법입니다. 이러한 모든 문제는 이미 해결되었으며 더 많은 보안 시한 폭탄을 사용하여 새로운 사용자 지정 방법을 구현할 이유가 없습니다. 올바른 경로는 Tomcat / GlassFish / Jetty 등을 구성하여 컨텐츠를 제공하거나 NGinX와 같은 전용 파일 서버를 사용하는 것입니다.
Leonhard Printz

@LeonhardPrintz : 보안 문제가 지적되면 답변을 삭제하고 Tomcat의 친구들에게 다시보고하겠습니다. 문제 없어요.
BalusC

19

나는 내 자신의 굴러 끝났다 StaticServlet. If-Modified-Sincegzip 인코딩을 지원하며 war 파일의 정적 파일도 제공 할 수 있어야합니다. 매우 어려운 코드는 아니지만 사소한 것도 아닙니다.

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;
}

2
내부 클래스 이름을 Error로 지정하지 마십시오. java.lang.Error로 착각 할 수 있으므로 혼동 될 수 있습니다. 또한 web.xml이 동일합니까?
Leonel

오류 경고에 감사드립니다. web.xml은 동일하며 "default"는 StaticServlet의 이름으로 대체됩니다.
Bruno De Fraine

1
통합 방법에 대해서는 서블릿 클래스 내에서 commons-lang StringUtils.defaultString (String, String)으로 대체 할 수 있습니다.
Mike Minicki

transferStreams () 메소드는 Files.copy (is, os)로 대체 될 수도 있습니다.
Gerrit Brink

이 방법이 왜 그렇게 인기가 있습니까? 사람들이 왜 이런 정적 파일 서버를 다시 구현하고 있습니까? 발견되기를 기다리는 많은 보안 허점이 있고 구현되지 않은 실제 정적 파일 서버의 많은 기능이 있습니다.
Leonhard Printz

12

위의 예제 정보를 토대로이 기사 전체가 Tomcat 6.0.29 및 이전 버전의 버그가있는 동작을 기반으로한다고 생각합니다. https://issues.apache.org/bugzilla/show_bug.cgi?id=50026을 참조 하십시오 . Tomcat 6.0.30으로 업그레이드하면 (Tomcat | Jetty) 간의 동작이 병합됩니다.


1
그것도 나의 이해입니다 svn diff -c1056763 http://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk/. 3 년 전이 WONTFIX를 표시 한 후 마침내!
Bruno De Fraine

12

이 시도

<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>    

편집 : 이것은 서블릿 2.5 사양 이상에서만 유효합니다.


이것이 유효한 구성이 아닌 것 같습니다.
Gedrox

10

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

https://github.com/apache/tomcat/blob/master/java/org/apache/catalina/servlets/DefaultServlet.java

DefaultServlet는 톰캣 정적 자원 (JPG, HTML, CSS, GIF 등)을 제공하는 서블릿이다.

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

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

  • org.apache.naming.resources 패키지에 대한 참조는 제거하거나 java.io.File 코드로 대체 할 수 있습니다.
  • org.apache.catalina.util 패키지에 대한 참조는 소스 코드에서 복제 할 수있는 유틸리티 메소드 / 클래스 일뿐입니다.
  • org.apache.catalina.Globals 클래스에 대한 참조는 인라인되거나 제거 될 수 있습니다.

의 많은 것들에 의존하는 것 같습니다 org.apache.*. Jetty와 함께 어떻게 사용할 수 있습니까?
Bruno De Fraine

..이 버전은 톰캣에 너무 많은 depedencies을 (caand 그것은 또한 당신이 원하는하지 않을 수 있습니다 많은 것을 지원하는 내 대답을 편집합니다 가지고, 맞다
파나지오티스 Korros


4

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>  

1

Spring의 AbstractUrlBasedView가 요청하는 /favicon.ico와 / WEB-INF / jsp / *의 JSP 파일뿐만 아니라 Spring 앱의 모든 요청을 처리하려면 jsp 서블릿과 기본 서블릿을 다시 매핑하면됩니다.

  <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'서블릿에 대해 배웠고 기본 서블릿처럼 경로 접두어를 제거하지 않는 것을 보았습니다. 그것은 내 문제를 해결했는데, 정확히 같지는 않았지만 충분히 흔했습니다.


1

Tomcat 8.x 확인 : 루트 서블릿이 ""에 매핑되면 정적 리소스가 제대로 작동합니다. 서블릿 3.x의 경우 다음을 수행 할 수 있습니다.@WebServlet("")


0

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>

URL 접두어로 contextPath의 값을 설정하고 resourceBase의 값을 정적 컨텐츠의 파일 경로로 설정하십시오.

그것은 나를 위해 일했다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.