1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.servlets;
20
21 import java.io.IOException;
22 import java.util.ArrayDeque;
23 import java.util.Queue;
24 import java.util.concurrent.ConcurrentHashMap;
25 import java.util.concurrent.ConcurrentMap;
26
27 import javax.servlet.Filter;
28 import javax.servlet.FilterChain;
29 import javax.servlet.FilterConfig;
30 import javax.servlet.ServletException;
31 import javax.servlet.ServletRequest;
32 import javax.servlet.ServletRequestEvent;
33 import javax.servlet.ServletRequestListener;
34 import javax.servlet.ServletResponse;
35 import javax.servlet.http.HttpSession;
36
37 import org.eclipse.jetty.http.HttpHeader;
38 import org.eclipse.jetty.http.HttpURI;
39 import org.eclipse.jetty.server.PushBuilder;
40 import org.eclipse.jetty.server.Request;
41 import org.eclipse.jetty.server.Response;
42 import org.eclipse.jetty.util.log.Log;
43 import org.eclipse.jetty.util.log.Logger;
44
45 public class PushSessionCacheFilter implements Filter
46 {
47 private static final String TARGET_ATTR = "PushCacheFilter.target";
48 private static final String TIMESTAMP_ATTR = "PushCacheFilter.timestamp";
49 private static final Logger LOG = Log.getLogger(PushSessionCacheFilter.class);
50 private final ConcurrentMap<String, Target> _cache = new ConcurrentHashMap<>();
51 private long _associateDelay = 5000L;
52
53 @Override
54 public void init(FilterConfig config) throws ServletException
55 {
56 if (config.getInitParameter("associateDelay") != null)
57 _associateDelay = Long.valueOf(config.getInitParameter("associateDelay"));
58
59
60
61 config.getServletContext().addListener(new ServletRequestListener()
62 {
63
64 @Override
65 public void requestDestroyed(ServletRequestEvent sre)
66 {
67 Request request = Request.getBaseRequest(sre.getServletRequest());
68 Target target = (Target)request.getAttribute(TARGET_ATTR);
69 if (target == null)
70 return;
71
72
73 Response response = request.getResponse();
74 target._etag = response.getHttpFields().get(HttpHeader.ETAG);
75 target._lastModified = response.getHttpFields().get(HttpHeader.LAST_MODIFIED);
76
77
78 if (request.isPush())
79 {
80 if (LOG.isDebugEnabled())
81 LOG.debug("Pushed {} for {}", request.getResponse().getStatus(), request.getRequestURI());
82 return;
83 }
84 else if (LOG.isDebugEnabled())
85 {
86 LOG.debug("Served {} for {}", request.getResponse().getStatus(), request.getRequestURI());
87 }
88
89
90 String referer = request.getHttpFields().get(HttpHeader.REFERER);
91
92 if (referer != null)
93 {
94
95 HttpURI referer_uri = new HttpURI(referer);
96 if (request.getServerName().equals(referer_uri.getHost()))
97 {
98 Target referer_target = _cache.get(referer_uri.getPath());
99 if (referer_target != null)
100 {
101 HttpSession session = request.getSession();
102 ConcurrentHashMap<String, Long> timestamps = (ConcurrentHashMap<String, Long>)session.getAttribute(TIMESTAMP_ATTR);
103 Long last = timestamps.get(referer_target._path);
104 if (last != null && (System.currentTimeMillis() - last) < _associateDelay)
105 {
106 if (referer_target._associated.putIfAbsent(target._path, target) == null)
107 {
108 if (LOG.isDebugEnabled())
109 LOG.debug("ASSOCIATE {}->{}", referer_target._path, target._path);
110 }
111 }
112 }
113 }
114 }
115 }
116
117 @Override
118 public void requestInitialized(ServletRequestEvent sre)
119 {
120 }
121 });
122 }
123
124 @Override
125 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
126 {
127
128 Request baseRequest = Request.getBaseRequest(request);
129 String uri = baseRequest.getRequestURI();
130
131 if (LOG.isDebugEnabled())
132 LOG.debug("{} {} push={}", baseRequest.getMethod(), uri, baseRequest.isPush());
133
134 HttpSession session = baseRequest.getSession(true);
135
136
137 Target target = _cache.get(uri);
138 if (target == null)
139 {
140 Target t = new Target(uri);
141 target = _cache.putIfAbsent(uri, t);
142 target = target == null ? t : target;
143 }
144 request.setAttribute(TARGET_ATTR, target);
145
146
147 ConcurrentHashMap<String, Long> timestamps = (ConcurrentHashMap<String, Long>)session.getAttribute(TIMESTAMP_ATTR);
148 if (timestamps == null)
149 {
150 timestamps = new ConcurrentHashMap<>();
151 session.setAttribute(TIMESTAMP_ATTR, timestamps);
152 }
153 timestamps.put(uri, System.currentTimeMillis());
154
155
156 if (baseRequest.isPushSupported() && !baseRequest.isPush() && !target._associated.isEmpty())
157 {
158
159 Queue<Target> queue = new ArrayDeque<>();
160 queue.offer(target);
161 while (!queue.isEmpty())
162 {
163 Target parent = queue.poll();
164 PushBuilder builder = baseRequest.getPushBuilder();
165 builder.addHeader("X-Pusher", PushSessionCacheFilter.class.toString());
166 for (Target child : parent._associated.values())
167 {
168 queue.offer(child);
169
170 String path = child._path;
171 if (LOG.isDebugEnabled())
172 LOG.debug("PUSH {} <- {}", path, uri);
173
174 builder.path(path).etag(child._etag).lastModified(child._lastModified).push();
175 }
176 }
177 }
178
179 chain.doFilter(request, response);
180 }
181
182 @Override
183 public void destroy()
184 {
185 _cache.clear();
186 }
187
188 private static class Target
189 {
190 private final String _path;
191 private final ConcurrentMap<String, Target> _associated = new ConcurrentHashMap<>();
192 private volatile String _etag;
193 private volatile String _lastModified;
194
195 private Target(String path)
196 {
197 _path = path;
198 }
199
200 @Override
201 public String toString()
202 {
203 return String.format("Target{p=%s,e=%s,m=%s,a=%d}", _path, _etag, _lastModified, _associated.size());
204 }
205 }
206 }