com.caucho.network.proxy.HttpProxyClient Maven / Gradle / Ivy
/*
* Copyright (c) 1998-2013 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Paul Cowan
*/
package com.caucho.network.proxy;
import java.io.*;
import java.util.Enumeration;
import java.util.logging.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.caucho.cloud.loadbalance.*;
import com.caucho.network.balance.ClientSocket;
import com.caucho.network.proxy.ProxyResult.ProxyStatus;
import com.caucho.server.http.CauchoRequest;
import com.caucho.util.L10N;
import com.caucho.vfs.*;
public class HttpProxyClient
{
private static final Logger log =
Logger.getLogger(HttpProxyClient.class.getName());
private static final L10N L = new L10N(HttpProxyClient.class);
private LoadBalanceManager _loadBalancer;
public HttpProxyClient(LoadBalanceManager loadBalancer)
{
_loadBalancer = loadBalancer;
}
protected LoadBalanceManager getLoadBalancer()
{
return _loadBalancer;
}
public void handleRequest(HttpServletRequest req,
HttpServletResponse res)
{
String sessionId = getSessionId(req);
ClientSocket client = getLoadBalancer().openSticky(sessionId, req, null);
if (client == null) {
proxyFailure(req, res, null, "no backend servers available", true);
return;
}
String uri = constructURI(req);
long requestStartTime = System.currentTimeMillis();
ProxyResult result = proxy(req, res, uri, sessionId, client);
if (! result.isSuccess()) {
proxyFailure(req, res, null, result.getFailureMessage(), true);
}
if (result.isKeepAlive()) {
client.free(requestStartTime);
} else {
client.close();
}
}
protected void proxyFailure(HttpServletRequest req,
HttpServletResponse res,
ClientSocket client,
String reason,
boolean send503)
{
log.warning(L.l("{0}: proxy {1} failed for {2}: {3}",
this,
client != null ? client : "",
req.getRequestURI(),
reason));
if (res.isCommitted())
return;
if (send503) {
try {
res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
} catch (IOException e) {
log.log(Level.FINE, L.l("{0}: failed to send error {1}: {2}",
this,
req.getRequestURI(),
e.getMessage()), e);
}
}
}
protected ProxyResult proxy(HttpServletRequest req,
HttpServletResponse res,
String uri,
String sessionId,
ClientSocket client)
{
ReadStream clientIn = client.getInputStream();
WriteStream clientOut = client.getOutputStream();
try {
clientOut.print(req.getMethod());
clientOut.print(' ');
printUtf8(clientOut, uri);
//clientOut.print(uri);
clientOut.print(" HTTP/1.1\r\n");
String host = req.getHeader("Host");
if (host == null)
host = req.getServerName() + ":" + req.getServerPort();
clientOut.print("Host: ");
clientOut.print(host);
clientOut.print("\r\n");
clientOut.print("X-Forwarded-For: ");
clientOut.print(req.getRemoteAddr());
clientOut.print("\r\n");
Enumeration e = req.getHeaderNames();
while (e.hasMoreElements()) {
String name = e.nextElement();
if (name.equalsIgnoreCase("Connection"))
continue;
else if (name.equalsIgnoreCase("Host"))
continue;
Enumeration e1 = req.getHeaders(name);
while (e1.hasMoreElements()) {
String value = e1.nextElement();
clientOut.print(name);
clientOut.print(": ");
clientOut.print(value);
clientOut.print("\r\n");
}
}
final int contentLength = req.getContentLength();
InputStream is = req.getInputStream();
TempBuffer tempBuffer = TempBuffer.allocate();
byte []buffer = tempBuffer.getBuffer();
boolean isFirst = true;
if (contentLength >= 0) {
isFirst = false;
clientOut.print("\r\n");
}
int len;
while ((len = is.read(buffer, 0, buffer.length)) > 0) {
if (isFirst) {
clientOut.print("Transfer-Encoding: chunked\r\n");
}
if (contentLength < 0) {
clientOut.print("\r\n");
clientOut.print(Integer.toHexString(len));
clientOut.print("\r\n");
}
clientOut.write(buffer, 0, len);
isFirst = false;
}
if (isFirst) {
clientOut.print("Content-Length: 0\r\n\r\n");
}
else if (contentLength < 0) {
clientOut.print("\r\n0\r\n");
}
TempBuffer.free(tempBuffer);
clientOut.flush();
return parseResults(clientIn, req, res);
} catch (Exception e) {
return new ProxyResult(ProxyStatus.FAIL, false, e.toString());
}
}
private void printUtf8(WriteStream out, String uri)
throws IOException
{
int len = uri.length();
for (int i = 0; i < len; i++) {
char ch = uri.charAt(i);
if (ch <= 0x20) {
printEscape(out, ch);
}
else if (ch < 0x80) {
switch (ch) {
// server/2s0e vs server/2s0c
case '%':
if (len <= i + 2 || ! isHex(uri.charAt(i + 1)) || ! isHex(uri.charAt(i + 2))) {
printEscape(out, ch);
}
else {
out.print(ch);;
}
break;
case '#':
printEscape(out, ch);
break;
default:
out.print(ch);
break;
}
}
else if (ch < 0x800) {
printEscape(out, 0xc0 + ((ch >>> 6) & 0x1f));
printEscape(out, 0x80 + (ch & 0x3f));
}
else {
printEscape(out, 0xe0 + ((ch >>> 12) & 0xf));
printEscape(out, 0x80 + ((ch >>> 6) & 0x3f));
printEscape(out, 0x80 + (ch & 0x3f));
}
}
}
private boolean isHex(int ch)
{
return ('0' <= ch && ch <= '9'
|| 'a' <= ch && ch <= 'f'
|| 'A' <= ch && ch <= 'F');
}
private void printEscape(WriteStream out, int ch)
throws IOException
{
out.print('%');
printHex(out, ch >> 4);
printHex(out, ch);
}
private void printHex(WriteStream out, int d)
throws IOException
{
d = (d & 0xf);
if (d < 10) {
out.print((char) ('0' + d));
}
else {
out.print((char) ('A' + d - 10));
}
}
protected String constructURI(HttpServletRequest req)
{
String uri;
if (req.isRequestedSessionIdFromURL()) {
uri = (req.getRequestURI() + ";jsessionid=" +
req.getRequestedSessionId());
} else {
uri = req.getRequestURI();
}
String queryString = null;
if (req instanceof CauchoRequest) {
queryString = ((CauchoRequest)req).getPageQueryString();
} else {
queryString
= (String) req.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING);
if (queryString == null)
queryString = req.getQueryString();
}
if (queryString != null)
uri += '?' + queryString;
return uri;
}
protected String getSessionId(HttpServletRequest req)
{
return req.getRequestedSessionId();
}
private ProxyResult parseResults(ReadStream is,
HttpServletRequest req,
HttpServletResponse res)
throws IOException
{
String line = parseStatus(is);
if (line.length() == 0)
return new ProxyResult(ProxyStatus.FAIL, false, "read failure");
boolean isKeepalive = true;
if (! line.startsWith("HTTP/1.1"))
isKeepalive = false;
int statusCode = parseStatusCode(line);
String location = null;
boolean isChunked = false;
int contentLength = -1;
while (true) {
line = is.readLine();
if (line == null)
break;
int p = line.indexOf(':');
if (p < 0)
break;
String name = line.substring(0, p);
String value = line.substring(p + 1).trim();
if (name.equalsIgnoreCase("transfer-encoding")) {
isChunked = true;
} else if (name.equalsIgnoreCase("content-length")) {
contentLength = Integer.parseInt(value);
} else if (name.equalsIgnoreCase("location")) {
location = value;
} else if (name.equalsIgnoreCase("connection")) {
if ("close".equalsIgnoreCase(value))
isKeepalive = false;
} else {
// XXX: split header
res.addHeader(name, value);
}
}
/* server/1965
if (location == null) {
}
else if (location.startsWith(hostURL)) {
location = location.substring(hostURL.length());
String prefix;
if (req.isSecure()) {
if (req.getServerPort() != 443)
prefix = ("https://" + req.getServerName() +
":" + req.getServerPort());
else
prefix = ("https://" + req.getServerName());
}
else {
if (req.getServerPort() != 80)
prefix = ("http://" + req.getServerName() +
":" + req.getServerPort());
else
prefix = ("http://" + req.getServerName());
}
if (! location.startsWith("/"))
location = prefix + "/" + location;
else
location = prefix + location;
}
*/
if (location != null)
res.setHeader("Location", location);
ProxyStatus resultStatus = ProxyStatus.OK;
if (statusCode == 302 && location != null)
res.sendRedirect(location);
else if (statusCode != 200)
res.setStatus(statusCode);
if (statusCode == 503) {
resultStatus = ProxyStatus.BUSY;
isKeepalive = false;
}
OutputStream os = res.getOutputStream();
if (isChunked)
writeChunkedData(os, is);
else if (contentLength > 0) {
res.setContentLength(contentLength);
writeContentLength(os, is, contentLength);
}
return new ProxyResult(resultStatus, isKeepalive);
}
private String parseStatus(ReadStream is)
throws IOException
{
int ch;
for (ch = is.read(); Character.isWhitespace(ch); ch = is.read()) {
}
StringBuilder sb = new StringBuilder();
for (; ch >= 0 && ch != '\n'; ch = is.read()) {
if (ch != '\r')
sb.append((char) ch);
}
return sb.toString();
}
private int parseStatusCode(String line)
{
int len = line.length();
int i = 0;
int ch;
for (; i < len && (ch = line.charAt(i)) != ' '; i++) {
}
for (; i < len && (ch = line.charAt(i)) == ' '; i++) {
}
int statusCode = 0;
for (; i < len && '0' <= (ch = line.charAt(i)) && ch <= '9'; i++) {
statusCode = 10 * statusCode + ch - '0';
}
if (statusCode == 0)
return 400;
else
return statusCode;
}
private void writeChunkedData(OutputStream os, ReadStream is)
throws IOException
{
int ch;
while (true) {
for (ch = is.read(); Character.isWhitespace(ch); ch = is.read()) {
}
int len = 0;
for (; ch >= 0; ch = is.read()) {
if ('0' <= ch && ch <= '9')
len = 16 * len + ch - '0';
else if ('a' <= ch && ch <= 'f')
len = 16 * len + ch - 'a' + 10;
else if ('A' <= ch && ch <= 'F')
len = 16 * len + ch - 'A' + 10;
else
break;
}
if (ch == '\r')
ch = is.read();
if (ch != '\n')
throw new IllegalStateException(L.l("unexpected chunking at '{0}'",
(char) ch));
if (len == 0)
break;
is.writeToStream(os, len);
}
ch = is.read();
if (ch == '\r')
ch = is.read();
// XXX: footer
}
private void writeContentLength(OutputStream os, ReadStream is, int length)
throws IOException
{
is.writeToStream(os, length);
}
public String toString()
{
return this.getClass().getSimpleName() + "[]";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy