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