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