blob: 0d43257dacd2508e768ef811c24209b6e42dc333 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package flex.messaging.services.http.proxy;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.StatusLine;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.methods.OptionsMethod;
import flex.messaging.io.MessageIOConstants;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import flex.messaging.FlexContext;
import flex.messaging.services.HTTPProxyService;
import flex.messaging.log.Log;
/**
* Send the response to the client, including custom copying of headers and cookies.
*/
public class ResponseFilter extends ProxyFilter {
// NOTE: any changes to this class should also be made to the corresponding version in the .NET.
// The corresponding class is in src/dotNet/libs/FlexASPlib/Aspx/Proxy
public static int RESPONSE_CHUNK = 4096;
private static final int STATUS_ERROR = 10708;
private static final int NULL_RESPONSE_STREAM = 10709;
private static final int CANNOT_STREAM_NOT_HTTP = 10710;
private static final int ERROR_WRITING_RESPONSE = 10711;
public void invoke(ProxyContext context) {
if (next != null) {
next.invoke(context);
}
checkStatusCode(context);
copyCookiesFromEndpoint(context);
copyHeadersFromEndpoint(context);
recordResponseHeaders(context);
setupResponse(context);
}
protected void checkStatusCode(ProxyContext context) {
int statusCode = context.getStatusCode();
// FIXME: Why do this only for HTTP Proxy? Why not WebServices?
if (statusCode >= 400 && statusCode != 401 & statusCode != 403 && !context.isSoapRequest()) {
StatusLine statusLine = context.getHttpMethod().getStatusLine();
String reason = null;
if (statusLine != null)
reason = statusLine.toString();
if (reason == null || "".equals(reason))
reason = String.valueOf(statusCode);
ProxyException pe = new ProxyException();
pe.setMessage(STATUS_ERROR, new Object[]{reason});
pe.setCode(ProxyException.CODE_SERVER_PROXY_REQUEST_FAILED);
pe.setDetails(STATUS_ERROR, "1", new Object[]{reason});
pe.setStatusCode(statusCode);
throw pe;
}
}
protected void copyCookiesFromEndpoint(ProxyContext context) {
HttpServletResponse clientResponse = FlexContext.getHttpResponse();
if (clientResponse != null) {
Cookie[] cookies = context.getHttpClient().getState().getCookies();
// We need to filter out the request cookies, we don't need to send back to the client
Set requestCookies = context.getRequestCookies();
for (int i = 0; i < cookies.length; i++) {
if (requestCookies != null && requestCookies.contains(cookies[i]) && cookies[i].getExpiryDate() == null) {
// It means it is a request cookie and nothing changed, we need to skip it
continue;
}
// Process the cookie;
String domain = cookies[i].getDomain();
String path = cookies[i].getPath();
String name = cookies[i].getName();
String value = cookies[i].getValue();
String clientName = ResponseUtil.getCookieName(context, path, name, domain);
if (Log.isInfo()) {
String str = "-- Cookie in response: domain = '" + domain + "', path = '" + path +
"', client name = '" + clientName + "', endpoint name = '" + name + "', value = '" + value;
Log.getLogger(HTTPProxyService.LOG_CATEGORY).debug(str);
}
javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie(clientName, value);
Date expiry = cookies[i].getExpiryDate();
if (expiry != null) {
int maxAge = (int) ((expiry.getTime() - System.currentTimeMillis()) / 1000);
cookie.setMaxAge(maxAge);
}
cookie.setSecure(cookies[i].getSecure());
cookie.setPath("/");
clientResponse.addCookie(cookie);
}
}
}
protected void copyHeadersFromEndpoint(ProxyContext context) {
HttpServletResponse clientResponse = FlexContext.getHttpResponse();
if (clientResponse != null) {
Header[] headers = context.getHttpMethod().getResponseHeaders();
for (int i = 0; i < headers.length; i++) {
Header header = headers[i];
String name = header.getName();
String value = header.getValue();
if (ResponseUtil.ignoreHeader(name, context)) {
continue;
}
if ((name != null) && (value != null)) {
clientResponse.addHeader(name, value);
if (Log.isInfo()) {
Log.getLogger(HTTPProxyService.LOG_CATEGORY).debug("-- Header in response: " + name + " : " + value);
}
}
}
// set Pragma needed for ATG on HTTPS
clientResponse.setHeader("Pragma", "public");
}
}
protected void recordResponseHeaders(ProxyContext context) {
String method = context.getMethod();
if (context.getRecordHeaders() || ProxyConstants.METHOD_HEAD.equals(method)) {
Header[] headers = context.getHttpMethod().getResponseHeaders();
HashMap responseHeaders = new HashMap();
for (int i = 0; i < headers.length; i++) {
Header header = headers[i];
String headerName = header.getName();
String headerValue = header.getValue();
Object existingHeaderValue = responseHeaders.get(headerName);
// Value(s) already exist for the header.
if (existingHeaderValue != null) {
ArrayList headerValues;
// Only a single value exists.
if (existingHeaderValue instanceof String) {
headerValues = new ArrayList();
headerValues.add(existingHeaderValue);
headerValues.add(headerValue);
responseHeaders.put(headerName, headerValues);
}
// Multiple values exist.
else if (existingHeaderValue instanceof ArrayList) {
headerValues = (ArrayList) existingHeaderValue;
headerValues.add(headerValue);
}
} else {
responseHeaders.put(headerName, headerValue);
}
}
context.setResponseHeaders(responseHeaders);
}
}
protected void setupResponse(ProxyContext context) {
String method = context.getMethod();
HttpMethodBase httpMethod = context.getHttpMethod();
if (MessageIOConstants.METHOD_POST.equals(method)) {
writeResponse(context);
} else if (ProxyConstants.METHOD_GET.equals(method)) {
writeResponse(context);
} else if (ProxyConstants.METHOD_OPTIONS.equals(method)) {
OptionsMethod optionsMethod = (OptionsMethod) httpMethod;
Enumeration options = optionsMethod.getAllowedMethods();
if (options != null) {
List ops = new ArrayList();
while (options.hasMoreElements()) {
Object option = options.nextElement();
ops.add(option);
}
Object[] o = ops.toArray();
context.setResponse(o);
}
} else if (ProxyConstants.METHOD_TRACE.equals(method)) {
writeResponse(context);
} else if (ProxyConstants.METHOD_DELETE.equals(method)) {
writeResponse(context);
} else if (ProxyConstants.METHOD_HEAD.equals(method)) {
context.setResponse(context.getResponseHeaders());
} else if (ProxyConstants.METHOD_PUT.equals(method)) {
writeResponse(context);
}
}
protected void writeResponse(ProxyContext context) {
try {
InputStream in = context.getHttpMethod().getResponseBodyAsStream();
if (in == null) {
throw new ProxyException(NULL_RESPONSE_STREAM);
}
int length = (int) context.getHttpMethod().getResponseContentLength();
// Stream response directly to client
if (context.streamResponseToClient()) {
HttpServletResponse clientResponse = FlexContext.getHttpResponse();
if (clientResponse != null) {
OutputStream out = clientResponse.getOutputStream();
if (length != -1) {
clientResponse.setContentLength(length);
}
writeStreamedResponse(in, out, context);
} else {
throw new ProxyException(CANNOT_STREAM_NOT_HTTP);
}
} else {
writeResponseAsString(in, length, context);
}
} catch (IOException ioe) {
ProxyException pe = new ProxyException();
pe.setMessage(ERROR_WRITING_RESPONSE, new Object[]{ioe.getMessage()});
throw pe;
}
}
protected void writeStreamedResponse(InputStream inStream, OutputStream out, ProxyContext context) throws IOException {
byte[] tmp = new byte[RESPONSE_CHUNK];
int i = 0;
while ((i = inStream.read(tmp)) >= 0) {
out.write(tmp, 0, i);
}
}
protected void writeResponseAsString(InputStream inStream, int length, ProxyContext context)
throws IOException {
char[] tmp = new char[RESPONSE_CHUNK];
//int i = 0;
StringBuffer sb = new StringBuffer(length < 0 ? 16 : length);
BufferedInputStream bufferedIn = new BufferedInputStream(inStream);
String charset = context.getHttpMethod().getResponseCharSet();
bufferedIn.mark(4);
// Check for BOM as InputStreamReader does not strip BOM in all cases.
boolean hasBOM = false;
int read = bufferedIn.read();
if (read > 0) {
// UTF-8 BOM is EF BB BF
if (0xEF == (read & 0xFF)) {
read = bufferedIn.read();
if (0xBB == (read & 0xFF)) {
read = bufferedIn.read();
if (0xBF == (read & 0xFF)) {
hasBOM = true;
charset = "UTF-8";
}
}
}
// UTF-16 Little Endian BOM is FF FE
// UTF-32 Little Endian BOM is FF FE 00 00
else if (0xFF == (read & 0xFF)) {
read = bufferedIn.read();
if (0xFE == (read & 0xFF)) {
hasBOM = true;
charset = "UTF16-LE";
// Check two more bytes incase we have UTF-32
bufferedIn.mark(2);
read = bufferedIn.read();
if (0x00 == (read & 0xFF)) {
read = bufferedIn.read();
if (0x00 == (read & 0xFF)) {
charset = "UTF32-LE";
} else {
bufferedIn.reset();
}
} else {
bufferedIn.reset();
}
}
}
// UTF-16 Big Endian BOM is FE FF
else if (0xFE == (read & 0xFF)) {
read = bufferedIn.read();
if (0xFF == (read & 0xFF)) {
hasBOM = true;
charset = "UTF16-BE";
}
}
// UTF-32 Big Endian BOM is 00 00 FE FF
else if (0x00 == (read & 0xFF)) {
read = bufferedIn.read();
if (0x00 == (read & 0xFF)) {
read = bufferedIn.read();
if (0xFE == (read & 0xFF)) {
read = bufferedIn.read();
if (0xFF == (read & 0xFF)) {
hasBOM = true;
charset = "UTF32-BE";
}
}
}
}
// If we didn't find a BOM, all bytes should contribute to the content
if (!hasBOM)
bufferedIn.reset();
}
BufferedReader reader = new BufferedReader(new InputStreamReader(bufferedIn, charset));
int charactersRead = -1;
while ((charactersRead = reader.read(tmp, 0, tmp.length)) >= 0) {
sb.append(new String(tmp, 0, charactersRead));
}
context.setResponse(sb.toString());
}
}