Frage

ich ein Webapp auf zwei verschiedene Container (Tomcat und Jetty) bereitstellen, aber die Standard Servlets für den statischen Inhalt dient, haben eine andere Möglichkeit, die URL-Struktur der Handhabung I ( Details ).

Ich suche deshalb ein kleines Servlet in der Webapp umfassen einen eigenen statischen Inhalte (Bilder, CSS, etc.) zu dienen. Das Servlet sollte die folgenden Eigenschaften haben:

  • Keine externen Abhängigkeiten
  • Einfach und zuverlässig
  • Unterstützung für If-Modified-Since Header (dh benutzerdefinierten getLastModified Verfahren)
  • (Optional) Unterstützung für gzip-Codierung, etags, ...

Ist solch ein Servlet irgendwo verfügbar? Der nächstgelegene ich finden können, ist Beispiel 4-10 aus dem Servlet-Buch.

Update: Die URL-Struktur 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>

So alle Anfragen sollten an die Haupt Servlet übergeben werden, es sei denn, sie für den static Weg sind. Das Problem ist, dass Tomcat-Servlet-Standard nicht die ServletPath nicht berücksichtigt (so sieht es für die statischen Dateien im Hauptordner), während Jetty tut (so sieht es aus im static Ordner).

War es hilfreich?

Lösung 5

beendete ich meine eigene StaticServlet Aufrollen. Es unterstützt If-Modified-Since, gzip-Codierung und es sollte auch statische Dateien von Krieg-Dateien dienen kann. Es ist nicht sehr schwierig, Code, aber es ist auch nicht ganz trivial.

Der Code steht zur Verfügung: StaticServlet.java . Fühlen Sie sich frei zu äußern.

Update: Khurram der ServletUtils Klasse fragt nach, die in StaticServlet verwiesen wird. Es ist einfach eine Klasse mit Hilfsmethoden, die ich für mein Projekt verwendet. Die einzige Methode, die Sie brauchen, ist coalesce (die die 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;
}

Andere Tipps

Ich kam mit einer etwas anderen Lösung auf. Es ist ein bisschen Hack-ish, aber hier ist die Abbildung:

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

Diese im Grunde bildet nur alle Inhaltsdateien durch Erweiterung des Standard-Servlet, und alles andere „myAppServlet“.

Es funktioniert in beiden Jetty und Tomcat.

Es besteht keine Notwendigkeit für ganz individuelle Implementierung des Standard-Servlet ist in diesem Fall können Sie diese einfache Servlet verwenden Anforderung an den Container-Implementierung zu wickeln:


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

Ich habe gute Ergebnisse mit FileServlet , da es ziemlich alle HTTP unterstützt (etags, Chunking, usw.).

Abstrakte Vorlage für ein statisches Ressource-Servlet

Teilweise basierend auf diesem Blog aus dem Jahr 2007, hier ist eine modernisierte und hoch wiederverwendbare abstrakte Vorlage für ein Servlet, die mit Caching, ETag, If-None-Match und If-Modified-Since richtig beschäftigt. (aber ohne gzip und Range-Unterstützung, nur um es einfach zu halten; gzip mit einem Filter oder über Container-Konfiguration durchgeführt werden kann)

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 zusammen mit der unten Schnittstelle 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;

}

Alles, was Sie brauchen, ist nur aus dem gegebenen abstrakten Servlet erstreckt und das getStaticResource() Verfahrens zur Durchführung des javadoc nach.

Konkretes Beispiel aus dem Dateisystem dient:

Hier ist ein konkretes Beispiel, die sie über eine URL wie /files/foo.ext von dem lokalen Festplatte Dateisystem dient:

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

}

Konkretes Beispiel aus der Datenbank dienen:

Hier ist ein konkretes Beispiel, die sie über eine URL wie /files/foo.ext aus der Datenbank über einen EJB-Service-Aufruf dient, die Ihr Unternehmen eine byte[] content Eigenschaft zurückgibt:

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

}

aus dem Beispiel Informationen zu urteilen oben, ich denke, der ganze Artikel auf einem abgehörten Verhalten in Tomcat 6.0.29 und früher basiert. Siehe https://issues.apache.org/bugzilla/show_bug.cgi?id=50026 . Upgrade auf Tomcat 6.0.30 und das Verhalten zwischen (Tomcat | Jetty) fusionieren sollte.

try this

<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:. Dies gilt nur für das Servlet 2.5-Spezifikation und bis

Ich hatte das gleiche Problem, und ich löse es durch den Code der ‚default Servlet‘ aus der Tomcat-Codebasis verwendet wird.

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

Die DefaultServlet ist das Servlet, das die statischen Ressourcen dient (jpg, hTML, CSS, gif usw.) in Tomcat.

Dieses Servlet ist sehr effizient und hat einige Eigenschaften, die Sie oben definiert sind.

Ich denke, dass diese Quellcode, ist eine gute Möglichkeit, die Funktionalität oder depedencies Sie nicht benötigen.

zu starten und entfernen
  • Verweise auf die org.apache.naming.resources Paket kann mit java.io.File Code entfernt oder ersetzt werden.
  • Verweise auf das org.apache.catalina.util Paket sind propably nur Utility-Methoden / Klassen, die im Quellcode kopiert werden können.
  • kann Hinweise auf die org.apache.catalina.Globals Klasse inlined oder entfernt werden.

Ich fand großes Tutorial auf dem Internet über einige Abhilfe. Es ist einfach und effizient, habe ich es in mehreren Projekten mit REST Urls Arten Ansatz:

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

Ich tat dies, indem Sie den Kater erstreckt DefaultServlet ( src ) und das Überschreiben der Methode 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);
   }
}

... Und hier sind meine Servletzuordnungen

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

Um alle Anforderungen von einem Frühlings-App zu dienen sowie /favicon.ico und die JSP-Dateien von / WEB-INF / jsp / * dass Spring AbstractUrlBasedView fordern Sie können einfach das jsp Servlet und Standard-Servlet neu zuordnen:

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

Wir können nicht auf dem vertrauen * .jsp url-Muster auf dem Standard-Mapping für den jsp Servlet, weil der Pfad Muster ‚/ *‘ abgestimmt ist, bevor eine Erweiterungszuordnung geprüft. Mapping the jsp Servlet zu einem tieferen Ordner bedeutet es zuerst abgestimmt wird. Matching ‚/favicon.ico‘ genau passiert, bevor Pfad Mustervergleich. Tiefer Weg funktioniert übereinstimmt, oder genau übereinstimmt, aber keine Verlängerung Spiele können es vorbei an dem Spiel ‚/ *‘ Weges machen. Mapping ‚/‘ Servlet auf Standard scheint nicht zu funktionieren. Man sollte meinen, die genaue ‚/‘ die ‚/ *‘ Pfad Muster auf springapp schlagen würde.

Die obige Filterlösung funktioniert nicht für weitergeleitet / enthalten JSP-Anfragen von der Anwendung ab. Damit es funktioniert ich die Filter anwenden musste direkt an springapp, an welcher Stelle des URL-Pattern-Matching war nutzlos, da alle Anforderungen, die an die Anwendung gehen auch auf ihre Filter gehen. So habe ich Muster auf dem Filter entsprechen und lernte dann über die ‚jsp‘ Servlet und sah, dass es nicht den Pfad Präfix nicht entfernt wie die Standard-Servlet der Fall ist. Das löste mein Problem, das nicht genau das gleiche, aber häufig genug war.

für Tomcat 8.x Suche: statische Ressourcen arbeiten, wenn root-Servlet-Karte auf "" OK. Für Servlet 3.x könnte es durch @WebServlet("") erfolgen

Mit org.mortbay.jetty.handler.ContextHandler. Sie benötigen keine zusätzlichen Komponenten wie StaticServlet.

An der Anlegestelle nach Hause,

$ cd Kontexte

$ cp javadoc.xml static.xml

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

Setzen Sie den Wert von contextPath mit Ihrem URL-Präfix, und setzen Sie den Wert von resourceBase als Dateipfad des statischen Inhalts.

Es funktioniert für mich.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top