/*******************************************************************************
* Copyright (c) 2009 MATERNA Information & Communications. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html. For further
* project-related information visit http://www.ws4d.org. The most recent
* version of the JMEDS framework can be obtained from
* http://sourceforge.net/projects/ws4d-javame.
******************************************************************************/
package org.ws4d.java.communication.protocol.http;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.ws4d.java.communication.ProtocolException;
import org.ws4d.java.communication.connection.tcp.TCPClient;
import org.ws4d.java.communication.connection.tcp.TCPConnection;
import org.ws4d.java.communication.protocol.http.header.HTTPRequestHeader;
import org.ws4d.java.communication.protocol.http.header.HTTPResponseHeader;
import org.ws4d.java.constants.HTTPConstants;
import org.ws4d.java.constants.Specialchars;
import org.ws4d.java.util.Sync;
/**
* Client for HTTP communication.
* <p>
* This client allows synchronous HTTP communication.
* </p>
* <h3>Example</h3>
* <p>
* HTTPClient client = HTTPClient.create("http://127.0.0.1:8080/test");<br />
* // Send a simple GET request for /test<br />
* OutputStream out = client.exchange();<br />
* // Get the response<br />
* HTTP header HTTPResponseHeader response = client.getResponseHeader();<br />
* // Get the response HTTP body<br />
* InputStream in = client.getResponseBody();<br />
* // Send a simple GET request for /hello<br />
* OutputStream out = client.exchange("/hello");<br />
* // Get the response HTTP header<br />
* HTTPResponseHeader response = client.getResponseHeader();<br />
* // Get the response HTTP body<br />
* InputStream in = client.getResponseBody();<br />
* // Close the communication<br />
* client.close();
* </p>
*/
public class SimpleHTTPClient {
/**
* The used TCP connection.
*/
private TCPConnection connection = null;
private volatile InputStream in = null;
private OutputStream out = null;
private Object lock = new Object();
/**
* The underlying TCP client.
*/
private TCPClient client = null;
/**
* The HTTP header for the request.
*/
private HTTPRequestHeader requestHeader = null;
/**
* The HTTP header from the response.
*/
private HTTPResponseHeader responseHeader = null;
/**
* Default request.
*/
private String request = "/";
/**
* Connection keep-alive indicator.
*/
private boolean keepalive = true;
/**
* Wrapped HTTP body input stream.
*/
private InputStream inBody = null;
private HTTPClientDestination destination = null;
/**
* Creates a HTTP client to communicate with given host.
* <p>
* The given request MUST be a HTTP address. The path behind host and port
* will be used in the {@link #exchange()} method as request. The method
* {@link #exchange(String)} and {@link #exchange(String, String)} expect
* the request on their own.
* </p>
*
* @param request The request which will be used in the {@link #exchange()}
* method.
* @return A HTTP client, ready for communication.
*/
public SimpleHTTPClient(HTTPClientDestination dest) {
destination = dest;
}
/**
* Returns the preset HTTP path for the request.
* <p>
* This request is used as default for the {@link #exchange()} method.
* </p>
*
* @return the HTTP path.
*/
public String getPresetRequest() {
return request;
}
/**
* Opens a TCP connection to the defined host.
* <p>
* Usually the <code>exchange</code> methods will open the TCP connection on
* their own, but sometimes it may be necessary to have previously opened
* the connection.
* </p>
*
* @throws IOException Throws exception if connection fails.
*/
public synchronized void explicitConnect() throws IOException {
if (connection == null) {
client = TCPClient.connect(destination.getHost(), destination.getPort(), destination.isSecure(), destination.getAlias());
connection = client.getConnection();
}
}
/**
* Resets the connection.
*
* @throws IOException
*/
public synchronized void resetConnection() {
if (connection != null) {
connection = null;
}
}
/**
* Sends a simple HTTP GET request to the host, defined by the
* <code>create</code> method.
*
* @return An {@link OutputStream} which can be used to send HTTP body as
* part of the request.
* @throws IOException Throws exception if sending the HTTP header fails.
*/
public OutputStream exchange() throws IOException {
return exchange(request);
}
/**
* Sends a simple HTTP GET request with given request path to the host,
* defined by the <code>create</code> method.
*
* @param request the HTTP request path.
* @return An {@link OutputStream} which can be used to send HTTP body as
* part of the request.
* @throws IOException Throws exception if sending the HTTP header fails.
*/
public OutputStream exchange(String request) throws IOException {
return exchange(HTTPConstants.HTTP_METHOD_GET, request);
}
/**
* Sends a HTTP request with a given HTTP method and request path to the
* host, defined by the <code>create</code> method.
* <p>
* The HTTP method can be <strong>GET</strong> or <strong>POST</strong>.
* </p>
*
* @param method HTTP method. e.g. <strong>GET</strong> or
* <strong>POST</strong>.
* @param request The HTTP request path.
* @return An {@link OutputStream} which can be used to send HTTP body as
* part of the request.
* @throws IOException Throws exception if sending the HTTP header fails.
*/
public OutputStream exchange(String method, String request) throws IOException {
HTTPRequestHeader header = new HTTPRequestHeader(method, request, HTTPConstants.HTTP_VERSION11);
if (header.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_HOST) == null) {
header.addHeaderFieldValue(HTTPConstants.HTTP_HEADER_HOST, destination.getHost().getAddressWithoutNicId() + (char) Specialchars.COL + destination.getPort());
}
if (header.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_USER_AGENT) == null) {
header.addHeaderFieldValue(HTTPConstants.HTTP_HEADER_USER_AGENT, "JMEDS HTTP Client");
}
return exchange(header, true);
}
/**
* Sends a HTTP request with given HTTP header to the host, defined by the
* <code>create</code> method.
*
* @param header The HTTP header.
* @return An {@link OutputStream} which can be used to send HTTP body as
* part of the request.
* @throws IOException Throws exception sending HTTP header fails.
*/
public OutputStream exchange(HTTPRequestHeader header, boolean sendHeader) throws IOException {
if (!keepalive) {
throw new IOException("Cannot send new request. Server requested to close the connection.");
}
/*
* Establishes connection
*/
synchronized (this) {
if (connection == null) {
client = TCPClient.connect(destination.getHost(), destination.getPort(), destination.isSecure(), destination.getAlias());
connection = client.getConnection();
}
synchronized (lock) {
in = connection.getInputStream();
out = connection.getOutputStream();
lock.notifyAll();
}
}
/*
* Checks Transfer-Encoding
*/
String sLength = header.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_CONTENT_LENGTH);
String encoding = header.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_TRANSFER_ENCODING);
int length = 0;
if (sLength != null) {
length = Integer.parseInt(sLength.trim());
}
String con = header.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_CONNECTION);
if (HTTPConstants.HTTP_HEADERVALUE_CONNECTION_CLOSE.equals(con)) {
keepalive = false;
}
/*
* Checks some fields we should set
*/
String agent = header.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_USER_AGENT);
if (agent == null) {
header.addHeaderFieldValue(HTTPConstants.HTTP_HEADER_USER_AGENT, "JMEDS HTTP Client");
}
String conHost = header.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_HOST);
if (conHost == null) {
header.addHeaderFieldValue(HTTPConstants.HTTP_HEADER_HOST, destination.getHost().getAddressWithoutNicId() + (char) Specialchars.COL + destination.getPort());
}
requestHeader = header;
/*
* Sends HTTP request
*/
if (sendHeader) {
header.toStream(out);
out.flush();
}
String expect = header.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_EXPECT);
if (HTTPConstants.HTTP_HEADERVALUE_EXPECT_CONTINUE.equals(expect)) {
/*
* This client wants to know whether he can send a body or not. So
* we can assume the server will tell us what to do.
*/
try {
HTTPResponseHeader response = HTTPResponseUtil.handleResponse(in);
if (response.getStatus() != 100) {
/*
* Server does not want us to continue. We MUST read the
* final header send.
*/
responseHeader = response;
return new HTTPOutputStream(out, 0);
}
} catch (ProtocolException e) {
throw new IOException("Cannot handle HTTP respone for continue: " + e.getMessage());
}
}
if (HTTPConstants.HTTP_HEADERVALUE_TRANSFERCODING_CHUNKED.equals(encoding)) {
return new ChunkedOutputStream(out, false);
}
return new HTTPOutputStream(out, length);
}
public void sendHeader() throws IOException {
/*
* Establish connection
*/
synchronized (this) {
if (connection == null) {
client = TCPClient.connect(destination.getHost(), destination.getPort(), destination.isSecure(), destination.getAlias());
connection = client.getConnection();
}
synchronized (lock) {
in = connection.getInputStream();
out = connection.getOutputStream();
lock.notifyAll();
}
}
requestHeader.toStream(out);
out.flush();
}
/**
* Returns the underlying TCP client.
*
* @return the TCP client.
*/
public TCPClient getTCPClient() {
return client;
}
/**
* Returns the HTTP request header.
*
* @return the HTTP request header.
*/
public HTTPRequestHeader getRequestHeader() {
return requestHeader;
}
/**
* Returns the HTTP response header.
*
* @return the HTTP response header.
* @throws IOException Throws exception receiving the header fails.
*/
public HTTPResponseHeader getResponseHeader() throws IOException {
synchronized (lock) {
while (in == null) {
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
try {
responseHeader = HTTPResponseUtil.handleResponse(in);
} catch (ProtocolException e) {
throw new IOException(e.getMessage());
}
String con = responseHeader.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_CONNECTION);
if (HTTPConstants.HTTP_HEADERVALUE_CONNECTION_CLOSE.equals(con)) {
keepalive = false;
}
return responseHeader;
}
/**
* Returns an {@link InputStream} which allows to read the HTTP response
* body.
*
* @return an {@link InputStream} which allows to read the HTTP response
* body.
*/
public InputStream getResponseBody() {
return getResponseBody(null);
}
public InputStream getResponseBody(Sync syn) {
synchronized (lock) {
while (in == null) {
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
String encoding = responseHeader.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_TRANSFER_ENCODING);
String bodyLength = responseHeader.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_CONTENT_LENGTH);
int size = 0;
if (bodyLength != null) {
size = Integer.parseInt(bodyLength.trim());
}
inBody = new HTTPInputStream(in, encoding, size, syn);
return inBody;
}
/**
* Returns <code>true</code> if the HTTP connection must stay persistent,
* <code>false</code> otherwise.
*
* @return <code>true</code> if the HTTP connection must stay persistent,
* <code>false</code> otherwise.
*/
public boolean isKeepAlive() {
return keepalive;
}
/**
* Closes the HTTP connection between client and server.
*
* @throws IOException Throws exception
*/
public synchronized void close() throws IOException {
if (connection != null) {
connection.close();
}
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return destination.hashCode();
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
SimpleHTTPClient other = (SimpleHTTPClient) obj;
if (destination.getHost() == null) {
if (other.destination.getHost() != null) return false;
} else if (!destination.getHost().equals(other.destination.getHost())) return false;
if (destination.getPort() != other.destination.getPort()) return false;
return true;
}
public boolean isSecure() {
return destination.isSecure();
}
public String getAlias() {
return destination.getAlias();
}
HTTPClientDestination getDestination() {
return destination;
}
}