当前位置:  开发笔记 > 编程语言 > 正文

用于提供静态内容的Servlet

如何解决《用于提供静态内容的Servlet》经验,为你挑选了9个好方法。

我在两个不同的容器(Tomcat和Jetty)上部署了一个webapp,但是他们用于提供静态内容的默认servlet有一种不同的方式来处理我想要使用的URL结构(详细信息).

因此,我希望在webapp中包含一个小servlet来提供自己的静态内容(图像,CSS等).servlet应具有以下属性:

没有外部依赖

简单可靠

支持If-Modified-Since标题(即自定义getLastModified方法)

(可选)支持gzip编码,etags,...

这样的servlet可以在某处使用吗?我能找到的最接近的是servlet书中的例子4-10.

更新:我想使用的URL结构 - 万一你想知道 - 只是:

    
            main
            /*
    
    
            default
            /static/*
    

所以所有请求都应该传递给主servlet,除非它们是static路径.问题是Tomcat的默认servlet不考虑ServletPath(因此它在主文件夹中查找静态文件),而Jetty则这样(因此它在static文件夹中查找).



1> Taylor Gauti..:

我提出了一个略有不同的解决方案.这有点hack-ish,但这里是映射:

   
    default
    *.html


    default
    *.jpg


 default
    *.png


    default
    *.css


    default
    *.js



    myAppServlet
    /

这基本上只是通过扩展将所有内容文件映射到默认servlet,将其他所有文件映射到"myAppServlet".

它适用于Jetty和Tomcat.


实际上你可以在servelet-mapping中添加多个url-pattern标签;)
Servlet 2.5和更新版本支持servlet-mapping中的多个url-pattern标签

2> axtavt..:

在这种情况下,不需要完全自定义实现默认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);
    }
}



3> Will Hartung..:

我对FileServlet有很好的结果,因为它支持几乎所有的HTTP(etags,chunking等).


虽然为了从应用程序外部的文件夹提供内容(我使用它来从磁盘服务器文件夹,比如C:\ resources),我修改了这一行:this.basePath = getServletContext().getRealPath(getInitParameter("basePath") ")); 并将其替换为:this.basePath = getInitParameter("basePath");

4> BalusC..:
静态资源servlet的抽象模板

部分基于2007 年的这个博客,这里是一个现代化和高度可重用的servlet抽象模板,它适当地处理缓存ETag,If-None-MatchIf-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 null 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 <mime-type> in web.xml.
     * @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 -1 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扩展并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();
            }
        };
    }

}
从数据库服务的具体示例:

这是一个具体的例子,它通过一个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();
            }
        };
    }

}



5> Bruno De Fra..:

我最终滚动了自己StaticServlet.它支持If-Modified-Sincegzip编码,它也应该能够从war文件中提供静态文件.代码并不是很难,但它也不是完全无关紧要的.

代码可用:StaticServlet.java.随意评论.

更新: Khurram询问了ServletUtils引用的类StaticServlet.它只是一个带有我用于项目的辅助方法的类.您需要的唯一方法是coalesce(与SQL函数相同COALESCE).这是代码:

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


不要将内部类命名为Error.这可能会引起混淆,因为你可以将它误认为是java.lang.Error另外,你的web.xml是一样的吗?

6> 小智..:

从上面的示例信息来看,我认为整篇文章都是基于Tomcat 6.0.29及更早版本中的错误行为.请参阅https://issues.apache.org/bugzilla/show_bug.cgi?id=50026.升级到Tomcat 6.0.30并且(Tomcat | Jetty)之间的行为应该合并.



7> Fareed Alnam..:

试试这个


    default
    *.js
    *.css
    *.ico
    *.png
    *.jpg
    *.htc
    *.gif
    

编辑:这仅适用于servlet 2.5规范及更高版本.



8> Panagiotis K..:

我有同样的问题,我通过使用Tomcat代码库中'default 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类的引用.



9> 小智..:

我在网上找到了一些关于一些解决方法的精彩教程.它简单而有效,我在几个项目中使用REST网址样式方法:

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

推荐阅读
携手相约幸福
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有