Servlet zum Bereitstellen statischer Inhalte

Lesezeit: 12 Minuten

Servlet zum Bereitstellen statischer Inhalte
Bruno de Fraine

Ich setze eine Webanwendung auf zwei verschiedenen Containern (Tomcat und Jetty) ein, aber ihre Standard-Servlets zum Bereitstellen des statischen Inhalts behandeln die URL-Struktur, die ich verwenden möchte, anders (Einzelheiten).

Ich versuche daher, ein kleines Servlet in die Webapp aufzunehmen, um seine eigenen statischen Inhalte (Bilder, CSS usw.) bereitzustellen. Das Servlet sollte die folgenden Eigenschaften haben:

  • Keine externen Abhängigkeiten
  • Einfach und zuverlässig
  • Unterstützung für If-Modified-Since Header (dh benutzerdefinierte getLastModified Methode)
  • (Optional) Unterstützung für gzip-Kodierung, Etags, …

Ist ein solches Servlet irgendwo verfügbar? Das nächste, was ich finden kann, ist Beispiel 4-10 aus dem Servletbuch.

Aktualisieren: Die URL-Struktur, die ich verwenden möchte – falls Sie sich fragen – ist einfach:

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

Daher sollten alle Anfragen an das Haupt-Servlet weitergeleitet werden, es sei denn, sie sind für die static Weg. Das Problem ist, dass das Standard-Servlet von Tomcat den ServletPath nicht berücksichtigt (es sucht also nach den statischen Dateien im Hauptordner), während Jetty dies tut (so sieht es in der static Mappe).

  • Könnten Sie die “URL-Struktur” erläutern, die Sie verwenden möchten? Das eigene Rollen, basierend auf dem verlinkten Beispiel 4-10, scheint ein trivialer Aufwand zu sein. Ich habe es selbst schon oft gemacht…

    – Stu Thompson

    25. September 2008 um 9:12 Uhr

  • Ich habe meine Frage bearbeitet, um die URL-Struktur zu erarbeiten. Und ja, am Ende rollte ich mein eigenes Servlet. Siehe meine Antwort unten.

    – Bruno de Fraine

    25. September 2008 um 12:17 Uhr

  • Warum nutzen Sie den Webserver nicht für statische Inhalte?

    – Stefan

    2. Oktober 2008 um 19:10 Uhr

  • @Stephen: weil nicht immer ein Apache vor dem Tomcat/Jetty steht. Und um den Aufwand einer separaten Konfiguration zu vermeiden. Aber du hast recht, ich könnte diese Option in Erwägung ziehen.

    – Bruno de Fraine

    3. Oktober 2008 um 13:32 Uhr

  • Ich kann einfach nicht verstehen, warum Sie kein Mapping wie dieses default / um statische Inhalte bereitzustellen

    – Maciek Kreft

    5. November 2011 um 9:40 Uhr


1646311030 613 Servlet zum Bereitstellen statischer Inhalte
Taylor Gautier

Ich bin auf eine etwas andere Lösung gekommen. Es ist ein bisschen hackig, aber hier ist die Zuordnung:

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

Dies ordnet im Grunde nur alle Inhaltsdateien per Erweiterung dem Standard-Servlet und alles andere “myAppServlet” zu.

Es funktioniert sowohl in Jetty als auch in Tomcat.

  • Tatsächlich können Sie mehr als ein URL-Muster-Tag innerhalb der Servelet-Zuordnung hinzufügen;)

    – Fared Alnamrouti

    28. Januar 2012 um 14:41 Uhr


  • Servlet 2.5 und neuer unterstützen mehrere URL-Muster-Tags innerhalb der Servlet-Zuordnung

    – Vivid_voidgroup

    22. Mai 2012 um 12:59 Uhr

  • Seien Sie nur vorsichtig mit Indexdateien (index.html), da sie möglicherweise Vorrang vor Ihrem Servlet haben.

    – Andrés

    11. Dezember 2015 um 15:43 Uhr

  • Ich denke, es ist eine schlechte Idee zu verwenden *.sth. Wenn jemand eine URL bekommt example.com/index.jsp?g=.sth Er erhält die Quelle der JSP-Datei. Oder liege ich falsch? (Ich bin neu in Java EE) Ich verwende normalerweise URL-Muster /css/* und ETC.

    – SemperPeritus

    2. Juni 2017 um 15:48 Uhr

In diesem Fall ist keine vollständig benutzerdefinierte Implementierung des Standard-Servlets erforderlich. Sie können dieses einfache Servlet verwenden, um die Anforderung in die Implementierung des Containers einzubinden:


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

  • Diese Frage hat eine nette Möglichkeit, / mit einem Filter einem Controller und /static einem statischen Inhalt zuzuordnen. Überprüfen Sie die positiv bewertete Antwort nach der akzeptierten: stackoverflow.com/questions/870150/…

    – David Carboni

    26. Oktober 2012 um 6:34 Uhr


  • HttpServletRequestWrapper javadoc

    – Ondra Žižka

    22. März 2016 um 1:22 Uhr

1646311031 137 Servlet zum Bereitstellen statischer Inhalte
Will Hartung

Ich habe gute Ergebnisse mit FileServletda es so ziemlich alles von HTTP unterstützt (Etags, Chunking usw.).

  • Danke! Stunden fehlgeschlagener Versuche und schlechter Antworten, und dies löste mein Problem

    – Yossi Shasho

    15. August 2012 um 7:38 Uhr

  • Um Inhalte aus einem Ordner außerhalb der App bereitzustellen (ich verwende ihn, um einen Ordner von der Festplatte zu servern, sagen wir C:\resources), habe ich diese Zeile geändert: this.basePath = getServletContext().getRealPath(getInitParameter(“basePath “)); Und ersetzt durch: this.basePath = getInitParameter(“basePath”);

    – Yossi Shasho

    15. August 2012 um 7:39 Uhr


  • Eine aktualisierte Version ist unter verfügbar showcase.omnifaces.org/servlets/FileServlet

    – koppor

    23. November 2016 um 9:19 Uhr

Servlet zum Bereitstellen statischer Inhalte
BalusC

Abstrakte Vorlage für ein statisches Ressourcen-Servlet

Teilweise basierend auf dieses Blog von 2007, hier ist eine modernisierte und hochgradig wiederverwendbare abstrakte Vorlage für ein Servlet, das sich richtig mit dem Caching befasst, ETag, If-None-Match und If-Modified-Since (aber keine Gzip- und Range-Unterstützung; nur um es einfach zu halten; Gzip könnte mit einem Filter oder über eine Containerkonfiguration erfolgen).

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

}

Verwenden Sie es zusammen mit der folgenden Schnittstelle, die eine statische Ressource darstellt.

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;

}

Sie müssen lediglich das angegebene abstrakte Servlet erweitern und implementieren getStaticResource() Methode gemäß Javadoc.

Konkretes Beispiel für die Bereitstellung aus dem Dateisystem:

Hier ist ein konkretes Beispiel, das es über eine URL wie bedient /files/foo.ext aus dem lokalen Plattendateisystem:

@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() || "https://stackoverflow.com/".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();
            }
        };
    }

}

Konkretes Beispiel für die Bereitstellung aus der Datenbank:

Hier ist ein konkretes Beispiel, das es über eine URL wie bedient /files/foo.ext aus der Datenbank über einen EJB-Serviceaufruf, der Ihre Entität mit a zurückgibt byte[] content Eigentum:

@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() || "https://stackoverflow.com/".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();
            }
        };
    }

}

Servlet zum Bereitstellen statischer Inhalte
Bruno de Fraine

Am Ende habe ich meine eigenen gedreht StaticServlet. Es unterstützt If-Modified-Since, gzip-Kodierung und es sollte in der Lage sein, auch statische Dateien aus Kriegsdateien bereitzustellen. Es ist kein sehr schwieriger Code, aber auch nicht ganz trivial.

Der Code ist verfügbar: StaticServlet.java. Fühlen Sie sich frei zu kommentieren.

Aktualisieren: Khurram fragt nach dem ServletUtils Klasse, auf die verwiesen wird StaticServlet. Es ist einfach eine Klasse mit Hilfsmethoden, die ich für mein Projekt verwendet habe. Die einzige Methode, die Sie brauchen, ist coalesce (was mit der SQL-Funktion identisch ist COALESCE). Dies ist der Code:

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

  • Nennen Sie Ihren inneren Klassenfehler nicht. Das könnte zu Verwirrung führen, da Sie es mit java.lang.Error verwechseln können. Ist Ihre web.xml auch dieselbe?

    – Leonel

    25. September 2008 um 17:10 Uhr

  • Danke für die Fehlerwarnung. web.xml ist identisch, wobei „default“ durch den Namen des StaticServlets ersetzt wird.

    – Bruno de Fraine

    25. September 2008 um 17:25 Uhr

  • Die Coalesce-Methode kann (innerhalb der Servlet-Klasse) durch commons-lang StringUtils.defaultString(String, String) ersetzt werden.

    – Mike Minicki

    25. Juli 2011 um 6:53 Uhr

  • Die Methode transferStreams() kann auch durch Files.copy(is ,os) ersetzt werden;

    – Gerrit Brink

    12. August 2014 um 6:25 Uhr

  • Warum ist dieser Ansatz so beliebt? Warum implementieren die Leute statische Dateiserver so neu? Es gibt so viele Sicherheitslücken, die nur darauf warten, entdeckt zu werden, und so viele Funktionen echter statischer Dateiserver, die nicht implementiert sind.

    – Leonhard Printz

    23. März 2020 um 16:28 Uhr

Servlet zum Bereitstellen statischer Inhalte
Jeff Stice-Hall

Nach den obigen Beispielinformationen zu urteilen, denke ich, dass dieser gesamte Artikel auf einem fehlerhaften Verhalten in Tomcat 6.0.29 und früher basiert. Sehen https://issues.apache.org/bugzilla/show_bug.cgi?id=50026. Aktualisieren Sie auf Tomcat 6.0.30 und das Verhalten zwischen (Tomcat|Jetty) sollte zusammengeführt werden.

  • Nennen Sie Ihren inneren Klassenfehler nicht. Das könnte zu Verwirrung führen, da Sie es mit java.lang.Error verwechseln können. Ist Ihre web.xml auch dieselbe?

    – Leonel

    25. September 2008 um 17:10 Uhr

  • Danke für die Fehlerwarnung. web.xml ist identisch, wobei „default“ durch den Namen des StaticServlets ersetzt wird.

    – Bruno de Fraine

    25. September 2008 um 17:25 Uhr

  • Die Coalesce-Methode kann (innerhalb der Servlet-Klasse) durch commons-lang StringUtils.defaultString(String, String) ersetzt werden.

    – Mike Minicki

    25. Juli 2011 um 6:53 Uhr

  • Die Methode transferStreams() kann auch durch Files.copy(is ,os) ersetzt werden;

    – Gerrit Brink

    12. August 2014 um 6:25 Uhr

  • Warum ist dieser Ansatz so beliebt? Warum implementieren die Leute statische Dateiserver so neu? Es gibt so viele Sicherheitslücken, die nur darauf warten, entdeckt zu werden, und so viele Funktionen echter statischer Dateiserver, die nicht implementiert sind.

    – Leonhard Printz

    23. März 2020 um 16:28 Uhr

1646311033 242 Servlet zum Bereitstellen statischer Inhalte
Gemeinschaft

Versuche dies

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

Bearbeiten: Dies gilt nur für die Servlet 2.5-Spezifikation und höher.

  • Scheint, dass dies keine gültige Konfiguration ist.

    – Gedrox

    22. November 2012 um 7:09 Uhr

923440cookie-checkServlet zum Bereitstellen statischer Inhalte

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy