blob: 04c74439acddce392ee4d3a71d25e94d1224eb2b [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 org.apache.axis2.transport.http;
import org.apache.axiom.mime.ContentType;
import org.apache.axiom.mime.Header;
import org.apache.axiom.om.OMAttribute;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMOutputFormat;
import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.NamedValue;
import org.apache.axis2.context.OperationContext;
import org.apache.axis2.description.TransportOutDescription;
import org.apache.axis2.i18n.Messages;
import org.apache.axis2.transport.MessageFormatter;
import org.apache.axis2.util.MessageProcessorSelector;
import org.apache.axis2.util.Utils;
import org.apache.axis2.wsdl.WSDLConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpStatus;
import org.apache.http.protocol.HTTP;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;
import javax.xml.namespace.QName;
//TODO - It better if we can define these method in a interface move these into AbstractHTTPSender and get rid of this class.
public abstract class HTTPSender {
private static final Log log = LogFactory.getLog(HTTPSender.class);
private boolean chunked = false;
private String httpVersion = HTTPConstants.HEADER_PROTOCOL_11;
protected TransportOutDescription proxyOutSetting = null;
protected OMOutputFormat format = new OMOutputFormat();
public void setChunked(boolean chunked) {
this.chunked = chunked;
}
public void setHttpVersion(String version) throws AxisFault {
if (version != null) {
if (HTTPConstants.HEADER_PROTOCOL_11.equals(version)) {
this.httpVersion = HTTPConstants.HEADER_PROTOCOL_11;
} else if (HTTPConstants.HEADER_PROTOCOL_10.equals(version)) {
this.httpVersion = HTTPConstants.HEADER_PROTOCOL_10;
// chunked is not possible with HTTP/1.0
this.chunked = false;
} else {
throw new AxisFault(
"Parameter " + HTTPConstants.PROTOCOL_VERSION
+ " Can have values only HTTP/1.0 or HTTP/1.1");
}
}
}
public void setFormat(OMOutputFormat format) {
this.format = format;
}
/**
* Start a new HTTP request.
*
* @param msgContext
* The MessageContext of the request message
* @param methodName
* The HTTP method name
* @param url
* The target URL
* @param requestEntity
* The content of the request or {@code null} if the HTTP request shouldn't have any
* content (e.g. for {@code GET} requests)
* @throws AxisFault
* Thrown in case an exception occurs
*/
protected abstract Request createRequest(MessageContext msgContext, String methodName, URL url,
AxisRequestEntity requestEntity) throws AxisFault;
public void send(MessageContext msgContext, URL url, String soapActionString)
throws IOException {
// execute the HtttpMethodBase - a connection manager can be given for
// handle multiple
String httpMethod =
(String) msgContext.getProperty(Constants.Configuration.HTTP_METHOD);
if (httpMethod == null) {
httpMethod = Constants.Configuration.HTTP_METHOD_POST;
}
MessageFormatter messageFormatter = MessageProcessorSelector
.getMessageFormatter(msgContext);
url = messageFormatter.getTargetAddress(msgContext, format, url);
String contentType = messageFormatter.getContentType(msgContext, format, soapActionString);
HTTPAuthenticator authenticator;
Object obj = msgContext.getProperty(HTTPConstants.AUTHENTICATE);
if (obj == null) {
authenticator = null;
} else {
if (obj instanceof HTTPAuthenticator) {
authenticator = (HTTPAuthenticator) obj;
} else {
throw new AxisFault("HttpTransportProperties.Authenticator class cast exception");
}
}
AxisRequestEntity requestEntity;
boolean gzip;
if (Constants.Configuration.HTTP_METHOD_GET.equalsIgnoreCase(httpMethod)
|| Constants.Configuration.HTTP_METHOD_DELETE.equalsIgnoreCase(httpMethod)) {
requestEntity = null;
gzip = false;
} else if (Constants.Configuration.HTTP_METHOD_POST.equalsIgnoreCase(httpMethod)
|| Constants.Configuration.HTTP_METHOD_PUT.equalsIgnoreCase(httpMethod)) {
gzip = msgContext.isPropertyTrue(HTTPConstants.MC_GZIP_REQUEST);
requestEntity = new AxisRequestEntity(messageFormatter, msgContext, format,
contentType, chunked, gzip, authenticator != null && authenticator.isAllowedRetry());
} else {
throw new AxisFault("Unsupported HTTP method " + httpMethod);
}
Request request = createRequest(msgContext, httpMethod, url, requestEntity);
if (msgContext.getOptions() != null && msgContext.getOptions().isManageSession()) {
// setting the cookie in the out path
Object cookieString = msgContext.getProperty(HTTPConstants.COOKIE_STRING);
if (cookieString != null) {
StringBuffer buffer = new StringBuffer();
buffer.append(cookieString);
request.setHeader(HTTPConstants.HEADER_COOKIE, buffer.toString());
}
}
if (httpVersion.equals(HTTPConstants.HEADER_PROTOCOL_10)) {
request.enableHTTP10();
}
request.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, contentType);
String soapAction = messageFormatter.formatSOAPAction(msgContext, format, soapActionString);
if (soapAction != null && !msgContext.isDoingREST()) {
request.setHeader(HTTPConstants.HEADER_SOAP_ACTION, soapAction);
}
if (gzip) {
request.setHeader(HTTPConstants.HEADER_CONTENT_ENCODING,
HTTPConstants.COMPRESSION_GZIP);
}
// set the custom headers, if available
addCustomHeaders(msgContext, request);
if (authenticator != null) {
request.enableAuthentication(authenticator);
}
setTimeouts(msgContext, request);
try {
request.execute();
boolean cleanup = true;
try {
int statusCode = request.getStatusCode();
log.trace("Handling response - " + statusCode);
boolean processResponse;
boolean fault;
if (statusCode == HttpStatus.SC_ACCEPTED) {
processResponse = false;
fault = false;
} else if (statusCode >= 200 && statusCode < 300) {
processResponse = true;
fault = false;
} else if (statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
|| statusCode == HttpStatus.SC_BAD_REQUEST || statusCode == HttpStatus.SC_NOT_FOUND) {
processResponse = true;
fault = true;
} else {
throw new AxisFault(Messages.getMessage("transportError", String.valueOf(statusCode),
request.getStatusText()));
}
obtainHTTPHeaderInformation(request, msgContext);
if (processResponse) {
OperationContext opContext = msgContext.getOperationContext();
MessageContext inMessageContext = opContext == null ? null
: opContext.getMessageContext(WSDLConstants.MESSAGE_LABEL_IN_VALUE);
if (opContext != null) {
InputStream in = request.getResponseContent();
if (in != null) {
String contentEncoding = request.getResponseHeader(HTTPConstants.HEADER_CONTENT_ENCODING);
if (contentEncoding != null) {
if (contentEncoding.equalsIgnoreCase(HTTPConstants.COMPRESSION_GZIP)) {
in = new GZIPInputStream(in);
// If the content-encoding is identity we can basically ignore
// it.
} else if (!"identity".equalsIgnoreCase(contentEncoding)) {
throw new AxisFault("HTTP :" + "unsupported content-encoding of '"
+ contentEncoding + "' found");
}
}
opContext.setProperty(MessageContext.TRANSPORT_IN, in);
// This implements the behavior of the HTTPClient 3.x based transport in
// Axis2 1.7: if AUTO_RELEASE_CONNECTION is enabled, we set the input stream
// in the message context, but we nevertheless release the connection.
// It is unclear in which situation this would actually be the right thing
// to do.
if (msgContext.isPropertyTrue(HTTPConstants.AUTO_RELEASE_CONNECTION)) {
log.debug("AUTO_RELEASE_CONNECTION enabled; are you sure that you really want that?");
} else {
cleanup = false;
}
}
}
if (fault) {
if (inMessageContext != null) {
inMessageContext.setProcessingFault(true);
}
if (Utils.isClientThreadNonBlockingPropertySet(msgContext)) {
throw new AxisFault(Messages.
getMessage("transportError",
String.valueOf(statusCode),
request.getStatusText()));
}
}
}
} finally {
if (cleanup) {
request.releaseConnection();
}
}
} catch (IOException e) {
log.info("Unable to send to url[" + url + "]", e);
throw AxisFault.makeFault(e);
}
}
private void addCustomHeaders(MessageContext msgContext, Request request) {
boolean isCustomUserAgentSet = false;
// set the custom headers, if available
Object httpHeadersObj = msgContext.getProperty(HTTPConstants.HTTP_HEADERS);
if (httpHeadersObj != null) {
if (httpHeadersObj instanceof List) {
List httpHeaders = (List) httpHeadersObj;
for (int i = 0; i < httpHeaders.size(); i++) {
NamedValue nv = (NamedValue) httpHeaders.get(i);
if (nv != null) {
if (HTTPConstants.HEADER_USER_AGENT.equals(nv.getName())) {
isCustomUserAgentSet = true;
}
request.addHeader(nv.getName(), nv.getValue());
}
}
}
if (httpHeadersObj instanceof Map) {
Map httpHeaders = (Map) httpHeadersObj;
for (Iterator iterator = httpHeaders.entrySet().iterator(); iterator.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
if (HTTPConstants.HEADER_USER_AGENT.equals(key)) {
isCustomUserAgentSet = true;
}
request.addHeader(key, value);
}
}
}
// we have to consider the TRANSPORT_HEADERS map as well
Map transportHeaders = (Map) msgContext.getProperty(MessageContext.TRANSPORT_HEADERS);
if (transportHeaders != null) {
removeUnwantedHeaders(msgContext);
Set headerEntries = transportHeaders.entrySet();
for (Object headerEntry : headerEntries) {
if (headerEntry instanceof Map.Entry) {
Header[] headers = request.getRequestHeaders();
boolean headerAdded = false;
for (Header header : headers) {
if (header.getName() != null
&& header.getName().equals(((Map.Entry) headerEntry).getKey())) {
headerAdded = true;
break;
}
}
if (!headerAdded) {
request.addHeader(((Map.Entry) headerEntry).getKey().toString(),
((Map.Entry) headerEntry).getValue().toString());
}
}
}
}
if (!isCustomUserAgentSet) {
String userAgentString = getUserAgent(msgContext);
request.setHeader(HTTPConstants.HEADER_USER_AGENT, userAgentString);
}
}
/**
* Remove unwanted headers from the transport headers map of outgoing
* request. These are headers which should be dictated by the transport and
* not the user. We remove these as these may get copied from the request
* messages
*
* @param msgContext
* the Axis2 Message context from which these headers should be
* removed
*/
private void removeUnwantedHeaders(MessageContext msgContext) {
Map headers = (Map) msgContext.getProperty(MessageContext.TRANSPORT_HEADERS);
if (headers == null || headers.isEmpty()) {
return;
}
Iterator iter = headers.keySet().iterator();
while (iter.hasNext()) {
String headerName = (String) iter.next();
if (HTTP.CONN_DIRECTIVE.equalsIgnoreCase(headerName)
|| HTTP.TRANSFER_ENCODING.equalsIgnoreCase(headerName)
|| HTTP.DATE_HEADER.equalsIgnoreCase(headerName)
|| HTTP.CONTENT_TYPE.equalsIgnoreCase(headerName)
|| HTTP.CONTENT_LEN.equalsIgnoreCase(headerName)) {
iter.remove();
}
}
}
private String getUserAgent(MessageContext messageContext) {
String userAgentString = "Axis2";
boolean locked = false;
if (messageContext.getParameter(HTTPConstants.USER_AGENT) != null) {
OMElement userAgentElement = messageContext.getParameter(HTTPConstants.USER_AGENT)
.getParameterElement();
userAgentString = userAgentElement.getText().trim();
OMAttribute lockedAttribute = userAgentElement.getAttribute(new QName("locked"));
if (lockedAttribute != null) {
if (lockedAttribute.getAttributeValue().equalsIgnoreCase("true")) {
locked = true;
}
}
}
// Runtime overing part
if (!locked) {
if (messageContext.getProperty(HTTPConstants.USER_AGENT) != null) {
userAgentString = (String) messageContext.getProperty(HTTPConstants.USER_AGENT);
}
}
return userAgentString;
}
/**
* This is used to get the dynamically set time out values from the message
* context. If the values are not available or invalid then the default
* values or the values set by the configuration will be used
*
* @param msgContext
* the active MessageContext
* @param request
* request
*/
private void setTimeouts(MessageContext msgContext, Request request) {
// If the SO_TIMEOUT of CONNECTION_TIMEOUT is set by dynamically the
// override the static config
Integer tempSoTimeoutProperty = (Integer) msgContext.getProperty(HTTPConstants.SO_TIMEOUT);
Integer tempConnTimeoutProperty = (Integer) msgContext
.getProperty(HTTPConstants.CONNECTION_TIMEOUT);
long timeout = msgContext.getOptions().getTimeOutInMilliSeconds();
if (tempConnTimeoutProperty != null) {
// timeout for initial connection
request.setConnectionTimeout(tempConnTimeoutProperty);
}
if (tempSoTimeoutProperty != null) {
// SO_TIMEOUT -- timeout for blocking reads
request.setSocketTimeout(tempSoTimeoutProperty);
} else {
// set timeout in client
if (timeout > 0) {
request.setSocketTimeout((int) timeout);
}
}
}
private void obtainHTTPHeaderInformation(Request request, MessageContext msgContext) throws AxisFault {
// Set RESPONSE properties onto the REQUEST message context. They will
// need to be copied off the request context onto
// the response context elsewhere, for example in the
// OutInOperationClient.
msgContext.setProperty(
MessageContext.TRANSPORT_HEADERS,
new CommonsTransportHeaders(request.getResponseHeaders()));
msgContext.setProperty(
HTTPConstants.MC_HTTP_STATUS_CODE,
new Integer(request.getStatusCode()));
String contentTypeString = request.getResponseHeader(HTTPConstants.HEADER_CONTENT_TYPE);
if (contentTypeString != null) {
ContentType contentType;
try {
contentType = new ContentType(contentTypeString);
} catch (ParseException ex) {
throw AxisFault.makeFault(ex);
}
String charSetEnc = contentType.getParameter(HTTPConstants.CHAR_SET_ENCODING);
MessageContext inMessageContext = msgContext.getOperationContext().getMessageContext(
WSDLConstants.MESSAGE_LABEL_IN_VALUE);
if (inMessageContext != null) {
inMessageContext.setProperty(Constants.Configuration.CONTENT_TYPE, contentTypeString);
inMessageContext.setProperty(Constants.Configuration.CHARACTER_SET_ENCODING, charSetEnc);
} else {
// Transport details will be stored in a HashMap so that anybody
// interested can
// retrieve them
Map<String,String> transportInfoMap = new HashMap<String,String>();
transportInfoMap.put(Constants.Configuration.CONTENT_TYPE, contentTypeString);
transportInfoMap.put(Constants.Configuration.CHARACTER_SET_ENCODING, charSetEnc);
// the HashMap is stored in the outgoing message.
msgContext.setProperty(Constants.Configuration.TRANSPORT_INFO_MAP, transportInfoMap);
}
}
Map<String,String> cookies = request.getCookies();
if (cookies != null) {
String customCookieId = (String) msgContext.getProperty(Constants.CUSTOM_COOKIE_ID);
String cookieString = null;
if (customCookieId != null) {
cookieString = buildCookieString(cookies, customCookieId);
}
if (cookieString == null) {
cookieString = buildCookieString(cookies, Constants.SESSION_COOKIE);
}
if (cookieString == null) {
cookieString = buildCookieString(cookies, Constants.SESSION_COOKIE_JSESSIONID);
}
if (cookieString != null) {
msgContext.getServiceContext().setProperty(HTTPConstants.COOKIE_STRING, cookieString);
}
}
}
private String buildCookieString(Map<String,String> cookies, String name) {
String value = cookies.get(name);
return value == null ? null : name + "=" + value;
}
}