정적 콘텐츠 서비스용 서블릿
웹 앱을 두 개의 다른 컨테이너(Tomcat 및 Jetty)에 배포하지만 정적 콘텐츠를 제공하기 위한 기본 서블릿은 사용하는 URL 구조(상세)를 처리하는 방법이 다릅니다.
따라서 웹 앱에 작은 서블릿을 추가하여 자체 정적 콘텐츠(이미지, CSS 등)를 제공하는 것을 검토하고 있습니다.서블릿에는 다음 속성이 있어야 합니다.
- 외부 의존관계 없음
- 심플하고 신뢰성 높은
- 헤더 지원(예: 커스텀 방식)
- (옵션) gzip 인코딩, 에태그 지원...
그런 서블릿이 어디 있나요?내가 찾을 수 있는 가장 가까운 것은 서블릿북의 예시 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>
모든 단, 이 는 "Main servlet이 .static
됩니다), 됩니다(Tomcat에서 됩니다).static
★★★★★★★★★★★★★★★★★★」
나는 조금 다른 해결책을 생각해 냈다.약간 해커가 있긴 하지만, 지도는 다음과 같습니다.
<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 모두에서 동작합니다.
이 경우 기본 서블릿을 완전히 맞춤형으로 구현할 필요가 없습니다. 이 단순한 서블릿을 사용하여 컨테이너 구현에 대한 요청을 래핑할 수 있습니다.
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);
}
}
File Servlet은 거의 모든 HTTP(etags, chunking 등)를 지원하므로 좋은 결과를 얻었습니다.
정적 리소스 서블릿의 추상 템플릿
2007년의 블로그에 근거해, 캐싱을 적절히 취급하는 servlet용의 현대화된 재사용성이 높은 추상 템플릿을 소개합니다.ETag
,If-None-Match
★★★★★★★★★★★★★★★★★」If-Modified-Since
(단, Gzip 및 Range는 지원되지 않습니다.이치노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><mime-type></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 서비스 콜을 통해 데이터베이스로부터 전송되며, EJB 서비스 콜은 EJB를 통해 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();
}
};
}
}
나는 결국 내 것을 굴리고 말았다.StaticServlet
를 지원합니다.If-Modified-Since
, gzip 인코딩 및 전쟁 파일의 정적 파일도 처리할 수 있어야 합니다.이것은 매우 어려운 코드는 아니지만, 완전히 사소한 것도 아니다.
코드는 다음과 같습니다.Static Servlet.java.편하게 말씀해주세요.
업데이트: Khurram은 다음 항목에 대해 묻습니다.ServletUtils
참조되는 클래스StaticServlet
제가 프로젝트에 사용한 것은 단순히 보조 메서드가 있는 클래스입니다.필요한 유일한 방법은coalesce
(SQL 함수와 동일합니다).코드는 다음과 같습니다.
public static <T> T coalesce(T...ts) {
for(T t: ts)
if(t != null)
return t;
return null;
}
위의 예시로 판단하건대, 이 기사 전체가 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>
편집: 이것은 서블릿 2.5 사양 이상에서만 유효합니다.
저도 같은 문제가 있어서 Tomcat 코드베이스의 'default servlet' 코드를 사용하여 해결했습니다.
https://github.com/apache/tomcat/blob/master/java/org/apache/catalina/servlets/DefaultServlet.java
Default Servlet은 Tomcat에서 정적 리소스(jpg,html,css,gif 등)를 처리하는 서블릿입니다.
이 서블릿은 매우 효율적이며 위에서 정의한 몇 가지 속성이 있습니다.
이 소스코드는 불필요한 기능 또는 기능을 시작하고 제거하는 데 좋은 방법이라고 생각합니다.
- org.disc.naming.disc 패키지에 대한 참조는 삭제하거나 java.io로 대체할 수 있습니다.파일 코드
- org.apache.catalina.util 패키지에 대한 참조는 소스 코드로 복제할 수 있는 유틸리티 메서드/클래스뿐인 것이 좋습니다.
- org.apache.catalina에 대한 참조.글로벌 클래스는 인라인 또는 삭제할 수 있습니다.
웹에서 몇 가지 해결 방법에 대한 훌륭한 튜토리얼을 찾았습니다.심플하고 효율적이며, REST URL 스타일의 접근방식으로 여러 프로젝트에서 사용하였습니다.
http://www.kuligowski.pl/java/rest-style-urls-and-url-mapping-for-static-content-apache-tomcat,5
이를 위해 Tomcat Default Servlet(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 앱의 모든 요청과 /favicon.ico 및 Spring의 AbstractUrlBasedView가 요구하는 /WEB-INF/jsp/*의 JSP 파일을 처리하려면 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 servlet을 더 깊은 폴더에 매핑하면 먼저 일치합니다.'/favicon.ico' 매칭은 경로 패턴 매칭 전에 정확히 수행됩니다.더 깊은 경로 일치가 작동하거나 정확히 일치하지만 확장 일치로 인해 '/*' 경로 일치를 통과할 수 없습니다.'/'을(를) 기본 서블릿에 매핑하면 작동하지 않는 것 같습니다.정확한 '/'은(는) springapp의 '/*' 경로 패턴을 능가한다고 생각할 수 있습니다.
위의 필터 솔루션은 응용 프로그램에서 전송/포함된 JSP 요청에는 작동하지 않습니다.springapp에 직접 필터를 적용해야 했습니다.이 시점에서는 어플리케이션으로 전송되는 모든 요구도 해당 필터로 전송되므로 URL 패턴 매칭은 무용지물입니다.그래서 필터에 패턴 매칭을 추가하고 'jsp' 서블릿에 대해 알아보니 기본 서블릿처럼 경로 접두사가 제거되지 않습니다.그것은 정확히 똑같지는 않지만 충분히 흔한 문제였던 나의 문제를 해결했다.
Tomcat 8.x: 루트 서블릿이 " " 에 매핑되면 정적 리소스가 정상적으로 동작하는지 확인.3.의 경우 servlet 3.x로 할 수 .@WebServlet("")
org.mortbay를 사용합니다.jetty.discloss.discloss.제콘텍스트 핸들러Static Servlet과 같은 추가 컴포넌트는 필요하지 않습니다.
제트가 있는 집에서
$ 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 값을 정적 콘텐츠의 파일 경로로 설정합니다.
그것은 나에게 효과가 있었다.
JSOS의 Static File을 참조해 주세요.http://www.servletsuite.com/servlets/staticfile.htm
언급URL : https://stackoverflow.com/questions/132052/servlet-for-serving-static-content
'programing' 카테고리의 다른 글
JavaScript에서 선두에 0이 있는 숫자를 출력하려면 어떻게 해야 합니까? (0) | 2022.09.05 |
---|---|
MySQL GUI 도구를 사용하여 필드를 NULL로 만드는 방법 (0) | 2022.09.05 |
최종 객체를 수정할 수 있는 이유는 무엇입니까? (0) | 2022.09.05 |
IntelliJ에서 Java 클래스의 메서드를 재정렬하는 간단한 방법? (0) | 2022.09.04 |
Joda-Time에서 두 날짜 사이의 일수 (0) | 2022.09.04 |