package xdi2.client.impl.http; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import xdi2.client.XDIClient; import xdi2.client.exceptions.Xdi2ClientException; import xdi2.client.impl.XDIAbstractClient; import xdi2.client.impl.http.ssl.XDI2X509TrustManager; import xdi2.client.util.URLURIUtil; import xdi2.core.Graph; import xdi2.core.impl.memory.MemoryGraphFactory; import xdi2.core.io.MimeType; import xdi2.core.io.XDIReader; import xdi2.core.io.XDIReaderRegistry; import xdi2.core.io.XDIWriter; import xdi2.core.io.XDIWriterRegistry; import xdi2.messaging.MessageEnvelope; import xdi2.messaging.http.AcceptHeader; import xdi2.messaging.response.TransportMessagingResponse; /** * An XDI client that can send XDI messages over HTTP and receive results. * It supports the following parameters (passed to the init method): * <ul> * <li>endpointUrl - The URL of the XDI endpoint to talk to.</li> * <li>sendMimeType - The mime type to use to send the XDI messages to the endpoint. The Content-type header will be set accordingly.</li> * <li>recvMimeType - The mime type in which we want to receive the results from the endpoint. The Accept header will be set accordingly. * If the endpoint replies in some other mime type than requested, we will still try to read it.</li> * <li>useragent - The User-Agent HTTP header to use.</li> * </ul> * * @author markus */ public class XDIHttpClient extends XDIAbstractClient<TransportMessagingResponse> implements XDIClient<TransportMessagingResponse> { public static final String KEY_ENDPOINTURI = "endpointUri"; public static final String KEY_SENDMIMETYPE = "sendmimetype"; public static final String KEY_RECVMIMETYPE = "recvmimetype"; public static final String KEY_USERAGENT = "useragent"; public static final String KEY_FOLLOWREDIRECTS = "followredirects"; public static final String DEFAULT_SENDMIMETYPE = "application/xdi+json;implied=0"; public static final String DEFAULT_RECVMIMETYPE = "application/xdi+json;implied=0"; public static final String DEFAULT_USERAGENT = "XDI2 Java Library"; public static final String DEFAULT_FOLLOWREDIRECTS = "false"; private static final Logger log = LoggerFactory.getLogger(XDIHttpClient.class); private HttpURLConnection httpURLConnection; private URI xdiEndpointUri; private MimeType sendMimeType; private MimeType recvMimeType; private String userAgent; private boolean followRedirects; static { try { XDI2X509TrustManager.enableTrustAll(); } catch (Exception ex) { throw new RuntimeException(ex.getMessage(), ex); } } public XDIHttpClient(HttpURLConnection httpURLConnection, URI xdiEndpointUri, MimeType sendMimeType, MimeType recvMimeType, String userAgent, Boolean followRedirects) { super(); this.httpURLConnection = httpURLConnection; this.xdiEndpointUri = xdiEndpointUri; this.sendMimeType = (sendMimeType != null) ? sendMimeType : new MimeType(DEFAULT_SENDMIMETYPE); this.recvMimeType = (recvMimeType != null) ? recvMimeType : new MimeType(DEFAULT_RECVMIMETYPE); this.userAgent = (userAgent != null) ? userAgent : DEFAULT_USERAGENT; this.followRedirects = (followRedirects != null) ? followRedirects.booleanValue() : Boolean.parseBoolean(DEFAULT_FOLLOWREDIRECTS); } public XDIHttpClient(HttpURLConnection httpURLConnection, URI xdiEndpointUri, MimeType sendMimeType, MimeType recvMimeType, String userAgent) { this(httpURLConnection, xdiEndpointUri, sendMimeType, recvMimeType, userAgent, null); } public XDIHttpClient(HttpURLConnection httpURLConnection, URI xdiEndpointUri, MimeType sendMimeType, MimeType recvMimeType) { this(httpURLConnection, xdiEndpointUri, sendMimeType, recvMimeType, null, null); } public XDIHttpClient(HttpURLConnection httpURLConnection, URI xdiEndpointUri) { this(httpURLConnection, xdiEndpointUri, null, null, null, null); } public XDIHttpClient(HttpURLConnection httpURLConnection, String xdiEndpointUri) { this(httpURLConnection, URLURIUtil.URI(xdiEndpointUri), null, null, null, null); } public XDIHttpClient(HttpURLConnection httpURLConnection, Properties parameters) { this(httpURLConnection, null, null, null, null, null); if (parameters != null) { if (parameters.containsKey(KEY_ENDPOINTURI)) this.xdiEndpointUri = URLURIUtil.URI(parameters.getProperty(KEY_ENDPOINTURI)); if (parameters.containsKey(KEY_SENDMIMETYPE)) this.sendMimeType = new MimeType(parameters.getProperty(KEY_SENDMIMETYPE)); if (parameters.containsKey(KEY_RECVMIMETYPE)) this.recvMimeType = new MimeType(parameters.getProperty(KEY_RECVMIMETYPE)); if (parameters.containsKey(KEY_USERAGENT)) this.userAgent = parameters.getProperty(KEY_USERAGENT); if (parameters.containsKey(KEY_FOLLOWREDIRECTS)) this.followRedirects = Boolean.parseBoolean(parameters.getProperty(KEY_FOLLOWREDIRECTS)); if (log.isDebugEnabled()) log.debug("Initialized with " + parameters.toString() + "."); } } public XDIHttpClient(HttpURLConnection httpURLConnection) { this(httpURLConnection, null, null, null, null, null); } public XDIHttpClient(URI xdiEndpointUri, MimeType sendMimeType, MimeType recvMimeType, String userAgent, Boolean followRedirects) { this((HttpURLConnection) null, xdiEndpointUri, sendMimeType, recvMimeType, userAgent, followRedirects); } public XDIHttpClient(URI xdiEndpointUri, MimeType sendMimeType, MimeType recvMimeType, String userAgent) { this((HttpURLConnection) null, xdiEndpointUri, sendMimeType, recvMimeType, userAgent); } public XDIHttpClient(URI xdiEndpointUri, MimeType sendMimeType, MimeType recvMimeType) { this((HttpURLConnection) null, xdiEndpointUri, sendMimeType, recvMimeType); } public XDIHttpClient(URI xdiEndpointUri) { this((HttpURLConnection) null, xdiEndpointUri); } public XDIHttpClient(String xdiEndpointUri) { this((HttpURLConnection) null, xdiEndpointUri); } public XDIHttpClient(Properties parameters) { this((HttpURLConnection) null, parameters); } public XDIHttpClient() { this((HttpURLConnection) null); } @Override protected TransportMessagingResponse sendInternal(MessageEnvelope messageEnvelope) throws Xdi2ClientException { // find out which XDIWriter we want to use MimeType sendMimeType = this.sendMimeType; XDIWriter writer = XDIWriterRegistry.forMimeType(sendMimeType); if (writer == null) { sendMimeType = new MimeType(DEFAULT_SENDMIMETYPE); writer = XDIWriterRegistry.forMimeType(sendMimeType); } if (writer == null) throw new Xdi2ClientException("Cannot find a suitable XDI writer."); if (log.isDebugEnabled()) log.debug("Using writer " + writer.getClass().getName() + "."); // find out which XDIReader we want to use MimeType recvMimeType = this.getRecvMimeType(); XDIReader reader = XDIReaderRegistry.forMimeType(recvMimeType); if (reader == null) { recvMimeType = new MimeType(DEFAULT_RECVMIMETYPE); reader = XDIReaderRegistry.forMimeType(recvMimeType); } if (reader == null) throw new Xdi2ClientException("Cannot find a suitable XDI reader."); if (log.isDebugEnabled()) log.debug("Using reader " + reader.getClass().getName() + "."); // connect HttpURLConnection httpURLConnection; try { httpURLConnection = this.connect(); } catch (Xdi2ClientException ex) { throw ex; } catch (Exception ex) { throw new Xdi2ClientException("Cannot open HTTP(S) connection: " + ex.getMessage(), ex); } // send the message envelope if (log.isDebugEnabled()) log.debug("MessageEnvelope: " + messageEnvelope.getGraph().toString(null, null)); int responseCode; String responseMessage; try { OutputStream outputStream = httpURLConnection.getOutputStream(); writer.write(messageEnvelope.getGraph(), outputStream); outputStream.flush(); outputStream.close(); responseCode = httpURLConnection.getResponseCode(); responseMessage = httpURLConnection.getResponseMessage(); } catch (Exception ex) { throw new Xdi2ClientException("Cannot send message envelope: " + ex.getMessage(), ex); } // check response code if (responseCode >= 300) { throw new Xdi2ClientException("HTTP code " + responseCode + " received: " + responseMessage); } // check in which format we receive the result String contentType = httpURLConnection.getContentType(); int contentLength = httpURLConnection.getContentLength(); if (log.isDebugEnabled()) log.debug("Received result. Content-Type: " + contentType + ", Content-Length: " + contentLength); if (contentType != null) { reader = XDIReaderRegistry.forMimeType(new MimeType(contentType)); if (reader == null) { log.info("Don't know how to read message result with Content-Type " + contentType + ". Trying to auto-detect format."); reader = XDIReaderRegistry.getAuto(); } } else { log.info("No Content-Type received. Trying to auto-detect format."); reader = XDIReaderRegistry.getAuto(); } // read the messaging response and close connection Graph messagingResponseGraph = MemoryGraphFactory.getInstance().openGraph(); try { InputStream inputStream = httpURLConnection.getInputStream(); reader.read(messagingResponseGraph, inputStream); inputStream.close(); } catch (Exception ex) { throw new Xdi2ClientException("Cannot read message result: " + ex.getMessage(), ex); } finally { this.disconnect(); } TransportMessagingResponse messagingResponse = TransportMessagingResponse.fromGraph(messagingResponseGraph); // done return messagingResponse; } @Override public void close() { this.disconnect(); } private HttpURLConnection connect() throws Xdi2ClientException, IOException { if (this.getHttpURLConnection() != null) return this.getHttpURLConnection(); if (this.getXdiEndpointUri() == null) throw new Xdi2ClientException("No URL to connect to."); // prepare Accept: header AcceptHeader acceptHeader = AcceptHeader.create(recvMimeType); if (log.isDebugEnabled()) log.debug("Using Accept header " + acceptHeader.toString() + "."); // connect if (log.isDebugEnabled()) log.debug("Connecting to " + this.getXdiEndpointUri()); URL url = URLURIUtil.URItoURL(this.getXdiEndpointUri()); URLConnection URLconnection = url.openConnection(); if (! (URLconnection instanceof HttpURLConnection)) throw new Xdi2ClientException("Can only work with HTTP(S) URLs: " + this.getXdiEndpointUri()); HttpURLConnection httpURLConnection = (HttpURLConnection) URLconnection; httpURLConnection.setDoInput(true); httpURLConnection.setDoOutput(true); httpURLConnection.setInstanceFollowRedirects(this.getFollowRedirects()); System.setProperty("http.strictPostRedirect", "true"); httpURLConnection.setRequestProperty("Content-Type", sendMimeType.toString()); httpURLConnection.setRequestProperty("Accept", acceptHeader.toString()); httpURLConnection.setRequestProperty("User-Agent", this.getUserAgent()); httpURLConnection.setRequestMethod("POST"); // done if (log.isDebugEnabled()) log.debug("Connected successfully."); this.setHttpURLConnection(httpURLConnection); return httpURLConnection; } private void disconnect() { try { if (this.getHttpURLConnection() != null) { this.getHttpURLConnection().disconnect(); } } catch (Exception ex) { log.error("Cannot disconnect: " + ex.getMessage(), ex); } finally { this.setHttpURLConnection(null); } if (log.isDebugEnabled()) log.debug("Disconnected successfully."); } /* * Getters and setters */ public HttpURLConnection getHttpURLConnection() { return this.httpURLConnection; } public void setHttpURLConnection(HttpURLConnection httpURLConnection) { this.httpURLConnection = httpURLConnection; } public URI getXdiEndpointUri() { return this.xdiEndpointUri; } public void setXdiEndpointUri(URI xdiEndpointUri) { this.xdiEndpointUri = xdiEndpointUri; } public MimeType getSendMimeType() { return this.sendMimeType; } public void setSendMimeType(MimeType sendMimeType) { this.sendMimeType = sendMimeType; } public MimeType getRecvMimeType() { return this.recvMimeType; } public void setRecvMimeType(MimeType recvMimeFormat) { this.recvMimeType = recvMimeFormat; } public String getUserAgent() { return this.userAgent; } public void setUserAgent(String userAgent) { this.userAgent = userAgent; } public boolean getFollowRedirects() { return this.followRedirects; } public void setFollowRedirects(boolean followRedirects) { this.followRedirects = followRedirects; } /* * Object methods */ @Override public String toString() { return this.getXdiEndpointUri().toString(); } }