/*******************************************************************************
* 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.DPWSFramework;
import org.ws4d.java.communication.DPWSProtocolData;
import org.ws4d.java.communication.ProtocolData;
import org.ws4d.java.communication.ProtocolException;
import org.ws4d.java.communication.connection.ip.IPAddress;
import org.ws4d.java.communication.connection.tcp.Socket;
import org.ws4d.java.communication.connection.tcp.SocketFactory;
import org.ws4d.java.communication.monitor.MonitorStreamFactory;
import org.ws4d.java.communication.monitor.MonitoredInputStream;
import org.ws4d.java.communication.monitor.MonitoredOutputStream;
import org.ws4d.java.communication.monitor.MonitoringContext;
import org.ws4d.java.communication.monitor.ResourceLoader;
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.MIMEConstants;
import org.ws4d.java.security.DPWSSecurityManager;
import org.ws4d.java.security.SecurityManager;
import org.ws4d.java.structures.HashMap;
import org.ws4d.java.types.InternetMediaType;
import org.ws4d.java.types.URI;
import org.ws4d.java.util.Log;
/**
* Utility class for handling HTTP requests.
*/
public class HTTPRequestUtil {
protected static final String FAULT_METHOD_NOT_SUPPORTED = "HTTP Method not supported.";
// predefined exception messages.
protected static final String FAULT_UNEXPECTED_END = "Unexpected end of stream.";
protected static final String FAULT_MALFORMED_REQUEST = "Malformed HTTP request line.";
protected static final String FAULT_MALFORMED_HEADERFIELD = "Malformed HTTP header field.";
protected static final String FAULT_MALFORMED_CHUNK = "Malformed HTTP chunk header.";
// HTTP stuff
protected static final String[] supportedMethods = { HTTPConstants.HTTP_METHOD_GET, HTTPConstants.HTTP_METHOD_HEAD, HTTPConstants.HTTP_METHOD_POST };
protected static int delta = -1;
static {
/*
* Get maximal length for HTTP method string. After this length we
* should know whether the incoming data is HTTP or not.
*/
for (int k = 0; k < supportedMethods.length; k++) {
if (supportedMethods[k].length() > delta) {
delta = supportedMethods[k].length();
}
}
}
/**
* We are shy!
*/
private HTTPRequestUtil() {
}
public static HTTPRequestHeader handleRequest(InputStream in) throws IOException, ProtocolException {
/*
* This method handles the incoming HTTP connection and looks for the
* HTTP header and payload.
*/
String method = null;
String request = null;
String version = null;
method = HTTPUtil.readElement(in, delta);
if (method == null || method.length() == 0) {
return null;
} else {
// supported HTTP method found?
boolean supported = false;
for (int k = 0; k < supportedMethods.length; k++) {
if (method.equals(supportedMethods[k])) {
supported = true;
break;
}
}
if (!supported) {
throw new ProtocolException(HTTPRequestUtil.FAULT_METHOD_NOT_SUPPORTED + " (" + method + ")");
}
}
// Read the HTTP request
request = HTTPUtil.readElement(in);
// Read the HTTP version
version = HTTPUtil.readRequestVersion(in);
// Read the HTTP header fields
HashMap headerfields = new HashMap();
HTTPUtil.readHeaderFields(in, headerfields);
// Create HTTP header object
return new HTTPRequestHeader(method, request, version, headerfields, null);
}
/**
* Creates a request URI for the given request (relative request) and
* endpoint.
*
* @param request the HTTP request.
* @param address the host address.
* @return the <code>URI</code> representing this request.
*/
public static URI createRequestURI(String request, String address) {
// Create URI object for this request
// create base host address from endpoint information.
String hostAdr = HTTPConstants.HTTP_SCHEMA + "://" + address;
URI host = new URI(hostAdr);
URI requestURI = new URI(request, host);
return requestURI;
}
/**
* Creates an HTTP request header for HTTP POST with application/soap+xml
* content type.
*
* @param request the URI to which the header is sent.
* @return the HTTP request header.
*/
public static HTTPRequestHeader getDPWSPOSTHeader(String request) {
HashMap headerfields = new HashMap();
InternetMediaType type = new InternetMediaType(MIMEConstants.MEDIATYPE_APPLICATION, MIMEConstants.SUBTYPE_SOAPXML);
headerfields.put(HTTPConstants.HTTP_HEADER_CONTENT_TYPE, type.getMediaType());
HTTPRequestHeader header = new HTTPRequestHeader(HTTPConstants.HTTP_METHOD_POST, request, HTTPConstants.HTTP_VERSION11, headerfields);
return header;
}
/**
* Creates an HTTP request header for HTTP GET with application/soap+xml
* content type.
*
* @param request the URI to which the header is sent.
* @return the HTTP request header.
*/
public static HTTPRequestHeader getDPWSGETHeader(String request) {
HashMap headerfields = new HashMap();
InternetMediaType type = new InternetMediaType(MIMEConstants.MEDIATYPE_APPLICATION, MIMEConstants.SUBTYPE_SOAPXML);
headerfields.put(HTTPConstants.HTTP_HEADER_CONTENT_TYPE, type.getMediaType());
HTTPRequestHeader header = new HTTPRequestHeader(HTTPConstants.HTTP_METHOD_GET, request, HTTPConstants.HTTP_VERSION11, headerfields);
return header;
}
/**
* Writes an HTTP GET request header to the stream for the given request URI
* and with given media type (e.g. application/soap+xml). Can be set to
* chunked mode if the length of followed communication cannot be
* determined. The returned <code>OutputStream</code> MUST be used, it
* should be ensure that the chnuks are written correctly.
*
* @param out the output stream to which to write the HTTP request.
* @param method the HTTP request method.
* @param request the request URI.
* @param type the internet media type.
* @param chunked <code>true</code> if a special chunked output stream
* should be returned, <code>false</code> otherwise.
* @param trailer <code>true</code> if the chunk trailer should be appended
* at the end, <code>false</code> otherwise.
* @return <code>ChunkedOutputStream</code> if <code>chunked</code> is true,
* the normal output stream otherwise.
* @throws IOException
*/
public static OutputStream writeRequest(OutputStream out, String method, String request, InternetMediaType type, boolean chunked, boolean trailer) throws IOException {
return writeRequest(out, method, request, null, type, chunked, trailer);
}
/**
* Writes an HTTP GET request header to the stream for the given request URI
* and with given media type (e.g. application/soap+xml). Can be set to
* chunked mode, if the length of followed communication cannot be
* determined. The returned <code>OutputStream</code> MUST be used, it
* should ensure that the chnuks are written correctly.
*
* @param out the output stream to write the HTTP request to.
* @param method the HTTP request method.
* @param request the request URI.
* @param headerfields HTTP headerfields.
* @param type the internet media type.
* @param chunked <code>true</code> if a special chunked output stream
* should be returned, <code>false</code> otherwise.
* @param trailer <code>true</code> if the chunk trailer should be appended
* at the end, <code>false</code> otherwise.
* @return <code>ChunkedOutputStream</code> if <code>chunked</code> is true,
* the normal output stream otherwise.
* @throws IOException
*/
public static OutputStream writeRequest(OutputStream out, String method, String request, HashMap headerfields, InternetMediaType type, boolean chunked, boolean trailer) throws IOException {
if (method == null || (!method.equals(HTTPConstants.HTTP_METHOD_GET) && !method.equals(HTTPConstants.HTTP_METHOD_POST))) {
throw new IOException("No HTTP method set.");
}
if (headerfields == null) {
headerfields = new HashMap();
}
HTTPRequestHeader header = null;
if (method.equals(HTTPConstants.HTTP_METHOD_POST)) {
header = new HTTPRequestHeader(HTTPConstants.HTTP_METHOD_POST, request, HTTPConstants.HTTP_VERSION11, headerfields);
} else {
header = new HTTPRequestHeader(HTTPConstants.HTTP_METHOD_GET, request, HTTPConstants.HTTP_VERSION11, headerfields);
}
if (Log.isDebug()) {
Log.debug("<O> " + header.toString(), Log.DEBUG_LAYER_COMMUNICATION);
}
header.addHeaderFieldValue(HTTPConstants.HTTP_HEADER_CONTENT_TYPE, type.toString());
if (chunked) {
header.addHeaderFieldValue(HTTPConstants.HTTP_HEADER_TRANSFER_ENCODING, HTTPConstants.HTTP_HEADERVALUE_TRANSFERCODING_CHUNKED);
header.toStream(out);
// out.flush();
return new ChunkedOutputStream(out, trailer);
}
header.toStream(out);
// out.flush();
return out;
}
/**
* Returns an input stream which allows the reading of a resource from the
* given location.
*
* @param resLocation the resource's location (e.g.
* http://example.org/test.wsdl).
* @return an input stream for the given resource.
*/
public static ResourceLoader getResourceAsStream(String resLocation) throws IOException, ProtocolException {
if (resLocation.toLowerCase().startsWith(HTTPConstants.HTTP_SCHEMA)) {
URI location = new URI(resLocation);
if (Log.isDebug()) {
Log.debug("<O> Accessing resource over HTTP from " + resLocation, Log.DEBUG_LAYER_COMMUNICATION);
}
MonitorStreamFactory monFac = DPWSFramework.getMonitorStreamFactory();
Socket tcpSocket = null;
if (DPWSFramework.hasModule(DPWSFramework.SECURITY_MODULE) && resLocation.toLowerCase().startsWith(HTTPConstants.HTTPS_SCHEMA)) {
SecurityManager secMan = DPWSFramework.getSecurityManager();
if (!(secMan instanceof DPWSSecurityManager)) {
throw new IOException("Security manager is not valid for DPWS.");
}
tcpSocket = ((DPWSSecurityManager) secMan).getSecureSocket(location);
} else {
tcpSocket = SocketFactory.getInstance().createSocket(new IPAddress(location.getHost()), location.getPort());
}
DPWSProtocolData pd_out = null;
if (tcpSocket.getRemoteAddress() == null) {
/*
* TODO: CLDC quick fix! It's not possible to retrieve the
* remote address from the CLDC socket. :-(
*/
pd_out = new DPWSProtocolData(null, ProtocolData.DIRECTION_OUT, tcpSocket.getLocalAddress().getAddressWithoutNicId(), tcpSocket.getLocalPort(), null, tcpSocket.getRemotePort(), true);
} else {
pd_out = new DPWSProtocolData(null, ProtocolData.DIRECTION_OUT, tcpSocket.getLocalAddress().getAddressWithoutNicId(), tcpSocket.getLocalPort(), tcpSocket.getRemoteAddress().getAddressWithoutNicId(), tcpSocket.getRemotePort(), true);
}
final OutputStream out;
if (DPWSFramework.getMonitorStreamFactory() != null) {
out = new MonitoredOutputStream(tcpSocket.getOutputStream(), pd_out);
} else {
out = tcpSocket.getOutputStream();
}
if (monFac != null) {
monFac.getNewMonitoringContextOut(pd_out);
}
HTTPRequestHeader requestHeader = new HTTPRequestHeader(HTTPConstants.HTTP_METHOD_GET, location.getPath(), HTTPConstants.HTTP_VERSION11);
requestHeader.addHeaderFieldValue(HTTPConstants.HTTP_HEADER_HOST, location.getHost());
requestHeader.addHeaderFieldValue(HTTPConstants.HTTP_HEADER_CONNECTION, HTTPConstants.HTTP_HEADERVALUE_CONNECTION_CLOSE);
/*
* Write the request.
*/
requestHeader.toStream(out);
out.flush();
if (monFac != null) {
MonitoringContext context = monFac.getMonitoringContextOut(pd_out);
monFac.requestResource(pd_out, context, location);
}
String targetAddress = null;
if (Log.isDebug()) {
targetAddress = tcpSocket.getRemoteAddress() + "@" + tcpSocket.getRemotePort();
Log.debug("<O> " + requestHeader + " to " + targetAddress, Log.DEBUG_LAYER_COMMUNICATION);
}
/*
* Handle the response.
*/
DPWSProtocolData pd_in = (DPWSProtocolData) pd_out.createSwappedProtocolData();
InputStream in = tcpSocket.getInputStream();
if (DPWSFramework.getMonitorStreamFactory() != null) {
in = new MonitoredInputStream(in, pd_in);
}
if (monFac != null) {
monFac.getNewMonitoringContextIn(pd_in);
}
HTTPResponseHeader responseHeader = null;
try {
responseHeader = HTTPResponseUtil.handleResponse(in);
} catch (ProtocolException e) {
// TODO Auto-generated catch block
Log.printStackTrace(e);
}
if (responseHeader == null) {
throw new IOException("No HTTP response found.");
}
if (Log.isDebug()) {
if (targetAddress == null) {
targetAddress = tcpSocket.getRemoteAddress() + "@" + tcpSocket.getRemotePort();
}
Log.debug("<I> " + responseHeader + " from " + targetAddress, Log.DEBUG_LAYER_COMMUNICATION);
}
String encoding = responseHeader.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_TRANSFER_ENCODING);
String sSize = responseHeader.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_CONTENT_LENGTH);
int size = 0;
if (sSize != null) {
size = Integer.parseInt(sSize.trim());
}
// wrap the HTTP stream
InputStream httpIn = new HTTPInputStream(in, encoding, size) {
public void close() throws IOException {
out.close();
super.close();
}
};
if (responseHeader.getStatus() == 200) {
ResourceLoader rl = new ResourceLoader(httpIn, pd_out);
return rl;
} else if (responseHeader.getStatus() == 301 || responseHeader.getStatus() == 303 || responseHeader.getStatus() == 307) {
String newLocation = responseHeader.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_LOCATION);
if (newLocation == null) {
throw new IOException("HTTP Response malformed.");
}
ResourceLoader rl = getResourceAsStream(newLocation);
return rl;
}
}
return null;
}
public static void writeLastChunk(OutputStream out) throws IOException {
if (out instanceof ChunkedOutputStream) {
ChunkedOutputStream.writeLastChunk((ChunkedOutputStream) out);
}
}
}