Servlet สำหรับการให้บริการเนื้อหาคงที่


145

ฉันปรับใช้ webapp บนสองตู้คอนเทนเนอร์ที่แตกต่างกัน (Tomcat และ Jetty) แต่เซิร์ฟเล็ตเริ่มต้นสำหรับการให้บริการเนื้อหาคงมีวิธีการจัดการโครงสร้าง URL ที่แตกต่างกันที่ฉันต้องการใช้ ( รายละเอียด )

ดังนั้นฉันจึงต้องการรวมเซิร์ฟเล็ตขนาดเล็กในเว็บแอปเพื่อแสดงเนื้อหาแบบคงที่ของตัวเอง (รูปภาพ, CSS และอื่น ๆ ) เซิร์ฟเล็ตควรมีคุณสมบัติต่อไปนี้:

  • ไม่มีการอ้างอิงภายนอก
  • ง่ายและเชื่อถือได้
  • รองรับIf-Modified-Sinceส่วนหัว (เช่นgetLastModifiedวิธีการที่กำหนดเอง)
  • (ไม่บังคับ) รองรับการเข้ารหัส gzip, etags, ...

servlet นั้นมีอยู่ที่ไหนบ้าง? ฉันสามารถค้นหาได้ใกล้เคียงที่สุดคือตัวอย่างที่ 4-10จากหนังสือ servlet

อัปเดต:โครงสร้าง URL ที่ฉันต้องการใช้ - ในกรณีที่คุณสงสัย - เป็นเพียง:

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

ดังนั้นคำร้องขอทั้งหมดควรถูกส่งผ่านไปยังเซิร์ฟเล็ตหลักยกเว้นว่าเป็นไปตามstaticเส้นทาง ปัญหาคือว่า servlet เริ่มต้นของ Tomcat ไม่ได้คำนึงถึง ServletPath (ดังนั้นจึงมองหาไฟล์สแตติกในโฟลเดอร์หลัก) ในขณะที่ Jetty ทำ (ดังนั้นจึงดูในstaticโฟลเดอร์)


คุณสามารถอธิบายรายละเอียดเกี่ยวกับ "โครงสร้าง URL" ที่คุณต้องการใช้ได้หรือไม่? การหมุนของคุณเองตามตัวอย่างที่เชื่อมโยง 4-10 ดูเหมือนเป็นเรื่องเล็กน้อย ผมเคยทำมันด้วยตัวเองมากมายครั้ง ...
สตู ธ อมป์สัน

ฉันแก้ไขคำถามเพื่ออธิบายโครงสร้าง URL อย่างละเอียด และใช่ฉันสิ้นสุดการหมุน servlet ของฉันเอง ดูคำตอบของฉันด้านล่าง
Bruno De Fraine

1
ทำไมคุณไม่ใช้เว็บเซิร์ฟเวอร์สำหรับเนื้อหาแบบคงที่
สตีเฟ่น

4
@ สตีเฟ่น: เพราะไม่มี Apache ในด้านหน้าของ Tomcat / Jetty เสมอไป และเพื่อหลีกเลี่ยงความยุ่งยากในการกำหนดค่าแยกต่างหาก แต่คุณพูดถูกฉันสามารถพิจารณาตัวเลือกนั้นได้
Bruno De Fraine

ฉันไม่เข้าใจว่าทำไมคุณไม่ใช้การแมปแบบนี้ <servlet-mapping> <servlet-name> ค่าเริ่มต้น </servlet-name> <url-pattern> / </url-pattern> / </url-pattern> </ servlet-mapping > เพื่อแสดงเนื้อหาคงที่
Maciek Kreft

คำตอบ:


53

ฉันคิดวิธีแก้ปัญหาที่แตกต่างออกไปเล็กน้อย มันค่อนข้างแฮ็ค - ish แต่นี่คือแผนที่:

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

โดยทั่วไปจะแมปไฟล์เนื้อหาทั้งหมดโดยขยายไปยังเซิร์ฟเล็ตเริ่มต้นและทุกอย่างอื่นไปที่ "myAppServlet"

มันทำงานได้ทั้ง Jetty และ Tomcat


13
จริงๆแล้วคุณสามารถเพิ่มแท็ก url-pattern ได้มากกว่าหนึ่งแท็กใน servelet-mapping;)
Fareed Alnamrouti

5
Servlet 2.5 และใหม่กว่ารองรับแท็ก url-pattern หลาย ๆ อันในการทำแผนที่ servlet
vivid_voidgroup

เพียงใช้ความระมัดระวังกับไฟล์ดัชนี (index.html) เนื่องจากไฟล์เหล่านี้อาจมีความสำคัญเหนือเซิร์ฟเล็ตของคุณ
Andres

*.sthฉันคิดว่ามันใช้ความคิดที่ดี หากใครบางคนจะได้รับ url example.com/index.jsp?g=.sthเขาจะได้รับแหล่งที่มาของไฟล์ jsp หรือฉันผิด (ฉันใหม่ใน Java EE) ฉันมักจะใช้รูปแบบ URL /css/*และอื่น ๆ
SemperPeritus

46

ไม่จำเป็นต้องมีการปรับใช้ 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);
    }
}

คำถามนี้มีวิธีการแมป / กับตัวควบคุมและ / แบบคงที่กับเนื้อหาแบบคงที่โดยใช้ตัวกรอง ตรวจสอบคำตอบที่ได้รับการโหวตหลังจากคำตอบที่ยอมรับแล้ว: stackoverflow.com/questions/870150/…
David Carboni


30

ฉันได้ผลลัพธ์ที่ดีกับFileServletเนื่องจากมันรองรับ HTTP เกือบทั้งหมด (etags, chunking, ฯลฯ )


ขอบคุณ! ชั่วโมงของความพยายามที่ล้มเหลวและคำตอบที่ไม่ดีและสิ่งนี้สามารถแก้ไขปัญหาของฉันได้
Yossi Shasho

4
แม้ว่าในการให้บริการเนื้อหาจากโฟลเดอร์นอกแอพ (ฉันใช้มันไปยังเซิร์ฟเวอร์โฟลเดอร์จากดิสก์พูด C: \ resources) ฉันแก้ไขแถวนี้: this.basePath = getServletContext (). getRealPath (getInitParameter ("basePath) ")); และแทนที่ด้วย: this.basePath = getInitParameter ("basePath");
Yossi Shasho

1
รุ่นที่ปรับปรุงแล้วมีอยู่ที่showcase.omnifaces.org/servlets/FileServlet
koppor

26

แม่แบบนามธรรมสำหรับเซิร์ฟเล็ตทรัพยากรแบบคงที่

ส่วนบนพื้นฐานของบล็อกนี้จากปี 2007 ที่นี่เป็นที่ทันสมัยและนำมาใช้ใหม่สูงแม่แบบนามธรรมสำหรับเซิร์ฟเล็ตซึ่งต้องเกี่ยวข้องกับแคชETag, If-None-MatchและIf-Modified-Since( แต่ไม่มีการสนับสนุน Gzip และช่วงนั้นก็เพื่อให้มันง่าย; 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 <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));
            }
        }
    }

}

ใช้ร่วมกับอินเทอร์เฟซด้านล่างที่แสดงถึงทรัพยากรแบบคงที่

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;

}

สิ่งที่คุณต้องการเพียงแค่ขยายจากบทคัดย่อเซิร์ฟเล็ตที่กำหนดและนำไปใช้ getStaticResource()วิธีตาม javadoc

ตัวอย่างที่เป็นรูปธรรมที่ให้บริการจากระบบไฟล์:

นี่คือตัวอย่างที่เป็นรูปธรรมซึ่งให้บริการผ่าน URL เช่น/files/foo.extจากระบบไฟล์ดิสก์ภายในเครื่อง:

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

}

1
เรียน @BalusC files/%2e%2e/mysecretfile.txtผมคิดว่าวิธีการของคุณจะเป็นความเสี่ยงที่จะแฮ็กเกอร์ที่ส่งคำขอต่อไปนี้อาจนำทางรางระบบไฟล์: คำขอนี้สร้างfiles/../mysecretfile.txtขึ้น ฉันทดสอบมันกับ Tomcat 7.0.55 พวกเขาเรียกมันว่าการปีนเขาไดเรกทอรี: owasp.org/index.php/Path_Traversal
Cristian Arteaga

1
@Cristian: ใช่เป็นไปได้ ฉันอัปเดตตัวอย่างเพื่อแสดงวิธีการป้องกัน
BalusC

สิ่งนี้ไม่ควรรับ upvotes การให้บริการไฟล์คงที่สำหรับหน้าเว็บด้วย Servlet เช่นนี้เป็นสูตรสำหรับความปลอดภัยภัยพิบัติ ปัญหาดังกล่าวทั้งหมดได้รับการแก้ไขแล้วและไม่มีเหตุผลที่จะใช้วิธีการใหม่ที่กำหนดเองพร้อมกับระเบิดเวลาความปลอดภัยที่ยังไม่ถูกค้นพบมากขึ้นที่จะปิดตัวลง เส้นทางที่ถูกต้องคือการกำหนดค่า Tomcat / GlassFish / Jetty ฯลฯ เพื่อให้บริการเนื้อหาหรือดียิ่งกว่าการใช้ไฟล์เซิร์ฟเวอร์เฉพาะเช่น NGinX
Leonhard Printz

@LeonhardPrintz: ฉันจะลบคำตอบและรายงานกลับไปยังเพื่อนของฉันที่ Tomcat เมื่อคุณชี้ให้เห็นปัญหาด้านความปลอดภัย ไม่มีปัญหา.
BalusC

19

StaticServletฉันสิ้นสุดกลิ้งของตัวเอง รองรับการIf-Modified-Sinceเข้ารหัส gzip และควรให้บริการไฟล์คงที่จากไฟล์ war เช่นกัน มันไม่ได้เป็นรหัสที่ยากมาก แต่ก็ไม่ได้เป็นเรื่องเล็กน้อยอย่างใดอย่างหนึ่ง

รหัสที่สามารถใช้ได้: StaticServlet.java รู้สึกอิสระที่จะแสดงความคิดเห็น

ปรับปรุง:รัมถามเกี่ยวกับระดับที่ถูกอ้างถึงในServletUtils StaticServletมันเป็นคลาสที่มีวิธีการเสริมที่ฉันใช้สำหรับโครงการของฉัน วิธีการเดียวที่คุณต้องการคือcoalesce(ซึ่งเหมือนกับฟังก์ชัน SQL COALESCE) นี่คือรหัส:

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

2
อย่าตั้งชื่อข้อผิดพลาดคลาสภายในของคุณ ที่อาจทำให้เกิดความสับสนในขณะที่คุณสามารถเข้าใจผิดสำหรับ java.lang.Error นอกจากนี้ web.xml ของคุณเหมือนกันหรือไม่
Leonel

ขอบคุณสำหรับคำเตือนข้อผิดพลาด web.xml เหมือนกันโดยที่ "default" ถูกแทนที่ด้วยชื่อของ StaticServlet
Bruno De Fraine

1
สำหรับวิธีการรวมกันมันสามารถถูกแทนที่ (ภายในคลาส Servlet) โดย commons-lang StringUtils.defaultString (สตริงสตริง)
Mike Minicki

เมธอด transferStreams () สามารถถูกแทนที่ด้วย Files.copy (คือ, os);
Gerrit Brink

ทำไมวิธีนี้จึงได้รับความนิยม? เหตุใดผู้คนจึงปรับใช้เซิร์ฟเวอร์ไฟล์แบบคงที่เช่นนี้อีกครั้ง มีช่องโหว่ความปลอดภัยมากมายที่รอการค้นพบและคุณสมบัติมากมายของเซิร์ฟเวอร์ไฟล์สแตติกจริงที่ไม่ได้ใช้งาน
Leonhard Printz

12

ตัดสินจากข้อมูลตัวอย่างข้างต้นฉันคิดว่าบทความทั้งหมดนี้ขึ้นอยู่กับพฤติกรรมบั๊กใน Tomcat 6.0.29 และก่อนหน้า ดูhttps://issues.apache.org/bugzilla/show_bug.cgi?id=50026 อัปเกรดเป็น Tomcat 6.0.30 และพฤติกรรมระหว่าง (Tomcat | Jetty) ควรผสาน


1
นั่นเป็นความเข้าใจของฉันsvn diff -c1056763 http://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk/ด้วย ในที่สุดนานหลังจากทำเครื่องหมาย WONTFIX นี้ +3 ปีที่แล้ว!
Bruno De Fraine

12

ลองนี้

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

แก้ไข: สิ่งนี้ใช้ได้สำหรับข้อมูลจำเพาะ servlet 2.5 และสูงกว่าเท่านั้น


ดูเหมือนว่านี่ไม่ใช่การกำหนดค่าที่ถูกต้อง
Gedrox

10

ฉันมีปัญหาเดียวกันและฉันแก้ไขมันโดยใช้รหัสของ 'servlet เริ่มต้น' จาก Tomcat codebase

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

DefaultServletเป็นเซิร์ฟเล็ตที่ให้บริการทรัพยากรแบบคงที่ (JPG, HTML, CSS, gif ฯลฯ ) ใน Tomcat

เซิร์ฟเล็ตนี้มีประสิทธิภาพมากและมีคุณสมบัติบางอย่างที่คุณกำหนดไว้ด้านบน

ฉันคิดว่าซอร์สโค้ดนี้เป็นวิธีที่ดีในการเริ่มต้นและลบการทำงานหรือการพึ่งพาที่คุณไม่ต้องการ

  • การอ้างอิงถึงแพ็กเกจ org.apache.naming.resources สามารถลบหรือแทนที่ด้วยรหัส java.io.File
  • การอ้างอิงถึงแพ็กเกจ org.apache.catalina.util เป็นไปได้เพียงวิธี / ชั้นเรียนอรรถประโยชน์ที่สามารถทำซ้ำในซอร์สโค้ดของคุณ
  • การอ้างอิงถึงคลาส org.apache.catalina.Globals สามารถถูกแทรกหรือลบออกได้

org.apache.*มันน่าจะขึ้นอยู่กับหลายสิ่งจาก คุณจะใช้กับ Jetty ได้อย่างไร?
Bruno De Fraine

คุณพูดถูก, รุ่นนี้มีการพึ่งพา Tomcat มากเกินไป (และมันยังรองรับหลาย ๆ อย่างที่คุณอาจไม่ต้องการฉันจะแก้ไขคำตอบของฉัน
Panagiotis Korros


4

ฉันทำสิ่งนี้โดยขยาย tomcat DefaultServlet ( src ) และแทนที่เมธอด 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);
   }
}

... และนี่คือการแม็ปเซิร์ฟเล็ตของฉัน

<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

ในการให้บริการการร้องขอทั้งหมดจากแอพ Spring รวมถึง /favicon.ico และไฟล์ JSP จาก / WEB-INF / jsp / * ที่ AbstractUrlBasedView ของ Spring จะร้องขอคุณสามารถทำการแมปเซิร์ฟเล็ต jsp และ servlet เริ่มต้นใหม่ได้:

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

เราไม่สามารถพึ่งพารูปแบบ url * .jsp ในการแม็พมาตรฐานสำหรับ jsp servlet เนื่องจากรูปแบบพา ธ '/ *' ถูกจับคู่ก่อนการแมปส่วนขยายใด ๆ จะถูกตรวจสอบ การแม็พเซิร์ฟเล็ต jsp กับโฟลเดอร์ที่ลึกกว่าหมายถึงการจับคู่ก่อน การจับคู่ '/favicon.ico' จะเกิดขึ้นก่อนการจับคู่รูปแบบเส้นทาง การจับคู่เส้นทางที่ลึกกว่านั้นจะใช้งานได้หรือการจับคู่ที่ตรงกัน แต่ไม่มีการจับคู่ส่วนขยายที่สามารถผ่านการจับคู่เส้นทาง '/ *' ได้ การแมป '/' ไปยังเซิร์ฟเล็ตเริ่มต้นไม่ทำงาน คุณคิดว่า '/' จะเอาชนะรูปแบบเส้นทาง '/ *' ที่แน่นอนใน springapp

โซลูชันตัวกรองด้านบนไม่ทำงานสำหรับคำขอ JSP ที่ส่งต่อ / รวมจากแอปพลิเคชัน เพื่อให้มันทำงานได้ฉันต้องใช้ตัวกรองกับ springapp โดยตรงตรงจุดนั้นการจับคู่รูปแบบ URL นั้นไร้ประโยชน์เนื่องจากคำขอทั้งหมดที่ไปยังแอปพลิเคชันนั้นไปที่ตัวกรอง ดังนั้นฉันจึงเพิ่มรูปแบบการจับคู่กับตัวกรองแล้วเรียนรู้เกี่ยวกับ servlet 'jsp' และเห็นว่ามันไม่ได้ลบคำนำหน้าเส้นทางเช่นที่ servlet เริ่มต้นทำ นั่นช่วยแก้ไขปัญหาของฉันซึ่งไม่เหมือนกันทุกประการ แต่พบได้บ่อยพอ


1

ตรวจสอบ Tomcat 8.x: ทรัพยากรแบบสแตติกทำงานได้ดีถ้ารูทเซิร์ฟเล็ตแมปกับ "" สำหรับ servlet 3.x สามารถทำได้โดย@WebServlet("")


0

ใช้ org.mortbay.jetty.handler.ContextHandler คุณไม่ต้องการส่วนประกอบเพิ่มเติมเช่น StaticServlet

ที่บ้านท่าเทียบเรือ

บริบท $ cd

$ 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 ด้วยคำนำหน้า URL ของคุณและตั้งค่าของ resourceBase เป็นพา ธ ไฟล์ของเนื้อหาสแตติก

มันใช้งานได้สำหรับฉัน


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.