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