Statik içerik sunmak için servlet


146

Bir web uygulamasını iki farklı kapsayıcıya (Tomcat ve Jetty) dağıtıyorum, ancak statik içeriği sunmak için varsayılan sunucu uygulamaları kullanmak istediğim URL yapısını farklı bir şekilde ele alıyor ( ayrıntılar ).

Bu nedenle web uygulamasına kendi statik içeriğini (resimler, CSS, vb.) Sunmak için küçük bir sunucu uygulaması eklemeyi düşünüyorum. Sunucu uygulamacığı aşağıdaki özelliklere sahip olmalıdır:

  • Dış bağımlılık yok
  • Basit ve güvenilir
  • If-Modified-SinceBaşlık desteği (yani özel getLastModifiedyöntem)
  • (İsteğe bağlı) gzip kodlaması, etags, ... desteği

Böyle bir servlet bir yerlerde mevcut mu? Bulabildiğim en yakın örnek , servlet kitabındaki 4-10 örneğidir .

Güncelleme: Kullanmak istediğim URL yapısı - merak ediyorsanız - basitçe:

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

Bu nedenle, staticyol için olmadıkları sürece tüm istekler ana sunucu uygulamasına geçirilmelidir . Sorun, Tomcat'in varsayılan sunucu uygulamasının ServletPath'i hesaba katmaması (bu nedenle ana klasördeki statik dosyaları arar), Jetty ise (bu nedenle staticklasörde görünür ) olmasıdır.


Kullanmak istediğiniz "URL yapısını" biraz açabilir misiniz? Bağlantılı örnek 4-10'a göre kendi başınızı çevirmek önemsiz bir çaba gibi görünüyor. Kendim defalarca yaptım ...
Stu Thompson

URL yapısını detaylandırmak için sorumu düzenledim. Ve evet, sonunda kendi servletimi yuvarladım. Aşağıdaki cevabıma bakın.
Bruno De Fraine

1
Statik içerik için neden web sunucusunu kullanmıyorsunuz?
Stephen

4
@Stephen: Çünkü Tomcat / Jetty'nin önünde her zaman bir Apache yoktur. Ve ayrı bir konfigürasyon zahmetinden kaçınmak için. Ama haklısın, bu seçeneği düşünebilirim.
Bruno De Fraine

Neden böyle <servlet-mapping> <servlet-name> default </servlet-name> <url-pattern> / </url-pattern> </ servlet-mapping gibi bir eşleme kullanmadığınızı anlayamıyorum > statik içerik sunmak için
Maciek Kreft

Yanıtlar:


54

Biraz farklı bir çözüm buldum. Bu biraz hack-ish, ama işte eşleme:

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

Bu temelde tüm içerik dosyalarını uzantıya göre varsayılan sunucu uygulamasına ve diğer her şeyi "myAppServlet" e eşler.

Hem Jetty hem de Tomcat'te çalışır.


13
aslında
servelet

5
Servlet 2.5 ve daha yeni
sürüm,

Yalnızca dizin dosyalarına (index.html) dikkat edin, çünkü bunlar sunucu uygulamanıza göre öncelikli olabilir.
Andres

Bence kötü fikir kullanımı *.sth. Biri url example.com/index.jsp?g=.sthalacaksa jsp dosyasının kaynağını alacaktır. Ya da ben hatalıyım? (Java /css/*
EE'de yeniyim

46

Bu durumda, varsayılan sunucu uygulamasının tamamen özel bir şekilde uygulanmasına gerek yoktur, bu basit sunucu uygulamasını, isteği kapsayıcının uygulamasına sarmak için kullanabilirsiniz:


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

Bu soru, bir filtre kullanarak / bir denetleyiciye ve / statik içeriğe bir filtre kullanarak haritalamanın düzgün bir yoluna sahiptir. Kabul edilen cevaptan sonra oy verilen yanıtı kontrol edin: stackoverflow.com/questions/870150/…
David Carboni


30

Hemen hemen tüm HTTP'yi (etags, chunking, vb.) Desteklediği için FileServlet ile iyi sonuçlar aldım.


Teşekkürler! saatlerce başarısız girişimler ve kötü cevaplar ve bu benim sorunumu çözdü
Yossi Shasho

4
Uygulamanın dışındaki bir klasörden içerik sunmak için (bunu diskteki bir klasöre sunucu olarak kullanıyorum, C: \ kaynaklar diyorum) Bu satırı değiştirdim: this.basePath = getServletContext (). GetRealPath (getInitParameter ("basePath ")); Ve şu şekilde değiştirildi: this.basePath = getInitParameter ("basePath");
Yossi Shasho

1
Güncellenmiş bir versiyon showcase.omnifaces.org/servlets/FileServlet
koppor

26

Statik kaynak sunucu uygulaması için soyut şablon

Kısmen dayanan bu blog 2007'den itibaren, burada düzgün önbelleğe alma ile ilgilenen, bir sunucu uygulaması için modernize ve son derece yeniden kullanılabilir soyut şablon var ETag, If-None-Matchve If-Modified-Since(ancak hiçbir Gzip ve Range desteği, sadece basit tutmak; Gzip bir filtre ile veya üzeri yapılabilirdi kapsayıcı yapılandırması).

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

}

Statik bir kaynağı temsil eden aşağıdaki arayüzle birlikte kullanın.

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;

}

İhtiyacınız olan tek şey, verilen soyut sunucu uygulamasından genişletmek ve getStaticResource() yöntemi javadoc'a göre .

Dosya sisteminden somut örnek sunum:

İşte /files/foo.extyerel disk dosya sistemindeki gibi bir URL aracılığıyla ona hizmet eden somut bir örnek :

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

}

Veritabanından somut örnek sunum:

İşte /files/foo.extsize bir byte[] contentözelliğe sahip olan varlığınızı döndüren bir EJB servis çağrısı yoluyla veritabanından olduğu gibi bir URL aracılığıyla hizmet veren somut bir örnek :

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

}

1
Sevgili @BalusC ben yaklaşımınız dosya sistemini teknesi gezinmek olabilecek aşağıdaki isteği göndererek bir hacker karşı savunmasız olduğunu düşünüyorum: files/%2e%2e/mysecretfile.txt. Bu istek üretir files/../mysecretfile.txt. Tomcat 7.0.55'te test ettim. Buna dizin tırmanışı diyorlar
Cristian Arteaga

1
@Cristian: Evet, mümkün. Bunun nasıl önleneceğini göstermek için örneği güncelledim.
BalusC

Bu olumlu oylar almamalıdır. Servlet ile bir web sayfası için statik dosyaların bu şekilde sunulması, felaket güvenliği açısından bir reçetedir. Tüm bu tür sorunlar halihazırda çözüldü ve muhtemelen daha keşfedilmemiş güvenlikli saatli bombaların patlaması için yeni bir Özel yöntem uygulamak için hiçbir neden yok. Doğru yol, Tomcat / GlassFish / Jetty vb .'yi içeriğe hizmet edecek şekilde yapılandırmak veya daha da iyisi NGinX gibi özel bir dosya sunucusu kullanmaktır.
Leonhard Printz

@LeonhardPrintz: Güvenlik sorunlarına işaret ettiğinizde cevabı sileceğim ve Tomcat'teki arkadaşlarıma bildireceğim. Sorun değil.
BalusC

19

Kendi kendime baktım StaticServlet. If-Modified-SinceGzip kodlamasını destekler ve war dosyalarından statik dosyalar sunabilmelidir. Çok zor bir kod değil, ama tamamen önemsiz de değil.

Kod mevcuttur: StaticServlet.java . Yorum yapmaktan çekinmeyin.

Güncelleme: Khurram ServletUtils, içinde referans verilen sınıfı sorar StaticServlet. Projem için kullandığım yardımcı metotları olan bir sınıf. İhtiyacınız olan tek yöntem coalesce(SQL işlevi ile aynıdır COALESCE). Kod bu:

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

2
İç sınıfınıza Hata adını vermeyin. Bu, java.lang.Error ile karıştırabileceğiniz için kafa karışıklığına neden olabilir. Ayrıca, web.xml dosyanız da aynı mı?
Leonel

Hata uyarısı için teşekkürler. web.xml aynıdır ve "varsayılan", StaticServlet'in adıyla değiştirilir.
Bruno De Fraine

1
Birleştirme yöntemine gelince, (Servlet sınıfının içinde) commons-lang StringUtils.defaultString (String, String) ile değiştirilebilir
Mike Minicki

TransferStreams () yöntemi, Files.copy (is, os) ile de değiştirilebilir;
Gerrit Brink

Bu yaklaşım neden bu kadar popüler? İnsanlar neden statik dosya sunucularını bunun gibi yeniden uyguluyor? Keşfedilmeyi bekleyen çok sayıda güvenlik açığı ve gerçek statik dosya sunucularının uygulanmayan pek çok özelliği var.
Leonhard Printz

12

Yukarıdaki örnek bilgilerden yola çıkarak, bu makalenin tamamının Tomcat 6.0.29 ve önceki sürümlerdeki hatalı davranışa dayandığını düşünüyorum. Bkz. Https://issues.apache.org/bugzilla/show_bug.cgi?id=50026 . Tomcat 6.0.30'a yükseltin ve (Tomcat | Jetty) arasındaki davranış birleşmelidir.


1
Benim de anladığım bu svn diff -c1056763 http://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk/. Sonunda, bu WONTFIX'i +3 yıl önce işaretledikten sonra!
Bruno De Fraine

12

bunu dene

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

Düzenleme: Bu yalnızca sunucu uygulaması 2.5 spesifikasyonu ve üstü için geçerlidir.


Görünüşe göre bu geçerli bir yapılandırma değil.
Gedrox

10

Aynı sorunu yaşadım ve Tomcat kod tabanındaki 'varsayılan sunucu uygulamasının' kodunu kullanarak çözdüm.

https://github.com/apache/tomcat/blob/master/java/org/apache/catalina/servlets/DefaultServlet.java

DefaultServlet Tomcat statik kaynakları (jpg, html, css, gif vs) vermektedir servlet olduğunu.

Bu sunucu uygulaması çok verimlidir ve yukarıda tanımladığınız bazı özelliklere sahiptir.

Bu kaynak kodun, ihtiyacınız olmayan işlevselliği veya bağımlılıkları başlatmak ve kaldırmak için iyi bir yol olduğunu düşünüyorum.

  • Org.apache.naming.resources paketine yapılan referanslar kaldırılabilir veya java.io.File kodu ile değiştirilebilir.
  • Org.apache.catalina.util paketine yapılan atıflar muhtemelen yalnızca kaynak kodunuzda çoğaltılabilen yardımcı yöntemler / sınıflardır.
  • Org.apache.catalina.Globals sınıfına yapılan başvurular satır içi olabilir veya kaldırılabilir.

Bir çok şeye bağlı görünüyor org.apache.*. Jetty ile nasıl kullanabilirsiniz?
Bruno De Fraine

Haklısın, bu sürümde Tomcat için çok fazla bağımlılık var (ayrıca istemeyebileceğiniz birçok şeyi de destekliyor. Cevabımı düzenleyeceğim.
Panagiotis Korros


4

Bunu tomcat DefaultServlet'i ( src ) genişleterek ve getRelativePath () yöntemini geçersiz kılarak yaptım.

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

... Ve işte servlet eşlemelerim

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

1

Bir Spring uygulamasından ve /favicon.ico'dan gelen tüm istekleri ve Spring'in AbstractUrlBasedView'unun isteyeceği / WEB-INF / jsp / * içindeki JSP dosyalarını karşılamak için jsp sunucu uygulamasını ve varsayılan sunucu uygulamasını yeniden eşleştirebilirsiniz:

  <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 sunucu uygulaması için standart eşlemede * .jsp url kalıbına güvenemeyiz çünkü herhangi bir uzantı eşlemesi kontrol edilmeden önce '/ *' yol kalıbı eşleşir. Jsp sunucu uygulamasını daha derin bir klasörle eşlemek, önce eşleştirildiği anlamına gelir. '/Favicon.ico' ile eşleştirme, tam olarak yol kalıbı eşleşmesinden önce gerçekleşir. Daha derin yol eşleşmeleri veya tam eşleşmeler işe yarayacaktır, ancak hiçbir uzantı eşleşmesi "/ *" yol eşleşmesini geçemez. Varsayılan sunucu uygulamasına '/' eşlemesi işe yaramıyor gibi görünüyor. Springapp'taki '/ *' yol kalıbını tam '/' geçeceğini düşünürsünüz.

Yukarıdaki filtre çözümü, uygulamadan iletilen / dahil edilen JSP talepleri için çalışmaz. Çalışmasını sağlamak için filtreyi doğrudan springapp'a uygulamam gerekiyordu, bu noktada url kalıbı eşleştirmesi işe yaramazdı çünkü uygulamaya giden tüm istekler de filtrelerine gitti. Bu yüzden filtreye desen eşleştirme ekledim ve sonra 'jsp' sunucu uygulamasını öğrendim ve varsayılan sunucu uygulamasının yaptığı gibi yol önekini kaldırmadığını gördüm. Bu, sorunumu çözdü, tam olarak aynı değil, yeterince yaygın.


1

Tomcat 8.x için kontrol edildi: kök sunucu uygulaması "" ile eşleşirse statik kaynaklar sorunsuz çalışır. Servlet 3.x için, şu şekilde yapılabilir:@WebServlet("")


0

Org.mortbay.jetty.handler.ContextHandler'ı kullanın. StaticServlet gibi ek bileşenlere ihtiyacınız yoktur.

İskelede evde,

$ cd bağlamları

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

ContextPath değerini URL önekinizle ayarlayın ve resourceBase değerini statik içeriğin dosya yolu olarak ayarlayın.

Benim için çalıştı.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.