1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.proxy;
20
21 import java.net.URI;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.HashSet;
25 import java.util.LinkedList;
26 import java.util.List;
27 import java.util.Set;
28 import java.util.concurrent.atomic.AtomicLong;
29 import javax.servlet.ServletException;
30 import javax.servlet.UnavailableException;
31 import javax.servlet.http.Cookie;
32 import javax.servlet.http.HttpServletRequest;
33
34 import org.eclipse.jetty.util.URIUtil;
35
36 public class BalancerServlet extends ProxyServlet
37 {
38 private static final String BALANCER_MEMBER_PREFIX = "balancerMember.";
39 private static final List<String> FORBIDDEN_CONFIG_PARAMETERS;
40
41 static
42 {
43 List<String> params = new LinkedList<>();
44 params.add("hostHeader");
45 params.add("whiteList");
46 params.add("blackList");
47 FORBIDDEN_CONFIG_PARAMETERS = Collections.unmodifiableList(params);
48 }
49
50 private static final List<String> REVERSE_PROXY_HEADERS;
51
52 static
53 {
54 List<String> params = new LinkedList<>();
55 params.add("Location");
56 params.add("Content-Location");
57 params.add("URI");
58 REVERSE_PROXY_HEADERS = Collections.unmodifiableList(params);
59 }
60
61 private static final String JSESSIONID = "jsessionid";
62 private static final String JSESSIONID_URL_PREFIX = JSESSIONID + "=";
63
64 private final List<BalancerMember> _balancerMembers = new ArrayList<>();
65 private final AtomicLong counter = new AtomicLong();
66 private boolean _stickySessions;
67 private boolean _proxyPassReverse;
68
69 @Override
70 public void init() throws ServletException
71 {
72 validateConfig();
73 super.init();
74 initStickySessions();
75 initBalancers();
76 initProxyPassReverse();
77 }
78
79 private void validateConfig() throws ServletException
80 {
81 for (String initParameterName : Collections.list(getServletConfig().getInitParameterNames()))
82 {
83 if (FORBIDDEN_CONFIG_PARAMETERS.contains(initParameterName))
84 {
85 throw new UnavailableException(initParameterName + " not supported in " + getClass().getName());
86 }
87 }
88 }
89
90 private void initStickySessions()
91 {
92 _stickySessions = Boolean.parseBoolean(getServletConfig().getInitParameter("stickySessions"));
93 }
94
95 private void initBalancers() throws ServletException
96 {
97 Set<BalancerMember> members = new HashSet<>();
98 for (String balancerName : getBalancerNames())
99 {
100 String memberProxyToParam = BALANCER_MEMBER_PREFIX + balancerName + ".proxyTo";
101 String proxyTo = getServletConfig().getInitParameter(memberProxyToParam);
102 if (proxyTo == null || proxyTo.trim().length() == 0)
103 throw new UnavailableException(memberProxyToParam + " parameter is empty.");
104 members.add(new BalancerMember(balancerName, proxyTo));
105 }
106 _balancerMembers.addAll(members);
107 }
108
109 private void initProxyPassReverse()
110 {
111 _proxyPassReverse = Boolean.parseBoolean(getServletConfig().getInitParameter("proxyPassReverse"));
112 }
113
114 private Set<String> getBalancerNames() throws ServletException
115 {
116 Set<String> names = new HashSet<>();
117 for (String initParameterName : Collections.list(getServletConfig().getInitParameterNames()))
118 {
119 if (!initParameterName.startsWith(BALANCER_MEMBER_PREFIX))
120 continue;
121
122 int endOfNameIndex = initParameterName.lastIndexOf(".");
123 if (endOfNameIndex <= BALANCER_MEMBER_PREFIX.length())
124 throw new UnavailableException(initParameterName + " parameter does not provide a balancer member name");
125
126 names.add(initParameterName.substring(BALANCER_MEMBER_PREFIX.length(), endOfNameIndex));
127 }
128 return names;
129 }
130
131 @Override
132 protected URI rewriteURI(HttpServletRequest request)
133 {
134 BalancerMember balancerMember = selectBalancerMember(request);
135 if (_log.isDebugEnabled())
136 _log.debug("Selected {}", balancerMember);
137 String path = request.getRequestURI();
138 String query = request.getQueryString();
139 if (query != null)
140 path += "?" + query;
141 return URI.create(balancerMember.getProxyTo() + "/" + path).normalize();
142 }
143
144 private BalancerMember selectBalancerMember(HttpServletRequest request)
145 {
146 if (_stickySessions)
147 {
148 String name = getBalancerMemberNameFromSessionId(request);
149 if (name != null)
150 {
151 BalancerMember balancerMember = findBalancerMemberByName(name);
152 if (balancerMember != null)
153 return balancerMember;
154 }
155 }
156 int index = (int)(counter.getAndIncrement() % _balancerMembers.size());
157 return _balancerMembers.get(index);
158 }
159
160 private BalancerMember findBalancerMemberByName(String name)
161 {
162 for (BalancerMember balancerMember : _balancerMembers)
163 {
164 if (balancerMember.getName().equals(name))
165 return balancerMember;
166 }
167 return null;
168 }
169
170 private String getBalancerMemberNameFromSessionId(HttpServletRequest request)
171 {
172 String name = getBalancerMemberNameFromSessionCookie(request);
173 if (name == null)
174 name = getBalancerMemberNameFromURL(request);
175 return name;
176 }
177
178 private String getBalancerMemberNameFromSessionCookie(HttpServletRequest request)
179 {
180 Cookie[] cookies = request.getCookies();
181 if (cookies != null)
182 {
183 for (Cookie cookie : cookies)
184 {
185 if (JSESSIONID.equalsIgnoreCase(cookie.getName()))
186 return extractBalancerMemberNameFromSessionId(cookie.getValue());
187 }
188 }
189 return null;
190 }
191
192 private String getBalancerMemberNameFromURL(HttpServletRequest request)
193 {
194 String requestURI = request.getRequestURI();
195 int idx = requestURI.lastIndexOf(";");
196 if (idx > 0)
197 {
198 String requestURISuffix = requestURI.substring(idx + 1);
199 if (requestURISuffix.startsWith(JSESSIONID_URL_PREFIX))
200 return extractBalancerMemberNameFromSessionId(requestURISuffix.substring(JSESSIONID_URL_PREFIX.length()));
201 }
202 return null;
203 }
204
205 private String extractBalancerMemberNameFromSessionId(String sessionId)
206 {
207 int idx = sessionId.lastIndexOf(".");
208 if (idx > 0)
209 {
210 String sessionIdSuffix = sessionId.substring(idx + 1);
211 return sessionIdSuffix.length() > 0 ? sessionIdSuffix : null;
212 }
213 return null;
214 }
215
216 @Override
217 protected String filterResponseHeader(HttpServletRequest request, String headerName, String headerValue)
218 {
219 if (_proxyPassReverse && REVERSE_PROXY_HEADERS.contains(headerName))
220 {
221 URI locationURI = URI.create(headerValue).normalize();
222 if (locationURI.isAbsolute() && isBackendLocation(locationURI))
223 {
224 StringBuilder newURI = URIUtil.newURIBuilder(request.getScheme(), request.getServerName(), request.getServerPort());
225 String component = locationURI.getRawPath();
226 if (component != null)
227 newURI.append(component);
228 component = locationURI.getRawQuery();
229 if (component != null)
230 newURI.append('?').append(component);
231 component = locationURI.getRawFragment();
232 if (component != null)
233 newURI.append('#').append(component);
234 return URI.create(newURI.toString()).normalize().toString();
235 }
236 }
237 return headerValue;
238 }
239
240 private boolean isBackendLocation(URI locationURI)
241 {
242 for (BalancerMember balancerMember : _balancerMembers)
243 {
244 URI backendURI = balancerMember.getBackendURI();
245 if (backendURI.getHost().equals(locationURI.getHost()) &&
246 backendURI.getScheme().equals(locationURI.getScheme())
247 && backendURI.getPort() == locationURI.getPort())
248 {
249 return true;
250 }
251 }
252 return false;
253 }
254
255 @Override
256 public boolean validateDestination(String host, int port)
257 {
258 return true;
259 }
260
261 private static class BalancerMember
262 {
263 private final String _name;
264 private final String _proxyTo;
265 private final URI _backendURI;
266
267 public BalancerMember(String name, String proxyTo)
268 {
269 _name = name;
270 _proxyTo = proxyTo;
271 _backendURI = URI.create(_proxyTo).normalize();
272 }
273
274 public String getName()
275 {
276 return _name;
277 }
278
279 public String getProxyTo()
280 {
281 return _proxyTo;
282 }
283
284 public URI getBackendURI()
285 {
286 return _backendURI;
287 }
288
289 @Override
290 public String toString()
291 {
292 return String.format("%s[name=%s,proxyTo=%s]", getClass().getSimpleName(), _name, _proxyTo);
293 }
294
295 @Override
296 public int hashCode()
297 {
298 return _name.hashCode();
299 }
300
301 @Override
302 public boolean equals(Object obj)
303 {
304 if (this == obj)
305 return true;
306 if (obj == null)
307 return false;
308 if (getClass() != obj.getClass())
309 return false;
310 BalancerMember that = (BalancerMember)obj;
311 return _name.equals(that._name);
312 }
313 }
314 }