2008-09-25 185 views
138

我在兩個不同的容器(Tomcat和Jetty)上部署webapp,但其用於提供靜態內容的默認servlet具有處理我想要使用的URL結構的不同方式(details)。用於提供靜態內容的Servlet

因此,我期待在webapp中包含一個小servlet來提供自己的靜態內容(圖像,CSS等)。該servlet應具有以下屬性:

這樣的servlet可用嗎?我能找到的最接近的是來自servlet書的example 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> 

因此,所有的請求應傳遞到主的servlet,除非他們正在爲static路徑。問題在於Tomcat的默認servlet沒有考慮到ServletPath(因此它在主文件夾中查找靜態文件),而Jetty卻是這樣(因此它在static文件夾中查找)。

+0

您能詳細說明您想使用的「網址結構」嗎?基於鏈接的例子4-10,自己滾動,看起來像是一個小小的努力。我自己做了很多次... – 2008-09-25 09:12:43

+0

我編輯了我的問題來詳細說明URL結構。是的,我結束了自己的servlet。請參閱下面的答案。 – 2008-09-25 12:17:20

+1

爲什麼你不使用靜態內容的網絡服務器? – Stephen 2008-10-02 19:10:09

回答

19

我最終滾了我自己的StaticServlet。它支持If-Modified-Since,gzip編碼,它也應該能夠提供來自war文件的靜態文件。這不是非常困難的代碼,但它也不是微不足道的。

該代碼可用:StaticServlet.java。隨意發表評論。

更新: Khurram詢問ServletUtils課程,該課程在StaticServlet中引用。這只是一個輔助方法,我用於我的項目。您需要的唯一方法是​​3210(與SQL函數COALESCE相同)。這是代碼:

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

不要命名你的內部類錯誤,這可能會導致混淆,因爲你可能會錯誤它爲java.lang.Error 另外,你的web.xml是否一樣? – Leonel 2008-09-25 17:10:05

+0

感謝Error錯誤警告。web.xml是一樣的,用「Default」代替StaticServlet的名字。 – 2008-09-25 17:25:50

10

我遇到了同樣的問題,我通過使用Tomcat代碼庫中'默認servlet'的代碼解決了這個問題。

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

DefaultServlet是在Tomcat中提供的靜態資源(JPG,HTML,CSS,GIF等),該servlet。

這個servlet非常高​​效,並且具有上面定義的一些屬性。

我認爲這個源代碼是啓動和刪除不需要的功能或依賴的好方法。

  • 對org.apache.naming.resources包的引用可以被移除或替換爲java.io.File代碼。
  • 對org.apache.catalina.util包的引用只是可用的方法/類,可以在源代碼中複製。
  • 對org.apache.catalina.Globals類的引用可以內聯或刪除。
+0

它似乎取決於來自`org.apache。*`的許多內容。你如何使用Jetty? – 2008-09-25 08:27:59

+0

你是對的,這個版本對Tomcat有太多的依賴(caand它也支持很多你可能不想要的東西,我會編輯我的答案 – 2008-09-25 08:40:28

0

使用org.mortbay.jetty.handler.ContextHandler。你不需要像StaticServlet這樣的附加組件。

在碼頭家,

$ CD上下文

$ CP javadoc.xml static.xml

$六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的值設置爲fi le靜態內容的路徑。

它爲我工作。

43

沒有必要完全自定義實現在這種情況下,默認的servlet,你可以使用這個簡單的servlet來包裝請求容器的實現:


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

我通過擴展tomcat的DefaultServletsrc)和重寫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> 
    <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> 
27

我有良好的結果FileServlet,因爲它支持幾乎所有的HTTP(ETag的,分塊等)。

1

爲了滿足從Spring應用程序從/ WEB-INF/JSP/*是Spring的AbstractUrlBasedView會要求你可以重新映射servlet和jsp默認servlet的所有請求以及/favicon.ico和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 servlet的標準映射上的* .jsp url模式,因爲路徑模式'/ *'在檢查任何擴展映射之前匹配。將jsp servlet映射到更深的文件夾意味着它首先被匹配。匹配'/favicon.ico'恰好發生在路徑模式匹配之前。更深的路徑匹配將起作用,或完全匹配,但沒有擴展匹配可以使其超過'/ *'路徑匹配。將'/'映射到默認的servlet似乎不起作用。你會認爲確切的'/'會在springapp上擊敗'/ *'路徑模式。

上述過濾器解決方案不適用於來自應用程序的轉發/包含的JSP請求。爲了使它工作,我必須直接將過濾器應用於springapp,此時,url-pattern匹配是無用的,因爲去應用程序的所有請求也會進入它的過濾器。所以我向過濾器添加了模式匹配,然後瞭解了'jsp'servlet,並發現它不像默認的servlet那樣刪除路徑前綴。這解決了我的問題,這不完全一樣,但很普通。

45

我想出了一個稍微不同的解決方案。這是一個有點劈十歲上下,但這裏是映射:

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

這基本上是通過擴展默認的servlet,一切以「myAppServlet」的所有內容文件的映射。

它適用於Jetty和Tomcat。

10

試試這個

<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的規範和起來。

22

摘要模板靜態資源的servlet

部分基於this blog從2007年,下面是一個servlet其妥善緩存,ETagIf-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; 

} 

所有你需要的就是從給定的抽象的servlet擴展和實現根據的Javadoc的getStaticResource()方法。

具體的例子,從文件系統服務:

下面是它通過從本地磁盤文件系統,如/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, name); 

     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的URL通過一個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(); 
      } 
     }; 
    } 

} 
0

檢查Tomcat 8.x:靜態資源工作確定如果根servlet映射到「」。 對於servlet 3.x,它可以通過@WebServlet("")

-1

完成靜態文件由默認servlet提供,您可以在web中配置單獨的擴展名。XML

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

如果你的文件沒有* .js文件,* .css和你要顯示它在瀏覽器中,你需要配置MIME映射

<mime-mapping> 
    <extension>wsdl</extension> 
    <mime-type>text/xml</mime-type> 
</mime-mapping> 

和你(例如:WSDL)文件將在瀏覽器中顯示爲文字