/*
* 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.ode.axis2.httpbinding;
import org.apache.axis2.transport.http.HttpTransportProperties;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.StatusLine;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.params.HttpParams;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ode.axis2.Properties;
import org.apache.ode.utils.DOMUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HttpClientHelper {
private static final Log log = LogFactory.getLog(HttpClientHelper.class);
public static void configure(HostConfiguration hostConfig, HttpState state, URI targetURI, HttpParams params) throws URIException {
if (log.isDebugEnabled()) log.debug("Configuring http client...");
// proxy configuration
if (ProxyConf.isProxyEnabled(params, targetURI.getHost())) {
if (log.isDebugEnabled()) log.debug("ProxyConf");
ProxyConf.configure(hostConfig, state, (HttpTransportProperties.ProxyProperties) params.getParameter(Properties.PROP_HTTP_PROXY_PREFIX));
}
// security
// ...
}
/**
* Parse and convert a HTTP status line into an aml element.
*
* @param statusLine
* @return
* @throws HttpException
* @see #statusLineToElement(org.w3c.dom.Document, org.apache.commons.httpclient.StatusLine)
*/
public static Element statusLineToElement(String statusLine) throws HttpException {
return statusLineToElement(new StatusLine(statusLine));
}
public static Element statusLineToElement(StatusLine statusLine) {
return statusLineToElement(DOMUtils.newDocument(), statusLine);
}
/**
* Convert a <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1">HTTP status line</a> into an xml element like this:
* <p/>
* < Status-line>
* < HTTP-Version>HTTP/1.1< /HTTP-Version>
* < Status-Code>200< /Status-Code>
* < Reason-Phrase>Success - The action was successfully received, understood, and accepted< /Reason-Phrase>
* < /Status-line></br>
*
* @param statusLine - the {@link org.apache.commons.httpclient.StatusLine} instance to be converted
* @param doc - the document to use to create new nodes
* @return an Element
*/
public static Element statusLineToElement(Document doc, StatusLine statusLine) {
Element statusLineEl = doc.createElementNS(null, "Status-Line");
Element versionEl = doc.createElementNS(null, "HTTP-Version");
Element codeEl = doc.createElementNS(null, "Status-Code");
Element reasonEl = doc.createElementNS(null, "Reason-Phrase");
// wiring
doc.appendChild(statusLineEl);
statusLineEl.appendChild(versionEl);
statusLineEl.appendChild(codeEl);
statusLineEl.appendChild(reasonEl);
// values
versionEl.setTextContent(statusLine.getHttpVersion());
codeEl.setTextContent(String.valueOf(statusLine.getStatusCode()));
reasonEl.setTextContent(statusLine.getReasonPhrase());
return statusLineEl;
}
/**
* Build a "details" element that looks like this:
*
* @param method
* @return
* @throws IOException
*/
public static Element prepareDetailsElement(HttpMethod method) throws IOException {
return prepareDetailsElement(method, true);
}
/**
* @param method
* @param bodyIsXml if true the body will be parsed as xml else the body will be inserted as string
* @return
* @throws IOException
*/
public static Element prepareDetailsElement(HttpMethod method, boolean bodyIsXml) throws IOException {
Document doc = DOMUtils.newDocument();
Element detailsEl = doc.createElementNS(null, "details");
Element statusLineEl = statusLineToElement(doc, method.getStatusLine());
detailsEl.appendChild(statusLineEl);
// set the body if any
final InputStream bodyAsStream = method.getResponseBodyAsStream();
if (bodyAsStream != null) {
Element bodyEl = doc.createElementNS(null, "responseBody");
detailsEl.appendChild(bodyEl);
// first, try to parse the body as xml
// if it fails, put it as string in the body element
boolean exceptionDuringParsing = false;
if (bodyIsXml) {
try {
Element parsedBodyEl = DOMUtils.parse(bodyAsStream).getDocumentElement();
bodyEl.appendChild(parsedBodyEl);
} catch (Exception e) {
String errmsg = "Unable to parse the response body as xml. Body will be inserted as string.";
if (log.isDebugEnabled()) log.debug(errmsg, e);
exceptionDuringParsing = true;
}
}
if (!bodyIsXml || exceptionDuringParsing) {
bodyEl.setTextContent(method.getResponseBodyAsString());
}
}
return detailsEl;
}
private static final Pattern NON_LWS_PATTERN = Pattern.compile("\r\n([^\\s])");
/**
* This method ensures that a header value containing CRLF does not mess up the HTTP request.
* Actually CRLF is the end-of-line marker for headers.
* <p/>
* To do so, all CRLF followed by a non-whitespace character are replaced by CRLF HT.
* <p/>
* This is possible because the
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2">Section 2.2</a> of HTTP standard (RFC2626) states that:
* <p/>
* <quote>
* HTTP/1.1 header field values can be folded onto multiple lines if the
* continuation line begins with a space or horizontal tab. All linear
* white space, including folding, has the same semantics as SP. A
* recipient MAY replace any linear white space with a single SP before
* interpreting the field value or forwarding the message downstream.
* <p/>
* LWS = [CRLF] 1*( SP | HT )
* <p/>
* </quote>
* <p/>
* FYI, HttpClient 3.x.x does not check this.
*
* @param header
* @return the string properly ready to be used as an HTTP header field-content
*/
public static String replaceCRLFwithLWS(String header) {
Matcher m = NON_LWS_PATTERN.matcher(header);
StringBuffer sb = new StringBuffer(header.length());
while (m.find()) {
m.appendReplacement(sb, "\r\n\t");
sb.append(m.group(1));
}
m.appendTail(sb);
return sb.toString();
}
}