/**
* Copyright (c) 2009, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* Licensed 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.synapse.transport.passthru;
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.transport.MessageFormatter;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.util.MessageProcessorSelector;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpVersion;
import org.apache.http.ProtocolVersion;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.nio.ContentEncoder;
import org.apache.http.nio.NHttpClientConnection;
import org.apache.http.params.DefaultedHttpParams;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.synapse.transport.nhttp.NhttpConstants;
import org.apache.synapse.transport.nhttp.util.MessageFormatterDecoratorFactory;
import org.apache.synapse.transport.passthru.config.TargetConfiguration;
import org.apache.synapse.transport.passthru.util.PassThroughTransportUtils;
import org.apache.synapse.transport.passthru.util.RelayUtils;
import javax.xml.stream.XMLStreamException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
/**
* This is a class for representing a request to be sent to a target.
*/
public class TargetRequest {
/** Configuration of the sender */
private TargetConfiguration targetConfiguration;
private HttpRoute route;
private Pipe pipe = null;
/** Headers map */
private Map<String, TreeSet<String>> headers = new HashMap<String, TreeSet<String>>();
/** URL */
private URL url;
/** HTTP Method */
private String method;
/** HTTP request created for sending the message */
private HttpRequest request = null;
/** Weather chunk encoding should be used */
private boolean chunk = true;
/** HTTP version that should be used */
private ProtocolVersion version = null;
/** Weather full url is used for the request */
private boolean fullUrl = false;
/** Port to be used for the request */
private int port = 80;
/** Weather this request has a body */
private boolean hasEntityBody = true;
/** Keep alive request */
private boolean keepAlive = true;
/**
* Create a target request.
*
* @param targetConfiguration the configuration of the sender
* @param url the url to be used
* @param method the HTTP method
* @param hasEntityBody weather request has an entity body
*/
public TargetRequest(TargetConfiguration targetConfiguration, HttpRoute route, URL url,
String method, boolean hasEntityBody) {
this(targetConfiguration, route, method, url, hasEntityBody);
}
public TargetRequest(TargetConfiguration targetConfiguration, HttpRoute route, String method,
URL url, boolean hasEntityBody) {
this.route = route;
this.method = method;
this.url = url;
this.targetConfiguration = targetConfiguration;
this.hasEntityBody = hasEntityBody;
}
public void connect(Pipe pipe) {
this.pipe = pipe;
}
public void start(NHttpClientConnection conn) throws IOException, HttpException {
if (pipe != null) {
TargetContext.get(conn).setWriter(pipe);
}
String path = fullUrl || (route.getProxyHost() != null && !route.isTunnelled()) ?
url.toString() : url.getPath() +
(url.getQuery() != null ? "?" + url.getQuery() : "");
long contentLength = -1;
String contentLengthHeader = null;
if(headers.get(HTTP.CONTENT_LEN) != null && headers.get(HTTP.CONTENT_LEN).size() > 0) {
contentLengthHeader = headers.get(HTTP.CONTENT_LEN).first();
}
if (contentLengthHeader != null) {
contentLength = Long.parseLong(contentLengthHeader);
headers.remove(HTTP.CONTENT_LEN);
}
MessageContext requestMsgCtx = TargetContext.get(conn).getRequestMsgCtx();
if(requestMsgCtx.getProperty(PassThroughConstants.PASSTROUGH_MESSAGE_LENGTH) != null){
contentLength = (Long)requestMsgCtx.getProperty(PassThroughConstants.PASSTROUGH_MESSAGE_LENGTH);
}
//fix for POST_TO_URI
if(requestMsgCtx.isPropertyTrue(NhttpConstants.POST_TO_URI)){
path = url.toString();
}
//fix GET request empty body
if ((("GET").equals(requestMsgCtx.getProperty(Constants.Configuration.HTTP_METHOD)))
|| (("DELETE").equals(requestMsgCtx.getProperty(Constants.Configuration.HTTP_METHOD)))) {
hasEntityBody = false;
MessageFormatter formatter = MessageProcessorSelector.getMessageFormatter(requestMsgCtx);
OMOutputFormat format = PassThroughTransportUtils.getOMOutputFormat(requestMsgCtx);
if (formatter != null && format != null) {
URL _url = formatter.getTargetAddress(requestMsgCtx, format, url);
if (_url != null && !_url.toString().isEmpty()) {
if (requestMsgCtx.getProperty(NhttpConstants.POST_TO_URI) != null
&& Boolean.TRUE.toString().equals(requestMsgCtx.getProperty(NhttpConstants.POST_TO_URI))) {
path = _url.toString();
} else {
path = _url.getPath()
+ ((_url.getQuery() != null && !_url.getQuery().isEmpty())
? ("?" + _url.getQuery())
: "");
}
}
headers.remove(HTTP.CONTENT_TYPE);
}
}
Object o = requestMsgCtx.getProperty(MessageContext.TRANSPORT_HEADERS);
if (o != null && o instanceof TreeMap) {
Map _headers = (Map) o;
String trpContentType = (String) _headers.get(HTTP.CONTENT_TYPE);
if (trpContentType != null && !trpContentType.equals("")) {
if (!trpContentType.contains(PassThroughConstants.CONTENT_TYPE_MULTIPART_RELATED)) {
addHeader(HTTP.CONTENT_TYPE, trpContentType);
}
}
}
if (hasEntityBody) {
request = new BasicHttpEntityEnclosingRequest(method, path,
version != null ? version : HttpVersion.HTTP_1_1);
BasicHttpEntity entity = new BasicHttpEntity();
boolean forceContentLength = requestMsgCtx.isPropertyTrue(
NhttpConstants.FORCE_HTTP_CONTENT_LENGTH);
boolean forceContentLengthCopy = requestMsgCtx.isPropertyTrue(
PassThroughConstants.COPY_CONTENT_LENGTH_FROM_INCOMING);
if (forceContentLength) {
entity.setChunked(false);
if (forceContentLengthCopy && contentLength > 0) {
entity.setContentLength(contentLength);
}
}else{
if (contentLength != -1) {
entity.setChunked(false);
entity.setContentLength(contentLength);
} else {
entity.setChunked(chunk);
}
}
((BasicHttpEntityEnclosingRequest) request).setEntity(entity);
} else {
request = new BasicHttpRequest(method, path,
version != null ? version : HttpVersion.HTTP_1_1);
}
Set<Map.Entry<String, TreeSet<String>>> entries = headers.entrySet();
for (Map.Entry<String, TreeSet<String>> entry : entries) {
if (entry.getKey() != null) {
Iterator<String> i = entry.getValue().iterator();
while(i.hasNext()) {
request.addHeader(entry.getKey(), i.next());
}
}
}
//setup wsa action..
if(request != null){
String soapAction = requestMsgCtx.getSoapAction();
if (soapAction == null) {
soapAction = requestMsgCtx.getWSAAction();
requestMsgCtx.getAxisOperation().getInputAction();
}
if (requestMsgCtx.isSOAP11() && soapAction != null &&
soapAction.length() > 0) {
Header existingHeader =
request.getFirstHeader(HTTPConstants.HEADER_SOAP_ACTION);
if (existingHeader != null) {
request.removeHeader(existingHeader);
}
MessageFormatter messageFormatter =
MessageFormatterDecoratorFactory.createMessageFormatterDecorator(requestMsgCtx);
request.setHeader(HTTPConstants.HEADER_SOAP_ACTION,
messageFormatter.formatSOAPAction(requestMsgCtx, null, soapAction));
//request.setHeader(HTTPConstants.USER_AGENT,"Synapse-PT-HttpComponents-NIO");
}
}
request.setParams(new DefaultedHttpParams(request.getParams(),
targetConfiguration.getHttpParams()));
//Chucking is not performed for request has "http 1.0" and "GET" http method
if (!((request.getProtocolVersion().equals(HttpVersion.HTTP_1_0)) ||
(("GET").equals(requestMsgCtx.getProperty(Constants.Configuration.HTTP_METHOD))) || (("DELETE").equals(requestMsgCtx.getProperty(Constants.Configuration.HTTP_METHOD))))) {
this.processChunking(conn, requestMsgCtx);
}
if (!keepAlive) {
request.setHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_CLOSE);
}
// Pre-process HTTP request
HttpContext httpContext = conn.getContext();
httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
if (port == -1) {
httpContext.setAttribute(ExecutionContext.HTTP_TARGET_HOST, new HttpHost(url.getHost()));
} else {
httpContext.setAttribute(ExecutionContext.HTTP_TARGET_HOST, new HttpHost(url.getHost(), port));
}
conn.getContext().setAttribute(ExecutionContext.HTTP_REQUEST, request);
httpContext.setAttribute(PassThroughConstants.PROXY_PROFILE_TARGET_HOST,
requestMsgCtx.getProperty(PassThroughConstants.PROXY_PROFILE_TARGET_HOST));
// start the request
targetConfiguration.getHttpProcessor().process(request, httpContext);
if (targetConfiguration.getProxyAuthenticator() != null
&& route.getProxyHost() != null && !route.isTunnelled()) {
targetConfiguration.getProxyAuthenticator().authenticatePreemptively(request, httpContext);
}
conn.submitRequest(request);
if (hasEntityBody) {
TargetContext.updateState(conn, ProtocolState.REQUEST_HEAD);
} else {
TargetContext.updateState(conn, ProtocolState.REQUEST_DONE);
}
}
/**
* Handles the chuking messages in Passthough context, create a temporary buffer and calculate the message
* size before writing to the external buffer, which is required the context of handling DISABLED chunking
* messages
*
* @param conn
* @param requestMsgCtx
* @throws IOException
* @throws AxisFault
*/
private void processChunking(NHttpClientConnection conn, MessageContext requestMsgCtx) throws IOException,
AxisFault {
String disableChunking = (String) requestMsgCtx.getProperty(PassThroughConstants.DISABLE_CHUNKING);
String forceHttp10 = (String) requestMsgCtx.getProperty(PassThroughConstants.FORCE_HTTP_1_0);
if ("true".equals(disableChunking) || "true".equals(forceHttp10)) {
if (requestMsgCtx.getEnvelope().getBody().getFirstElement() == null) {
BasicHttpEntity entity = (BasicHttpEntity) ((BasicHttpEntityEnclosingRequest) request).getEntity();
try {
RelayUtils.buildMessage(requestMsgCtx);
this.hasEntityBody = true;
Pipe pipe = (Pipe) requestMsgCtx.getProperty(PassThroughConstants.PASS_THROUGH_PIPE);
if (pipe != null) {
pipe.attachConsumer(conn);
this.connect(pipe);
if (Boolean.TRUE.equals(requestMsgCtx.getProperty(PassThroughConstants.MESSAGE_BUILDER_INVOKED))) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
MessageFormatter formatter = MessageProcessorSelector.getMessageFormatter(requestMsgCtx);
OMOutputFormat format = PassThroughTransportUtils.getOMOutputFormat(requestMsgCtx);
formatter.writeTo(requestMsgCtx, format, out, false);
OutputStream _out = pipe.getOutputStream();
IOUtils.write(out.toByteArray(), _out);
entity.setContentLength(new Long(out.toByteArray().length));
entity.setChunked(false);
}
}
// pipe.setSerializationComplete(true);
} catch (XMLStreamException e) {
e.printStackTrace();
}
}
}
}
/**
* Consume the data from the pipe and write it to the wire.
*
* @param conn the connection to the target
* @param encoder encoder for writing the message through
* @throws java.io.IOException if an error occurs
* @return number of bytes written
*/
public int write(NHttpClientConnection conn, ContentEncoder encoder) throws IOException {
int bytes = 0;
if (pipe != null) {
bytes = pipe.consume(encoder);
}
if (encoder.isCompleted()) {
conn.getContext().setAttribute(PassThroughConstants.REQ_DEPARTURE_TIME, System.currentTimeMillis());
conn.getContext().setAttribute(PassThroughConstants.REQ_TO_BACKEND_WRITE_END_TIME,System.currentTimeMillis());
targetConfiguration.getMetrics().
notifySentMessageSize(conn.getMetrics().getSentBytesCount());
TargetContext.updateState(conn, ProtocolState.REQUEST_DONE);
}
return bytes;
}
public boolean hasEntityBody() {
return hasEntityBody;
}
public void setHasEntityBody(boolean hasEntityBody) {
this.hasEntityBody = hasEntityBody;
}
public void addHeader(String name, String value) {
if (headers.get(name) == null) {
TreeSet<String> values = new TreeSet<String>();
values.add(value);
if (HTTP.CONTENT_TYPE.equalsIgnoreCase(name)) {
headers.put(HTTP.CONTENT_TYPE, values);
} else {
headers.put(name, values);
}
} else {
if (HTTP.CONTENT_TYPE.equalsIgnoreCase(name)) {
headers.remove(HTTP.CONTENT_TYPE);
TreeSet<String> values = new TreeSet<String>();
values.add(value);
headers.put(HTTP.CONTENT_TYPE, values);
} else {
TreeSet<String> values = headers.get(name);
values.add(value);
}
}
}
public String getMethod() {
return method;
}
public void setChunk(boolean chunk) {
this.chunk = chunk;
}
public void setPort(int port) {
this.port = port;
}
public void setFullUrl(boolean fullUrl) {
this.fullUrl = fullUrl;
}
public void setVersion(ProtocolVersion version) {
this.version = version;
}
public void setKeepAlive(boolean keepAlive) {
this.keepAlive = keepAlive;
}
public HttpRequest getRequest() {
return request;
}
public HttpRoute getRoute(){
return route;
}
}