in hawtio-system/src/main/java/io/hawt/web/proxy/ProxyServlet.java [204:362]
protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
throws ServletException, IOException {
// returns if enabled or not so that Connect plugin can turn on/off itself
if ("/enabled".equals(servletRequest.getPathInfo())) {
ServletHelpers.sendJSONResponse(servletResponse, enabled);
return;
}
if (!enabled) {
servletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// Make the Request
//note: we won't transfer the protocol version because I'm not sure it would truly be compatible
ProxyAddress proxyAddress = parseProxyAddress(servletRequest);
if (proxyAddress == null || proxyAddress.getFullProxyUrl() == null) {
servletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// TODO Implement allowlist protection for Kubernetes services as well
if (proxyAddress instanceof ProxyDetails) {
ProxyDetails details = (ProxyDetails) proxyAddress;
if (!allowlist.isAllowed(details)) {
LOG.debug("Rejecting {}", proxyAddress);
ServletHelpers.doForbidden(servletResponse, ForbiddenReason.HOST_NOT_ALLOWED);
return;
}
}
String method = servletRequest.getMethod();
String proxyRequestUri = proxyAddress.getFullProxyUrl();
URI targetUriObj;
try {
targetUriObj = new URI(proxyRequestUri);
} catch (URISyntaxException e) {
LOG.error("URL '{}' is not valid: {}", proxyRequestUri, e.getMessage());
servletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
HttpRequest proxyRequest;
//spec: RFC 2616, sec 4.3: either of these two headers signal that there is a message body.
if (servletRequest.getHeader(HttpHeaders.CONTENT_LENGTH) != null ||
servletRequest.getHeader(HttpHeaders.TRANSFER_ENCODING) != null) {
HttpEntityEnclosingRequest eProxyRequest = new BasicHttpEntityEnclosingRequest(method, proxyRequestUri);
// Add the input entity (streamed)
// note: we don't bother ensuring we close the servletInputStream since the container handles it
eProxyRequest.setEntity(new InputStreamEntity(servletRequest.getInputStream(), servletRequest.getContentLength()));
proxyRequest = eProxyRequest;
} else {
proxyRequest = new BasicHttpRequest(method, proxyRequestUri);
}
copyRequestHeaders(servletRequest, proxyRequest, targetUriObj);
String username = proxyAddress.getUserName();
String password = proxyAddress.getPassword();
if (Strings.isNotBlank(username) && Strings.isNotBlank(password)) {
String encodedCreds = Base64.encodeBase64String((username + ":" + password).getBytes());
proxyRequest.setHeader("Authorization", "Basic " + encodedCreds);
}
Header proxyAuthHeader = proxyRequest.getFirstHeader("Authorization");
if (proxyAuthHeader != null) {
String proxyAuth = proxyAuthHeader.getValue();
// if remote jolokia credentials have changed, we have to clear session cookies in http-client
HttpSession session = servletRequest.getSession();
if (session != null) {
String previousProxyCredentials = (String) session.getAttribute("proxy-credentials");
if (previousProxyCredentials != null && !previousProxyCredentials.equals(proxyAuth)) {
cookieStore.clear();
}
session.setAttribute("proxy-credentials", proxyAuth);
}
}
try {
setAuthHeaderWithRole(servletRequest, proxyRequest);
} catch (NoSuchAlgorithmException e) {
LOG.debug("Cannot set Authorization header for given role and user", e);
throw new RuntimeException(e);
}
setXForwardedForHeader(servletRequest, proxyRequest);
CloseableHttpResponse proxyResponse = null;
int statusCode = 0;
try {
// Execute the request
if (doLog) {
log("proxy " + method + " uri: " + servletRequest.getRequestURI() + " -- " + proxyRequest.getRequestLine().getUri());
}
LOG.debug("proxy {} uri: {} -- {}", method, servletRequest.getRequestURI(), proxyRequest.getRequestLine().getUri());
proxyResponse = proxyClient.execute(URIUtils.extractHost(targetUriObj), proxyRequest);
// Process the response
statusCode = proxyResponse.getStatusLine().getStatusCode();
if (statusCode == 401 || statusCode == 403) {
if (doLog) {
log("Authentication Failed on remote server " + proxyRequestUri);
}
LOG.debug("Authentication Failed on remote server {}", proxyRequestUri);
} else if (doResponseRedirectOrNotModifiedLogic(servletRequest, servletResponse, proxyResponse, statusCode, targetUriObj)) {
//the response is already "committed" now without any body to send
//TODO copy response headers?
return;
}
// Pass the response code. This method with the "reason phrase" is deprecated but it's the only way to pass the
// reason along too.
//noinspection deprecation
servletResponse.setStatus(statusCode, proxyResponse.getStatusLine().getReasonPhrase());
copyResponseHeaders(proxyResponse, servletResponse);
// Send the content to the client
copyResponseEntity(proxyResponse, servletResponse);
} catch (Exception e) {
//abort request, according to best practice with HttpClient
if (proxyRequest instanceof AbortableHttpRequest) {
AbortableHttpRequest abortableHttpRequest = (AbortableHttpRequest) proxyRequest;
abortableHttpRequest.abort();
}
// Exception needs to be suppressed for security reason
LOG.debug("Proxy to " + proxyRequestUri + " failed", e);
if (e instanceof ConnectException || e instanceof UnknownHostException) {
// Target host refused connection or doesn't exist
servletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
} else if (e instanceof ServletException) {
// Redirect / Not Modified failed
servletResponse.sendError(HttpServletResponse.SC_BAD_GATEWAY, e.getMessage());
} else if (e instanceof SecurityException) {
servletResponse.setHeader("WWW-Authenticate", "Basic");
servletResponse.sendError(statusCode, e.getMessage());
} else {
servletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
}
} finally {
if (proxyResponse != null) {
// Make sure the entire entity was consumed
EntityUtils.consumeQuietly(proxyResponse.getEntity());
try {
proxyResponse.close();
} catch (IOException e) {
LOG.error("Error closing proxy client response: {}", e.getMessage());
}
}
//Note: Don't need to close servlet outputStream:
// http://stackoverflow.com/questions/1159168/should-one-call-close-on-httpservletresponse-getoutputstream-getwriter
}
}