package org.objectweb.celtix.bus.transports.http;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.HttpsURLConnection;
import javax.wsdl.Port;
import javax.wsdl.WSDLException;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.handler.MessageContext;
import static javax.xml.ws.handler.MessageContext.HTTP_RESPONSE_CODE;
import org.mortbay.http.HttpRequest;
import org.mortbay.http.HttpResponse;
import org.mortbay.http.handler.AbstractHttpHandler;
import org.objectweb.celtix.Bus;
import org.objectweb.celtix.bindings.BindingContextUtils;
import org.objectweb.celtix.bindings.ClientBinding;
import org.objectweb.celtix.bindings.ResponseCallback;
import org.objectweb.celtix.bus.busimpl.ComponentCreatedEvent;
import org.objectweb.celtix.bus.busimpl.ComponentRemovedEvent;
import org.objectweb.celtix.bus.configuration.security.AuthorizationPolicy;
import org.objectweb.celtix.bus.configuration.security.SSLClientPolicy;
import org.objectweb.celtix.bus.configuration.wsdl.WsdlHttpConfigurationProvider;
import org.objectweb.celtix.bus.configuration.wsdl.WsdlPortProvider;
import org.objectweb.celtix.bus.management.counters.TransportClientCounters;
import org.objectweb.celtix.bus.transports.https.JettySslClientConfigurer;
import org.objectweb.celtix.common.logging.LogUtils;
import org.objectweb.celtix.common.util.Base64Utility;
import org.objectweb.celtix.configuration.Configuration;
import org.objectweb.celtix.configuration.ConfigurationBuilder;
import org.objectweb.celtix.configuration.ConfigurationBuilderFactory;
import org.objectweb.celtix.context.GenericMessageContext;
import org.objectweb.celtix.context.InputStreamMessageContext;
import org.objectweb.celtix.context.MessageContextWrapper;
import org.objectweb.celtix.context.ObjectMessageContext;
import org.objectweb.celtix.context.OutputStreamMessageContext;
import org.objectweb.celtix.transports.ClientTransport;
import org.objectweb.celtix.transports.http.configuration.HTTPClientPolicy;
import org.objectweb.celtix.ws.addressing.EndpointReferenceType;
import org.objectweb.celtix.wsdl.EndpointReferenceUtils;
public class HTTPClientTransport implements ClientTransport {
private static final Logger LOG = LogUtils.getL7dLogger(HTTPClientTransport.class);
private static final String PORT_CONFIGURATION_URI =
"http://celtix.objectweb.org/bus/jaxws/port-config";
private static final String HTTP_CLIENT_CONFIGURATION_URI =
"http://celtix.objectweb.org/bus/transports/http/http-client-config";
private static final String HTTP_CLIENT_CONFIGURATION_ID = "http-client";
final HTTPClientPolicy policy;
final SSLClientPolicy sslClientPolicy;
final AuthorizationPolicy authPolicy;
final AuthorizationPolicy proxyAuthPolicy;
final Configuration configuration;
final Configuration portConfiguration;
final EndpointReferenceType targetEndpoint;
final Bus bus;
final Port port;
final HTTPTransportFactory factory;
URL url;
TransportClientCounters counters;
private JettyHTTPServerEngine decoupledEngine;
private EndpointReferenceType decoupledEndpoint;
private String decoupledAddress;
private URL decoupledURL;
private ClientBinding clientBinding;
private ResponseCallback responseCallback;
public HTTPClientTransport(Bus b,
EndpointReferenceType ref,
ClientBinding binding,
HTTPTransportFactory f)
throws WSDLException, IOException {
bus = b;
portConfiguration = getPortConfiguration(bus, ref);
String address = portConfiguration.getString("address");
EndpointReferenceUtils.setAddress(ref, address);
targetEndpoint = ref;
clientBinding = binding;
factory = f;
url = new URL(address);
counters = new TransportClientCounters("HTTPClientTransport");
port = EndpointReferenceUtils.getPort(bus.getWSDLManager(), ref);
configuration = createConfiguration(portConfiguration);
policy = getClientPolicy(configuration);
authPolicy = getAuthPolicy("authorization", configuration);
proxyAuthPolicy = getAuthPolicy("proxyAuthorization", configuration);
sslClientPolicy = getSSLClientPolicy(configuration);
bus.sendEvent(new ComponentCreatedEvent(this));
}
private HTTPClientPolicy getClientPolicy(Configuration conf) {
HTTPClientPolicy pol = conf.getObject(HTTPClientPolicy.class, "httpClient");
if (pol == null) {
pol = new HTTPClientPolicy();
}
return pol;
}
private AuthorizationPolicy getAuthPolicy(String type, Configuration conf) {
AuthorizationPolicy pol = conf.getObject(AuthorizationPolicy.class, type);
if (pol == null) {
pol = new AuthorizationPolicy();
}
return pol;
}
private SSLClientPolicy getSSLClientPolicy(Configuration conf) {
SSLClientPolicy pol = conf.getObject(SSLClientPolicy.class, "sslClient");
if (pol == null) {
pol = new SSLClientPolicy();
}
return pol;
}
public EndpointReferenceType getTargetEndpoint() {
return targetEndpoint;
}
public synchronized EndpointReferenceType getDecoupledEndpoint() throws IOException {
if (decoupledEndpoint == null && policy.getDecoupledEndpoint() != null) {
decoupledEndpoint = setUpDecoupledEndpoint();
}
return decoupledEndpoint;
}
public Port getPort() {
return port;
}
public OutputStreamMessageContext createOutputStreamContext(MessageContext context) throws IOException {
return new HTTPClientOutputStreamContext(url, policy, authPolicy,
proxyAuthPolicy, sslClientPolicy,
context,
portConfiguration);
}
public void finalPrepareOutputStreamContext(OutputStreamMessageContext context) throws IOException {
HTTPClientOutputStreamContext ctx = (HTTPClientOutputStreamContext)context;
ctx.flushHeaders();
}
public void invokeOneway(OutputStreamMessageContext context) throws IOException {
try {
HTTPClientOutputStreamContext ctx = (HTTPClientOutputStreamContext)context;
context.getOutputStream().close();
ctx.getCorrespondingInputStreamContext().getInputStream().close();
counters.getInvokeOneWay().increase();
} catch (Exception ex) {
counters.getInvokeError().increase();
throw new IOException(ex.getMessage());
}
}
public InputStreamMessageContext invoke(OutputStreamMessageContext context) throws IOException {
try {
context.getOutputStream().close();
HTTPClientOutputStreamContext requestContext = (HTTPClientOutputStreamContext)context;
counters.getInvoke().increase();
return getResponseContext(requestContext);
} catch (Exception ex) {
counters.getInvokeError().increase();
throw new IOException(ex.getMessage());
}
}
public Future<InputStreamMessageContext> invokeAsync(OutputStreamMessageContext context,
Executor executor)
throws IOException {
try {
context.getOutputStream().close();
HTTPClientOutputStreamContext ctx = (HTTPClientOutputStreamContext)context;
FutureTask<InputStreamMessageContext> f = new FutureTask<InputStreamMessageContext>(
getInputStreamMessageContextCallable(ctx));
// client (service) must always have an executor associated with it
executor.execute(f);
counters.getInvokeAsync().increase();
return f;
} catch (Exception ex) {
counters.getInvokeError().increase();
throw new IOException(ex.getMessage());
}
}
public ResponseCallback getResponseCallback() {
return responseCallback;
}
public void shutdown() {
if (url != null) {
try {
URLConnection connect = url.openConnection();
if (connect instanceof HttpURLConnection) {
((HttpURLConnection)connect).disconnect();
}
} catch (IOException ex) {
//ignore
}
url = null;
}
if (decoupledURL != null && decoupledEngine != null) {
try {
DecoupledHandler decoupledHandler =
(DecoupledHandler)decoupledEngine.getServant(decoupledAddress);
if (decoupledHandler != null) {
decoupledHandler.release();
}
} catch (IOException ioe) {
// ignore
}
}
bus.sendEvent(new ComponentRemovedEvent(this));
}
protected InputStreamMessageContext getResponseContext(
HTTPClientOutputStreamContext requestContext)
throws IOException {
InputStreamMessageContext responseContext = null;
if (hasDecoupledEndpoint()) {
int responseCode = getResponseCode(requestContext.connection);
if (responseCode == HttpURLConnection.HTTP_ACCEPTED) {
// server transport was rebased on decoupled response endpoint,
// dispatch this partial response immediately as it may include
// piggybacked content
responseContext =
requestContext.getCorrespondingInputStreamContext();
BindingContextUtils.storeDecoupledResponse(responseContext, true);
} else {
// request failed *before* server transport was rebased on
// decoupled response endpoint
responseContext = requestContext.getCorrespondingInputStreamContext();
}
} else {
responseContext = requestContext.getCorrespondingInputStreamContext();
}
return responseContext;
}
private EndpointReferenceType setUpDecoupledEndpoint() {
EndpointReferenceType reference =
EndpointReferenceUtils.getEndpointReference(policy.getDecoupledEndpoint());
if (reference != null) {
decoupledAddress = reference.getAddress().getValue();
LOG.info("creating decoupled endpoint: " + decoupledAddress);
try {
decoupledURL = new URL(decoupledAddress);
decoupledEngine =
JettyHTTPServerEngine.getForPort(bus,
decoupledURL.getProtocol(),
decoupledURL.getPort());
DecoupledHandler decoupledHandler =
(DecoupledHandler)decoupledEngine.getServant(decoupledAddress);
if (decoupledHandler == null) {
responseCallback = clientBinding.createResponseCallback();
decoupledEngine.addServant(decoupledAddress,
new DecoupledHandler(responseCallback));
} else {
responseCallback = decoupledHandler.duplicate();
}
} catch (Exception e) {
// REVISIT move message to localizable Messages.properties
LOG.log(Level.WARNING, "decoupled endpoint creation failed: ", e);
}
}
return reference;
}
protected synchronized boolean hasDecoupledEndpoint() {
return decoupledEndpoint != null;
}
protected static Configuration getPortConfiguration(Bus bus, EndpointReferenceType ref) {
Configuration busConfiguration = bus.getConfiguration();
String id = EndpointReferenceUtils.getServiceName(ref).toString()
+ "/" + EndpointReferenceUtils.getPortName(ref);
Configuration portConfiguration = busConfiguration
.getChild(PORT_CONFIGURATION_URI,
id);
if (portConfiguration == null) {
ConfigurationBuilder cb = ConfigurationBuilderFactory.getBuilder(null);
portConfiguration = cb.getConfiguration(PORT_CONFIGURATION_URI, id,
bus.getConfiguration());
if (null == portConfiguration) {
portConfiguration = cb.buildConfiguration(PORT_CONFIGURATION_URI, id,
bus.getConfiguration());
}
// add the additional provider
Port port = null;
try {
port = EndpointReferenceUtils.getPort(bus.getWSDLManager(), ref);
} catch (WSDLException ex) {
throw new WebServiceException("Could not get port from wsdl", ex);
}
portConfiguration.getProviders().add(new WsdlPortProvider(port));
}
return portConfiguration;
}
private Configuration createConfiguration(Configuration portCfg) {
ConfigurationBuilder cb = ConfigurationBuilderFactory.getBuilder(null);
Configuration cfg = cb.getConfiguration(HTTP_CLIENT_CONFIGURATION_URI,
HTTP_CLIENT_CONFIGURATION_ID,
portCfg);
if (null == cfg) {
cfg = cb.buildConfiguration(HTTP_CLIENT_CONFIGURATION_URI,
HTTP_CLIENT_CONFIGURATION_ID,
portCfg);
}
// register the additional provider
if (null != port) {
cfg.getProviders().add(new WsdlHttpConfigurationProvider(port, false));
}
return cfg;
}
protected static int getResponseCode(URLConnection connection) throws IOException {
int responseCode = HttpURLConnection.HTTP_OK;
if (connection instanceof HttpURLConnection) {
HttpURLConnection hc = (HttpURLConnection)connection;
responseCode = hc.getResponseCode();
} else {
if (connection.getHeaderField(HTTP_RESPONSE_CODE) != null) {
responseCode =
Integer.parseInt(connection.getHeaderField(HTTP_RESPONSE_CODE));
}
}
return responseCode;
}
protected InputStreamMessageContextCallable getInputStreamMessageContextCallable(
HTTPClientOutputStreamContext context) {
return new InputStreamMessageContextCallable(context);
}
protected static class HTTPClientOutputStreamContext
extends MessageContextWrapper
implements OutputStreamMessageContext {
URLConnection connection;
WrappedOutputStream origOut;
OutputStream out;
HTTPClientInputStreamContext inputStreamContext;
HTTPClientPolicy policy;
AuthorizationPolicy authPolicy;
AuthorizationPolicy proxyAuthPolicy;
SSLClientPolicy sslClientPolicy;
Configuration portConfiguration;
@SuppressWarnings("unchecked")
public HTTPClientOutputStreamContext(URL url,
HTTPClientPolicy p,
AuthorizationPolicy ap,
AuthorizationPolicy pap,
SSLClientPolicy sslcp,
MessageContext ctx,
Configuration configParam)
throws IOException {
super(ctx);
Map<String, List<String>> headers = (Map<String, List<String>>)super.get(HTTP_REQUEST_HEADERS);
if (null == headers) {
headers = new HashMap<String, List<String>>();
super.put(HTTP_REQUEST_HEADERS, headers);
}
policy = p;
authPolicy = ap;
proxyAuthPolicy = pap;
sslClientPolicy = sslcp;
portConfiguration = configParam;
String value = (String)ctx.get(BindingProvider.ENDPOINT_ADDRESS_PROPERTY);
if (value != null) {
url = new URL(value);
}
if (policy.isSetProxyServer()) {
Proxy proxy = new Proxy(Proxy.Type.valueOf(policy.getProxyServerType().toString()),
new InetSocketAddress(policy.getProxyServer(),
policy.getProxyServerPort()));
connection = url.openConnection(proxy);
} else {
connection = url.openConnection();
}
connection.setDoOutput(true);
if (connection instanceof HttpURLConnection) {
HttpURLConnection hc = (HttpURLConnection)connection;
hc.setRequestMethod("POST");
}
connection.setConnectTimeout((int)policy.getConnectionTimeout());
connection.setReadTimeout((int)policy.getReceiveTimeout());
connection.setUseCaches(false);
if (connection instanceof HttpURLConnection) {
HttpURLConnection hc = (HttpURLConnection)connection;
if (policy.isAutoRedirect()) {
//cannot use chunking if autoredirect as the request will need to be
//completely cached locally and resent to the redirect target
hc.setInstanceFollowRedirects(true);
} else {
hc.setInstanceFollowRedirects(false);
if (policy.isAllowChunking()) {
hc.setChunkedStreamingMode(2048);
}
}
}
setPolicies(headers);
if (connection instanceof HttpsURLConnection) {
setSSLPolicies();
}
origOut = new WrappedOutputStream();
out = origOut;
}
private void setSSLPolicies() {
JettySslClientConfigurer sslClientConfigurer =
new JettySslClientConfigurer(sslClientPolicy, connection, portConfiguration);
sslClientConfigurer.configure();
}
private void setPolicies(Map<String, List<String>> headers) {
String userName = (String)get(BindingProvider.USERNAME_PROPERTY);
if (userName == null && authPolicy.isSetUserName()) {
userName = authPolicy.getUserName();
}
if (userName != null) {
String passwd = (String)get(BindingProvider.PASSWORD_PROPERTY);
if (passwd == null && authPolicy.isSetPassword()) {
passwd = authPolicy.getPassword();
}
userName += ":";
if (passwd != null) {
userName += passwd;
}
userName = Base64Utility.encode(userName.getBytes());
headers.put("Authorization",
Arrays.asList(new String[] {"Basic " + userName}));
} else if (authPolicy.isSetAuthorizationType() && authPolicy.isSetAuthorization()) {
String type = authPolicy.getAuthorizationType();
type += " ";
type += authPolicy.getAuthorization();
headers.put("Authorization",
Arrays.asList(new String[] {type}));
}
if (proxyAuthPolicy.isSetUserName()) {
userName = proxyAuthPolicy.getUserName();
if (userName != null) {
String passwd = "";
if (proxyAuthPolicy.isSetPassword()) {
passwd = proxyAuthPolicy.getPassword();
}
userName += ":";
if (passwd != null) {
userName += passwd;
}
userName = Base64Utility.encode(userName.getBytes());
headers.put("Proxy-Authorization",
Arrays.asList(new String[] {"Basic " + userName}));
} else if (proxyAuthPolicy.isSetAuthorizationType() && proxyAuthPolicy.isSetAuthorization()) {
String type = proxyAuthPolicy.getAuthorizationType();
type += " ";
type += proxyAuthPolicy.getAuthorization();
headers.put("Proxy-Authorization",
Arrays.asList(new String[] {type}));
}
}
if (policy.isSetCacheControl()) {
headers.put("Cache-Control",
Arrays.asList(new String[] {policy.getCacheControl().value()}));
}
if (policy.isSetHost()) {
headers.put("Host",
Arrays.asList(new String[] {policy.getHost()}));
}
if (policy.isSetConnection()) {
headers.put("Connection",
Arrays.asList(new String[] {policy.getConnection().value()}));
}
if (policy.isSetAccept()) {
headers.put("Accept",
Arrays.asList(new String[] {policy.getAccept()}));
}
if (policy.isSetAcceptEncoding()) {
headers.put("Accept-Encoding",
Arrays.asList(new String[] {policy.getAcceptEncoding()}));
}
if (policy.isSetAcceptLanguage()) {
headers.put("Accept-Language",
Arrays.asList(new String[] {policy.getAcceptLanguage()}));
}
if (policy.isSetContentType()) {
headers.put("Content-Type",
Arrays.asList(new String[] {policy.getContentType()}));
}
if (policy.isSetCookie()) {
headers.put("Cookie",
Arrays.asList(new String[] {policy.getCookie()}));
}
if (policy.isSetBrowserType()) {
headers.put("BrowserType",
Arrays.asList(new String[] {policy.getBrowserType()}));
}
if (policy.isSetReferer()) {
headers.put("Referer",
Arrays.asList(new String[] {policy.getReferer()}));
}
}
@SuppressWarnings("unchecked")
void flushHeaders() throws IOException {
Map<String, List<String>> headers = (Map<String, List<String>>)super.get(HTTP_REQUEST_HEADERS);
if (null != headers) {
for (String header : headers.keySet()) {
List<String> headerList = headers.get(header);
for (String string : headerList) {
connection.addRequestProperty(header, string);
}
}
}
origOut.resetOut(new BufferedOutputStream(connection.getOutputStream(), 1024));
}
public void setFault(boolean isFault) {
//nothing to do
}
public boolean isFault() {
return false;
}
public void setOneWay(boolean isOneWay) {
put(ONEWAY_MESSAGE_TF, isOneWay);
}
public boolean isOneWay() {
return ((Boolean)get(ONEWAY_MESSAGE_TF)).booleanValue();
}
public OutputStream getOutputStream() {
return out;
}
public void setOutputStream(OutputStream o) {
out = o;
}
public InputStreamMessageContext getCorrespondingInputStreamContext() throws IOException {
if (inputStreamContext == null) {
inputStreamContext = new HTTPClientInputStreamContext(connection);
}
return inputStreamContext;
}
private class WrappedOutputStream extends FilterOutputStream {
WrappedOutputStream() {
super(new ByteArrayOutputStream());
}
void resetOut(OutputStream newOut) throws IOException {
ByteArrayOutputStream bout = (ByteArrayOutputStream)out;
if (bout.size() > 0) {
bout.writeTo(newOut);
}
out = newOut;
}
public void close() throws IOException {
out.flush();
if (inputStreamContext != null) {
inputStreamContext.initialise();
}
}
}
}
static class HTTPClientInputStreamContext
extends GenericMessageContext
implements InputStreamMessageContext {
private static final long serialVersionUID = 1L;
final URLConnection connection;
InputStream origInputStream;
InputStream inStream;
private boolean initialised;
public HTTPClientInputStreamContext(URLConnection con) throws IOException {
connection = con;
initialise();
}
/**
* Calling getHeaderFields on the connection implicitly gets
* the InputStream from the connection. Getting the
* InputStream implicitly closes the output stream which
* renders it unwritable. The InputStream context is created
* before the binding is finished with it. For this reason it
* is necessary to initialise the InputStreamContext lazily.
* When the OutputStream associated with this connection is
* closed, it will invoke on this initialise method.
*/
void initialise() throws IOException {
if (!initialised) {
put(ObjectMessageContext.MESSAGE_INPUT, false);
put(HTTP_RESPONSE_HEADERS, connection.getHeaderFields());
put(HTTP_RESPONSE_CODE, getResponseCode(connection));
if (connection instanceof HttpURLConnection) {
HttpURLConnection hc = (HttpURLConnection)connection;
origInputStream = hc.getErrorStream();
if (null == origInputStream) {
origInputStream = connection.getInputStream();
}
} else {
origInputStream = connection.getInputStream();
}
inStream = origInputStream;
initialised = true;
}
}
public InputStream getInputStream() {
try {
initialise();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return inStream;
}
public void setInputStream(InputStream ins) {
inStream = ins;
}
public void setFault(boolean isFault) {
//nothing to do
}
public boolean isFault() {
assert get(HTTP_RESPONSE_CODE) != null;
return ((Integer)get(HTTP_RESPONSE_CODE)).intValue() == 500;
}
}
static class HTTPDecoupledClientInputStreamContext
extends GenericMessageContext
implements InputStreamMessageContext {
InputStream inStream;
public HTTPDecoupledClientInputStreamContext(HttpRequest decoupledResponse)
throws IOException {
put(ObjectMessageContext.MESSAGE_INPUT, false);
put(HTTP_RESPONSE_HEADERS, decoupledResponse.getParameters());
put(HTTP_RESPONSE_CODE, HttpURLConnection.HTTP_ACCEPTED);
inStream = drain(decoupledResponse.getInputStream());
}
public InputStream getInputStream() {
return inStream;
}
public void setInputStream(InputStream ins) {
inStream = ins;
}
public void setFault(boolean isFault) {
//nothing to do
}
public boolean isFault() {
return false;
}
private static InputStream drain(InputStream r) throws IOException {
byte[] bytes = new byte[4096];
ByteArrayOutputStream w = new ByteArrayOutputStream();
try {
int offset = 0;
int length = r.read(bytes, offset, bytes.length - offset);
while (length != -1) {
offset += length;
if (offset == bytes.length) {
w.write(bytes, 0, bytes.length);
offset = 0;
}
length = r.read(bytes, offset, bytes.length - offset);
}
if (offset != 0) {
w.write(bytes, 0, offset);
}
} finally {
bytes = null;
}
return new ByteArrayInputStream(w.toByteArray());
}
}
private class InputStreamMessageContextCallable implements Callable<InputStreamMessageContext> {
private final HTTPClientOutputStreamContext httpClientOutputStreamContext;
InputStreamMessageContextCallable(HTTPClientOutputStreamContext ctx) {
httpClientOutputStreamContext = ctx;
}
public InputStreamMessageContext call() throws Exception {
return getResponseContext(httpClientOutputStreamContext);
}
}
private class DecoupledHandler extends AbstractHttpHandler {
private ResponseCallback responseCallback;
private int refCount;
DecoupledHandler(ResponseCallback callback) {
responseCallback = callback;
}
synchronized ResponseCallback duplicate() {
refCount++;
return responseCallback;
}
synchronized void release() {
if (--refCount == 0) {
try {
decoupledEngine.removeServant(decoupledAddress);
JettyHTTPServerEngine.destroyForPort(decoupledURL.getPort());
} catch (IOException ex) {
//ignore
}
}
}
public void handle(String pathInContext,
String pathParams,
HttpRequest req,
HttpResponse resp) throws IOException {
HTTPDecoupledClientInputStreamContext ctx =
new HTTPDecoupledClientInputStreamContext(req);
responseCallback.dispatch(ctx);
resp.commit();
req.setHandled(true);
}
}
}