2008-09-25 139 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)文件将在浏览器中显示为文字