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 Cookie[] cookies = request.getCookies();
179 if (cookies != null)
180 {
181 for (Cookie cookie : cookies)
182 {
183 if (JSESSIONID.equalsIgnoreCase(cookie.getName()))
184 return extractBalancerMemberNameFromSessionId(cookie.getValue());
185 }
186 }
187 return null;
188 }
189
190 private String getBalancerMemberNameFromURL(HttpServletRequest request)
191 {
192 String requestURI = request.getRequestURI();
193 int idx = requestURI.lastIndexOf(";");
194 if (idx > 0)
195 {
196 String requestURISuffix = requestURI.substring(idx + 1);
197 if (requestURISuffix.startsWith(JSESSIONID_URL_PREFIX))
198 return extractBalancerMemberNameFromSessionId(requestURISuffix.substring(JSESSIONID_URL_PREFIX.length()));
199 }
200 return null;
201 }
202
203 private String extractBalancerMemberNameFromSessionId(String sessionId)
204 {
205 int idx = sessionId.lastIndexOf(".");
206 if (idx > 0)
207 {
208 String sessionIdSuffix = sessionId.substring(idx + 1);
209 return sessionIdSuffix.length() > 0 ? sessionIdSuffix : null;
210 }
211 return null;
212 }
213
214 @Override
215 protected String filterResponseHeader(HttpServletRequest request, String headerName, String headerValue)
216 {
217 if (_proxyPassReverse && REVERSE_PROXY_HEADERS.contains(headerName))
218 {
219 URI locationURI = URI.create(headerValue).normalize();
220 if (locationURI.isAbsolute() && isBackendLocation(locationURI))
221 {
222 String newURI = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort();
223 String component = locationURI.getRawPath();
224 if (component != null)
225 newURI += component;
226 component = locationURI.getRawQuery();
227 if (component != null)
228 newURI += "?" + component;
229 component = locationURI.getRawFragment();
230 if (component != null)
231 newURI += "#" + component;
232 return URI.create(newURI).normalize().toString();
233 }
234 }
235 return headerValue;
236 }
237
238 private boolean isBackendLocation(URI locationURI)
239 {
240 for (BalancerMember balancerMember : _balancerMembers)
241 {
242 URI backendURI = balancerMember.getBackendURI();
243 if (backendURI.getHost().equals(locationURI.getHost()) &&
244 backendURI.getScheme().equals(locationURI.getScheme())
245 && backendURI.getPort() == locationURI.getPort())
246 {
247 return true;
248 }
249 }
250 return false;
251 }
252
253 @Override
254 public boolean validateDestination(String host, int port)
255 {
256 return true;
257 }
258
259 private static class BalancerMember
260 {
261 private final String _name;
262 private final String _proxyTo;
263 private final URI _backendURI;
264
265 public BalancerMember(String name, String proxyTo)
266 {
267 _name = name;
268 _proxyTo = proxyTo;
269 _backendURI = URI.create(_proxyTo).normalize();
270 }
271
272 public String getName()
273 {
274 return _name;
275 }
276
277 public String getProxyTo()
278 {
279 return _proxyTo;
280 }
281
282 public URI getBackendURI()
283 {
284 return _backendURI;
285 }
286
287 @Override
288 public String toString()
289 {
290 return String.format("%s[name=%s,proxyTo=%s]", getClass().getSimpleName(), _name, _proxyTo);
291 }
292
293 @Override
294 public int hashCode()
295 {
296 return _name.hashCode();
297 }
298
299 @Override
300 public boolean equals(Object obj)
301 {
302 if (this == obj)
303 return true;
304 if (obj == null)
305 return false;
306 if (getClass() != obj.getClass())
307 return false;
308 BalancerMember that = (BalancerMember)obj;
309 return _name.equals(that._name);
310 }
311 }
312 }