Servlet pour servir le contenu statique

je déploie une webapp sur deux conteneurs différents (Tomcat et Jetty), mais leurs servlets par défaut pour servir le contenu statique ont une façon différente de gérer la structure D'URL que je veux utiliser ( détails ).

je cherche donc à inclure un petit servlet dans la webapp pour servir son propre contenu statique (images, CSS, etc.). Le servlet doit avoir les propriétés suivantes:

  • Non externe dépendances
  • Simple et fiable
  • Soutien pour If-Modified-Since de l'en-tête (c'est à dire la coutume getLastModified méthode
  • (en Option) support de l'encodage gzip, etags,...

un tel servlet est-il disponible quelque part? Le plus proche que je peux trouver est exemple 4-10 du livre servlet.

Maj: la structure D'URL que je veux utiliser-au cas où vous vous demandez - est simplement:

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

ainsi toutes les requêtes doivent être passées au servlet principal, à moins qu'elles ne soient pour le chemin static . Le problème est que le servlet par défaut de Tomcat ne prend pas en compte le ServletPath (il recherche donc les fichiers statiques dans le dossier principal), tandis que Jetty le fait (il recherche donc dans le dossier static ).

140
demandé sur Waldheinz 2008-09-25 12:04:28

15 réponses

j'ai trouvé une solution légèrement différente. Il est un peu hack-ish, Mais voici la cartographie:

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

il s'agit simplement de mapper tous les fichiers de contenu par extension vers le servlet par défaut, et tout le reste vers"myAppServlet".

il fonctionne à la fois dans Jetty et Tomcat.

47
répondu Taylor Gautier 2016-06-06 07:34:07

il n'y a pas besoin d'implémentation complètement personnalisée du servlet par défaut dans ce cas, vous pouvez utiliser ce servlet simple pour envelopper la requête à l'implémentation du conteneur:


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);
    }
}
45
répondu axtavt 2009-05-07 21:00:08

j'ai eu de bons résultats avec FileServlet , car il supporte à peu près tout HTTP (etags, chunking, etc.).

29
répondu Will Hartung 2015-01-14 21:08:33

Abstrait modèle pour une ressource statique servlet

en partie basé sur ce blog de 2007, voici un modèle de résumé modernisé et hautement réutilisable pour un servlet qui traite correctement la mise en cache, ETag , If-None-Match et If-Modified-Since (mais pas de gzip et de support de gamme; juste pour le garder simple; Gzip pourrait être fait avec un filtre ou via une configuration de conteneur).

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

}

utilisez-le avec l'interface ci-dessous représente une ressource statique.

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;

}

Tout ce dont vous avez besoin est de s'étendre à partir du servlet abstrait donné et de mettre en œuvre la méthode getStaticResource() selon le javadoc.

l'exemple Concret de servir de système de fichiers:

voici un exemple concret qui lui sert via une URL comme /files/foo.ext du système de fichiers Disque local:

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

}

exemple de béton servant de la base de données:

voici un exemple concret qui le Sert via une URL comme /files/foo.ext de la base de données via un appel de service EJB qui renvoie votre entité ayant une propriété 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();
            }
        };
    }

}
25
répondu BalusC 2018-08-05 13:48:55

j'ai fini par rouler mon propre StaticServlet . Il soutient If-Modified-Since , gzip encoding et il devrait être en mesure de servir des dossiers statiques de war-files aussi bien. Il n'est pas très difficile de code, mais il n'est pas insignifiant.

le code est disponible: StaticServlet.java . N'hésitez pas à commenter.

Update: Khurram s'interroge sur la classe ServletUtils référencée dans StaticServlet . C'est simplement une classe auxiliaire méthodes que j'ai utilisé pour mon projet. La seule méthode dont vous avez besoin est coalesce (qui est identique à la fonction SQL COALESCE ). C'est le code:

public static <T> T coalesce(T...ts) {
    for(T t: ts)
        if(t != null)
            return t;
    return null;
}
20
répondu Bruno De Fraine 2009-02-04 11:53:30

j'ai eu le même problème et je l'ai résolu en utilisant le code de la 'servlet par défaut' de la base de codes Tomcat.

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

le DefaultServlet est le servlet qui sert les ressources statiques (jpg,html,css,gif etc) dans Tomcat.

ce servlet est très efficace et a une les propriétés que vous avez définies ci-dessus.

je pense que ce code source, est un bon moyen de démarrer et de supprimer les fonctionnalités ou les depedencies dont vous n'avez pas besoin.

  • les Références à la org.Apache.nommer.le paquet de ressources peut être supprimé ou remplacé par java.io.Code du fichier.
  • les Références à la org.Apache.Catalina.les paquets util sont uniquement des méthodes utilitaires/classes qui peuvent être dupliquées dans votre code source.
  • les Références à la org.Apache.Catalina.La classe Globals peut être inlined ou retirée.
10
répondu Panagiotis Korros 2008-09-25 08:46:06

a en juger par l'exemple d'information ci-dessus, je pense que cet article entier est basé sur un comportement sur écoute dans Tomcat 6.0.29 et plus tôt. Voir https://issues.apache.org/bugzilla/show_bug.cgi?id=50026 . La mise à niveau vers Tomcat 6.0.30 et le comportement entre (Tomcat|Jetty) devraient fusionner.

10
répondu Jeff Stice-Hall 2011-02-17 22:48:10

essayez cette

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

Edit: ceci n'est valable que pour le servlet 2.5 spec et les versions supérieures.

10
répondu Fareed Alnamrouti 2012-12-06 05:38:16

j'ai trouvé grand tutoriel sur le web sur certains contournement. Il est simple et efficace, je l'ai utilisé dans plusieurs projets avec L'approche REST urls styles:

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

5
répondu 2009-08-28 09:53:18

j'ai fait cela en prolongeant le tomcat DefaultServlet ( src ) et en supplantant la méthode 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);
   }
}

... Et voici mon servlet mappings

<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>  
4
répondu delux247 2009-09-23 18:19:10

pour servir toutes les requêtes d'une application de printemps ainsi que /favicon.ico et les fichiers JSP à partir de /WEB-INF/jsp / * pour que L'AbstractUrlBasedView de Spring vous demande de simplement remaper le servlet jsp et le servlet par défaut:

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

Nous ne pouvons pas compter sur l' *.JSP url-pattern sur la mise en correspondance standard du servlet jsp car le pattern de chemin '/*' est mis en correspondance avant que toute mise en correspondance d'extension soit vérifiée. Le mappage du servlet jsp vers un dossier plus profond signifie qu'il est apparié en premier. Correspondant à " /favicon.ico ' se produit exactement avant que le modèle de chemin corresponde. Des correspondances de chemin plus profondes fonctionneront, ou des Correspondances exactes, mais aucune correspondance d'extension ne pourra dépasser la correspondance de chemin'/*'. Cartographie '/' à défaut de servlet qui ne fonctionne pas. On pourrait penser que le '/' exact battrait le '/*' pattern de chemin sur springapp.

la solution de filtre ci-dessus ne fonctionne pas pour les requêtes JSP transmises/incluses de l'application. Pour le faire fonctionner, j'ai dû appliquer le filtre à springapp directement, à ce moment, la correspondance url-pattern était inutile puisque toutes les requêtes qui vont à l'application vont aussi à ses filtres. J'ai donc ajouté le motif correspondant au filtre, puis j'ai appris à propos du servlet 'jsp' et j'ai vu qu'il ne supprime pas le préfixe de chemin comme le fait le servlet par défaut. Qui a résolu mon problème, qui n'était pas exactement le même, mais assez commun.

1
répondu 2009-09-27 11:54:16

vérifié pour Tomcat 8.x: les ressources statiques fonctionnent bien si la table des servlets racine est "". Pour servlet 3.x il pourrait être fait par @WebServlet("")

1
répondu GKislin 2017-01-08 10:04:21

Utiliser org.mortbay.jetée.manipulateur.ContextHandler. Vous n'avez pas besoin de composants supplémentaires comme StaticServlet.

À la jetée de la maison,

$ cd contextes

$ cp javadoc.xml statique.xml

$ vi statique.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>

définissez la valeur de contextPath avec votre préfixe D'URL, et définissez la valeur de resourceBase comme le chemin de fichier de la statique contenu.

ça a marché pour moi.

0
répondu yogman 2009-02-17 21:19:30
-1
répondu Coldbeans Software 2009-06-27 06:15:55

les fichiers statiques sont servis par servlet par défaut, et vous pouvez configurer l'extension séparée dans web.xml

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

si votre fichier n'est pas *.js,*.css et vous voulez l'afficher dans le navigateur, vous devez configurer mime-mapping

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

et votre fichier (par exemple: wsdl) sera affiché sous forme de texte dans le navigateur

-1
répondu zond 2018-01-18 12:42:02