/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and others contributors as indicated
* by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2005-2006, JBoss Inc.
*/
package org.jboss.soa.esb.actions.routing.http;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.log4j.Logger;
import org.jboss.internal.soa.esb.util.Encoding;
import org.jboss.internal.soa.esb.util.StreamUtils;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.actions.ActionLifecycleException;
import org.jboss.soa.esb.actions.ActionProcessingException;
import org.jboss.soa.esb.actions.routing.AbstractRouter;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.http.HttpClientFactory;
import org.jboss.soa.esb.http.HttpHeader;
import org.jboss.soa.esb.http.HttpRequest;
import org.jboss.soa.esb.http.HttpResponse;
import org.jboss.soa.esb.http.configurators.Connection;
import org.jboss.soa.esb.listeners.ListenerTagNames;
import org.jboss.soa.esb.listeners.message.MessageDeliverException;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.message.Properties;
import org.jboss.soa.esb.message.ResponseHeader;
import org.jboss.soa.esb.message.ResponseStatus;
import org.jboss.soa.esb.message.format.MessageFactory;
import org.jboss.soa.esb.message.format.MessageType;
import org.jboss.soa.esb.util.FileUtil;
import org.jboss.soa.esb.util.Util;
/**
* Http router.
* <p/>
* Uses HttpClient via the <a href="http://wiki.jboss.org/wiki/Wiki.jsp?page=HttpClientFactory">HttpClientFactory</a>.
*
* @author <a href="mailto:tom.fennelly@jboss.com">tom.fennelly@jboss.com</a>
*/
public class HttpRouter extends AbstractRouter {
private static Logger logger = Logger.getLogger(HttpRouter.class);
private ConfigTree config;
private java.util.Properties httpClientProps = new java.util.Properties();
private HttpClient httpclient;
private URL endpointUrl;
private String method;
private HttpMethodFactory methodFactory;
private ResponseType responseType;
private String contentType;
private ConfigTree[] requestHeaders;
private boolean httpResponseStatusEnabled;
private final String[] mappedHeaderList ;
private static Set<String> coreProperties;
static {
coreProperties = new HashSet<String>();
coreProperties.add("endpointUrl");
coreProperties.add("method");
coreProperties.add("responseType");
coreProperties.add("Content-Type");
}
public HttpRouter(ConfigTree config) throws ConfigurationException {
super(config);
this.config = config;
try {
endpointUrl = new URL(config.getRequiredAttribute("endpointUrl"));
} catch (MalformedURLException e) {
throw new ConfigurationException("Invalid endpoint URL '" + config.getRequiredAttribute("endpointUrl") + "'.", e);
}
//Overriding to set true by default
unwrap = config.getAttribute("unwrap", "true").equals("true");
// Extract the HttpClient creation properties from the ConfigTree. These are passed
// to the HttpClientFacatory...
extractHttpClientProps(config);
httpclient = HttpClientFactory.createHttpClient(httpClientProps);
method = config.getRequiredAttribute("method");
responseType = ResponseType.valueOf(config.getAttribute("responseType", ResponseType.STRING.toString()));
methodFactory = HttpMethodFactory.Factory.getInstance(method.toUpperCase(), config, endpointUrl);
contentType = config.getAttribute("Content-Type");
mappedHeaderList = extractMappedHeaderListConfig();
requestHeaders = config.getChildren("header");
httpResponseStatusEnabled = ResponseStatus.isHttpEnabled(config);
}
public Message process(Message message) throws ActionProcessingException {
HttpMethodBase method = null;
try {
if (unwrap) {
method = methodFactory.getInstance(message);
} else {
try {
Serializable payload = Util.serialize(message);
if (message.getType().equals(MessageType.JAVA_SERIALIZED)) {
String payloadStr = Encoding.encodeObject(payload);
method = methodFactory.getMethod(payloadStr, "application/x-java-serialized-object", Charset.defaultCharset().toString());
} else {
method = methodFactory.getMethod(payload.toString(), "text/xml", "UTF-8");
}
} catch (ParserConfigurationException e) {
throw new ActionProcessingException(e);
}
}
try {
setRequestHeaders(method, message);
int responseCode = httpclient.executeMethod(method);
if(responseCode != HttpStatus.SC_OK) {
logger.warn("Received status code '" + responseCode + "' on HTTP " + method + " request to '" + endpointUrl + "'.");
}
attachResponseDetails(message, method, responseCode);
InputStream resultStream = method.getResponseBodyAsStream();
try {
byte[] bytes = readStream(resultStream);
if(responseType == ResponseType.STRING) {
getPayloadProxy().setPayload(message, new String(bytes, method.getResponseCharSet()));
} else {
getPayloadProxy().setPayload(message, bytes);
}
} catch (MessageDeliverException e) {
throw new ActionProcessingException("problem setting message payload: " + e.getMessage(), e);
} finally {
closeStream(resultStream);
}
} finally {
method.releaseConnection();
}
} catch (IOException e) {
throw new ActionProcessingException("problem processing HTTP I/O: " + e.getMessage(), e);
}
return message;
}
static byte[] readStream(final InputStream stream) throws IOException {
if (stream != null) {
return StreamUtils.readStream(stream);
}
else
return new byte[0];
}
static void closeStream(final Closeable c) throws IOException {
if (c != null) {
c.close();
}
}
private String[] extractMappedHeaderListConfig() throws ConfigurationException {
final String mappedHeaders = config.getAttribute("MappedHeaderList");
if (mappedHeaders != null) {
final String[] headerList = mappedHeaders.split(",");
final int numHeaders = headerList.length ;
final ArrayList<String> headers = new ArrayList<String>(numHeaders) ;
for(int count = 0 ; count < numHeaders ; count++) {
final String header = headerList[count].trim() ;
if (header.length() > 0) {
headers.add(header) ;
}
}
return (String[])headers.toArray(new String[headers.size()]) ;
} else {
return new String[0] ;
}
}
private void attachResponseDetails(Message message, HttpMethodBase method, int responseCode) {
HttpResponse response = new HttpResponse(responseCode);
Properties properties = message.getProperties();
response.setEncoding(method.getResponseCharSet());
response.setLength(method.getResponseContentLength());
Header[] responseHeaders = method.getResponseHeaders();
for(Header responseHeader : responseHeaders) {
String name = responseHeader.getName();
String value = responseHeader.getValue();
response.addHeader(new org.jboss.soa.esb.http.HttpHeader(name, value));
// JBESB-2511
new ResponseHeader(name, value).setPropertyNameThis(properties);
}
// JBESB-2761
if (httpResponseStatusEnabled) {
ResponseStatus.setHttpProperties(properties, responseCode, method.getStatusLine().getReasonPhrase());
}
response.setResponse(message);
}
private void setRequestHeaders(HttpMethodBase method, Message message) {
//Try best to keep all the well-known HTTP headers that were sent from the client.
setMappedHttpHeaders(method, message);
//The setting of HTTP headers from config still takes precedence. So do not set
//a HTTP header in config if you want to keep the value sent from the client.
for (int i = 0; i < requestHeaders.length; i++) {
ConfigTree header = requestHeaders[i];
String name = header.getAttribute("name");
String value = header.getAttribute("value");
if(name != null && value != null) {
method.setRequestHeader(name, value);
} else {
logger.error("null Http request header name/value: '" + name + "':'" + value + "'.");
}
}
if (contentType != null) {
method.setRequestHeader("Content-Type", contentType);
} else if (method.getRequestHeader("Content-Type") == null) {
method.setRequestHeader("Content-Type", "text/xml;charset=UTF-8") ;
}
}
private void setMappedHttpHeaders(HttpMethodBase method, Message message) {
HttpRequest request = HttpRequest.getRequest(message);
Properties properties = message.getProperties();
for (String headerName : mappedHeaderList) {
String headerValue = null;
if (request != null) {
headerValue = getHttpHeaderValue(request, headerName);
}
if (headerValue == null) {
headerValue = getHttpHeaderValue(properties, headerName);
}
if (headerValue != null) {
method.setRequestHeader(headerName, headerValue);
}
}
}
private String getHttpHeaderValue(HttpRequest request, String headerName) {
String headerValue = null;
for (HttpHeader header : request.getHeaders()) {
String name = header.getName();
// HTTP header field names are case-insensitive
if (name.equalsIgnoreCase(headerName)) {
headerValue = header.getValue();
break;
}
}
return headerValue;
}
private String getHttpHeaderValue(Properties properties, String headerName) {
String headerValue = null;
for (String name : properties.getNames()) {
// HTTP header field names are case-insensitive
if (name.equalsIgnoreCase(headerName)) {
Object property = properties.getProperty(name);
if (property != null) {
headerValue = property.toString();
if (headerValue.length() == 0) {
headerValue = null;
}
}
break;
}
}
return headerValue;
}
public String[] getMappedHeaderList() {
return mappedHeaderList;
}
public void route(Object object) throws ActionProcessingException {
// Not used!
}
public void destroy() throws ActionLifecycleException {
if (httpclient != null) {
HttpClientFactory.shutdown(httpclient);
}
super.destroy();
}
private void extractHttpClientProps(ConfigTree config) {
ConfigTree[] httpClientConfigTrees = config.getChildren(HttpClientFactory.HTTP_CLIENT_PROPERTY);
httpClientProps.setProperty(HttpClientFactory.TARGET_HOST_URL, endpointUrl.toString());
final ConfigTree parent = config.getParent();
if (parent != null) {
final String maxThreads = parent.getAttribute(ListenerTagNames.MAX_THREADS_TAG);
if (maxThreads != null) {
httpClientProps.setProperty(Connection.MAX_TOTAL_CONNECTIONS, maxThreads);
httpClientProps.setProperty(Connection.MAX_CONNECTIONS_PER_HOST, maxThreads);
}
}
// The HttpClient properties are attached under the factory class/impl property as <http-client-property name="x" value="y" /> nodes
for(ConfigTree httpClientProp : httpClientConfigTrees) {
String propName = httpClientProp.getAttribute("name");
String propValue = httpClientProp.getAttribute("value");
if(propName != null && propValue != null) {
httpClientProps.setProperty(propName, propValue);
}
}
}
// public for testing purposes, see SOAPProxyUnitTest.test_maxThreads*()
public java.util.Properties getHttpClientProps() {
return httpClientProps;
}
public static void main(String[] args) throws ConfigurationException, ActionProcessingException {
ConfigTree configTree = new ConfigTree("config");
for(String arg : args) {
int equalsIdx = arg.indexOf('=');
if(equalsIdx == -1) {
throw new IllegalArgumentException("Arguments must be in 'name=value' format.");
}
String name = arg.substring(0, equalsIdx);
String value = arg.substring(equalsIdx + 1);
if(!coreProperties.contains(name) && !name.equals("payload")) {
ConfigTree httpClientProperty = new ConfigTree(HttpClientFactory.HTTP_CLIENT_PROPERTY, configTree);
httpClientProperty.setAttribute("name", name);
httpClientProperty.setAttribute("value", value);
} else {
configTree.setAttribute(name, value);
}
}
HttpRouter router = new HttpRouter(configTree);
Message message = MessageFactory.getInstance().getMessage();
String payload = configTree.getAttribute("payload");
if(payload != null) {
try {
File file = new File(payload);
if(file.exists()) {
payload = FileUtil.readTextFile(file);
}
} catch (Exception e) {
// Ignore...
}
System.out.println("Request payload:\n" + payload);
System.out.println("--------------------------\n");
message.getBody().add(payload);
}
message = router.process(message);
HttpResponse responseInfo = HttpResponse.getResponse(message);
System.out.println();
System.out.println("Response Status Code: " + responseInfo.getResponseCode());
System.out.println("Response payload:\n" + message.getBody().get());
System.out.println("--------------------------\n");
}
}