Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 61ae202fcb5ec004fdf5304e783adc3ba7914862 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//
//  ========================================================================
//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.servlets;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.util.URIUtil;

/**
 * <p>This servlet may be used to concatenate multiple resources into
 * a single response.</p>
 * <p>It is intended to be used to load multiple
 * javascript or css files, but may be used for any content of the
 * same mime type that can be meaningfully concatenated.</p>
 * <p>The servlet uses {@link RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}
 * to combine the requested content, so dynamically generated content
 * may be combined (Eg engine.js for DWR).</p>
 * <p>The servlet uses parameter names of the query string as resource names
 * relative to the context root.  So these script tags:</p>
 * <pre>
 * &lt;script type="text/javascript" src="../js/behaviour.js"&gt;&lt;/script&gt;
 * &lt;script type="text/javascript" src="../js/ajax.js"&gt;&lt;/script&gt;
 * &lt;script type="text/javascript" src="../chat/chat.js"&gt;&lt;/script&gt;
 * </pre>
 * <p>can be replaced with the single tag (with the {@code ConcatServlet}
 * mapped to {@code /concat}):</p>
 * <pre>
 * &lt;script type="text/javascript" src="../concat?/js/behaviour.js&amp;/js/ajax.js&amp;/chat/chat.js"&gt;&lt;/script&gt;
 * </pre>
 * <p>The {@link ServletContext#getMimeType(String)} method is used to determine the
 * mime type of each resource. If the types of all resources do not match, then a 415
 * UNSUPPORTED_MEDIA_TYPE error is returned.</p>
 * <p>If the init parameter {@code development} is set to {@code true} then the servlet
 * will run in development mode and the content will be concatenated on every request.</p>
 * <p>Otherwise the init time of the servlet is used as the lastModifiedTime of the combined content
 * and If-Modified-Since requests are handled with 304 NOT Modified responses if
 * appropriate. This means that when not in development mode, the servlet must be
 * restarted before changed content will be served.</p>
 */
public class ConcatServlet extends HttpServlet
{
    private boolean _development;
    private long _lastModified;

    @Override
    public void init() throws ServletException
    {
        _lastModified = System.currentTimeMillis();
        _development = Boolean.parseBoolean(getInitParameter("development"));
    }

    /*
     * @return The start time of the servlet unless in development mode, in which case -1 is returned.
     */
    @Override
    protected long getLastModified(HttpServletRequest req)
    {
        return _development ? -1 : _lastModified;
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        String query = request.getQueryString();
        if (query == null)
        {
            response.sendError(HttpServletResponse.SC_NO_CONTENT);
            return;
        }

        List<RequestDispatcher> dispatchers = new ArrayList<>();
        String[] parts = query.split("\\&");
        String type = null;
        for (String part : parts)
        {
            String path = URIUtil.canonicalPath(URIUtil.decodePath(part));
            if (path == null)
            {
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
                return;
            }

            // Verify that the path is not protected.
            if (startsWith(path, "/WEB-INF/") || startsWith(path, "/META-INF/"))
            {
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
                return;
            }

            String t = getServletContext().getMimeType(path);
            if (t != null)
            {
                if (type == null)
                {
                    type = t;
                }
                else if (!type.equals(t))
                {
                    response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
                    return;
                }
            }

            RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(path);
            if (dispatcher != null)
                dispatchers.add(dispatcher);
        }

        if (type != null)
            response.setContentType(type);

        for (RequestDispatcher dispatcher : dispatchers)
            dispatcher.include(request, response);
    }

    private boolean startsWith(String path, String prefix)
    {
        // Case insensitive match.
        return prefix.regionMatches(true, 0, path, 0, prefix.length());
    }
}

Back to the top