/*******************************************************************************
* 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.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.ws4d.java.DPWSFramework;
import org.ws4d.java.communication.ProtocolData;
import org.ws4d.java.communication.connection.tcp.TCPClient;
import org.ws4d.java.communication.connection.tcp.TCPConnection;
import org.ws4d.java.communication.monitor.MonitorStreamFactory;
import org.ws4d.java.communication.monitor.MonitoringContext;
import org.ws4d.java.communication.protocol.http.header.HTTPRequestHeader;
import org.ws4d.java.communication.protocol.http.header.HTTPResponseHeader;
import org.ws4d.java.communication.protocol.http.requests.DefaultHTTPGetRequest;
import org.ws4d.java.configuration.DPWSProperties;
import org.ws4d.java.configuration.HTTPProperties;
import org.ws4d.java.constants.HTTPConstants;
import org.ws4d.java.message.Message;
import org.ws4d.java.structures.HashMap;
import org.ws4d.java.structures.Iterator;
import org.ws4d.java.structures.LinkedList;
import org.ws4d.java.structures.Queue;
import org.ws4d.java.types.InternetMediaType;
import org.ws4d.java.util.Log;
import org.ws4d.java.util.Sync;
import org.ws4d.java.util.TimedEntry;
import org.ws4d.java.util.WatchDog;
/**
* Client for asynchronous HTTP communication.
* <p>
* This client allows the asynchronous communication over HTTP.
* </p>
* <h3>Example</h3>
* <p>
* HTTPClient client = HTTPClient.create("http://127.0.0.1:8080/hello");<br />
* // access http://127.0.0.1:8080/hello<br />
* client.exchange();<br />
* // access http://127.0.0.1:8080/test<br />
* client.exchange("/test");<br />
* // access http://127.0.0.1:8080/test with a user defined HTTP GET request<br />
* HTTPRequest request = new DefaultHTTPGetRequest(); client.exchange(request);<br />
* // Close the communication<br />
* client.close();
* </p>
*
* @see HTTPRequest
*/
public class HTTPClient extends TimedEntry {
public static int MAX_CLIENT_CONNECTIONS = HTTPProperties.getInstance().getMaxConnections();
/**
* Indicates whether this client is closed or not.
*/
private boolean closed = false;
/**
* The underlying simple HTTP client which allows the HTTP communication.
*/
private SimpleHTTPClient simpleHTTPClient = null;
/**
* Thread which handles queued requests.
*/
private AsyncRequesterWriter requester = null;
/**
* Thread which handles incoming responses.
*/
private AsyncResponseReader responder = null;
/**
* Map of registered handlers which handles the incoming response.
*/
private HashMap handlers = new HashMap();
/**
* Keep-alive mode (get first mode from framework).
*/
private boolean keepalive = true;
/**
* Flag for the asynchronous writer. Indicates that the writer is done.
*/
private boolean writerReady = false;
/**
* Flag for the asynchronous reader. Indicates that the reader is done.
*/
private boolean readerReady = false;
private Queue pendingRequests = null;
/**
* Timeout limit for the watch dog.
*/
private static long REQUEST_TIMEOUT = 5000;
/**
*
*/
private static boolean multipleCons = true;
/**
* Table of HTTP clients.
*/
private static HashMap allClients = new HashMap();
/**
* Table of HTTP clients which can be reused for requests.
*/
private static HashMap freeClients = new HashMap();
/**
* The thread pool for the threads started by this client.
*/
private static final DPWSProperties properties = DPWSProperties.getInstance();
/**
* Kill all existing and provided HTTP clients with the {@link #kill()}
* method.
*/
public synchronized static void killAllClients() {
Iterator it = allClients.values().iterator();
while (it.hasNext()) {
LinkedList l = (LinkedList) it.next();
if (l != null) {
Iterator jt = l.iterator();
while (jt.hasNext()) {
HTTPClient hc = (HTTPClient) jt.next();
hc.kill(false);
jt.remove();
}
}
it.remove();
}
}
/**
* Close all existing and provided HTTP clients with the {@link #close()}
* method.
*/
public synchronized static void closeAllClients() {
Iterator it = allClients.values().iterator();
while (it.hasNext()) {
LinkedList l = (LinkedList) it.next();
if (l != null) {
Iterator jt = l.iterator();
while (jt.hasNext()) {
HTTPClient hc = (HTTPClient) jt.next();
hc.close(false);
jt.remove();
}
}
it.remove();
}
}
/**
* Adds a HTTP client to the list of existing and provided clients.
*
* @param client the client to add.
* @return the client which was added.
*/
private static HTTPClient addClient(HTTPClient client) {
LinkedList c = (LinkedList) allClients.get(client.simpleHTTPClient.getDestination());
if (c == null) {
c = new LinkedList();
allClients.put(client.simpleHTTPClient.getDestination(), c);
}
c.add(client);
return client;
}
/**
* Removes a HTTP client from the list of existing and provided clients.
*
* @param client to remove.
*/
private static void removeClient(HTTPClient client) {
LinkedList c = (LinkedList) allClients.get(client.simpleHTTPClient.getDestination());
c.remove(client);
if (c.size() == 0) {
allClients.remove(client.simpleHTTPClient.getDestination());
}
}
/**
* Adds a HTTP client to the list of free clients.
*
* @param client the client to add.
*/
private synchronized static void addFreeClient(HTTPClient client) {
HTTPRequest request = client.getPendingRequest();
if (request != null) {
client.requester.setRequest(request);
return;
}
synchronized (freeClients) {
LinkedList frees = (LinkedList) freeClients.get(client.simpleHTTPClient.getDestination());
if (frees == null) {
frees = new LinkedList();
frees.addFirst(client);
freeClients.put(client.simpleHTTPClient.getDestination(), frees);
} else {
frees.addFirst(client);
}
WatchDog.getInstance().register(client, REQUEST_TIMEOUT);
}
}
/**
* Removes a HTTP client from the list of free clients.
*
* @param client the client to remove.
*/
private synchronized static boolean removeFreeClient(HTTPClient client) {
boolean result = false;
synchronized (freeClients) {
WatchDog.getInstance().unregister(client);
LinkedList frees = (LinkedList) freeClients.get(client.simpleHTTPClient.getDestination());
if (frees != null) {
result = frees.remove(client);
if (frees.size() == 0) {
freeClients.remove(client.simpleHTTPClient.getDestination());
}
}
}
return result;
}
/**
* Creates HTTP client based on host and port.
*
* @param host the host address to connect to.
* @param port the port on the host.
*/
private HTTPClient(HTTPClientDestination dest) {
simpleHTTPClient = new SimpleHTTPClient(dest);
keepalive = properties.getHTTPClientKeepAlive();
responder = new AsyncResponseReader(this);
requester = new AsyncRequesterWriter(this);
}
/**
* Registers a handler with given Internet media type which will handle
* incoming HTTP responses.
* <p>
* This {@link HTTPResponseHandler} will ONLY BE USED if NO handler is
* returned by the {@link HTTPRequest#getResponseHandler(InternetMediaType)}
* method.
* </p>
*
* @param type the Internet media type.
* @param handler the handler which will handle the HTTP response.
* @see HTTPRequest
*/
public void register(InternetMediaType type, HTTPResponseHandler handler) {
handlers.put(type, handler);
}
/**
* Sends a simple HTTP GET request to the host, defined by the
* <code>create</code> method.
* <p>
* The request is not actually sent instantaneously to the host. It is put
* into a request queue and will be started as soon as possible. The speed
* depends on the thread scheduler and the Object.notifiy() method.
* </p>
*/
public synchronized static void exchange(HTTPClientDestination dest) {
exchange(dest, "/");
}
/**
* Sends a simple HTTP GET request with given request path to the host,
* defined by the <code>create</code> method.
* <p>
* The request is not actually sent instantaneously to the host. It is put
* into a request queue and will be started as soon as possible. The speed
* depends on the thread scheduler and the Object.notifiy() method.
* </p>
*
* @param request the HTTP request path.
*/
public synchronized static void exchange(HTTPClientDestination dest, String request) {
exchange(dest, new DefaultHTTPGetRequest(request));
}
/**
* Sends a HTTP request (with the path defined in the HTTP header inside the
* request) to the host, defined by the <code>create</code> method.
* <p>
* The request is not actually sent instantaneously to the host. It is put
* into a request queue and will be started as soon as possible. The speed
* depends on the thread scheduler and the Object.notifiy() method..
* </p>
*
* @param request the HTTP request.
*/
public synchronized static void exchange(HTTPClientDestination dest, HTTPRequest request) {
addRequest(dest, request);
}
/**
* 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 simpleHTTPClient.getPresetRequest();
}
/**
* Returns the TCP connection for this HTTP client.
*
* @return the TCP connection
* @see TCPConnection
*/
public TCPConnection getTCPConnection() {
if (simpleHTTPClient != null) {
TCPClient tcpClient = simpleHTTPClient.getTCPClient();
if (tcpClient != null) {
return tcpClient.getConnection();
}
}
return null;
}
/**
* Closes the connection with the server.
* <p>
* This will stop the response and request threads before the connection is
* closed. The client will try to send the queued requests and handle the
* incoming responses before the client is closed.
* </p>
* <p>
* If the client should be closed immediately use the {@link #kill()}
* method.
* </p>
*/
public synchronized void close() {
close(true);
}
private void close(boolean remove) {
if (closed) return;
removeFreeClient(this);
closed = true;
requester.stop();
responder.stop();
try {
simpleHTTPClient.close();
} catch (IOException e) {
Log.error("Cannot close client connection. " + e.getMessage());
}
if (remove) {
removeClient(this);
}
}
/**
* Closes the connection with the server immediately!!!
* <p>
* Existing connections will be closed regardless of which thread wants to
* read the streams. This will stop the response and request threads before
* the connection is closed.
* </p>
* <p>
* If the connection should be closed without killing the connections the
* {@link #close()} method should be used.
* </p>
*/
public synchronized void kill() {
kill(true);
}
private void kill(boolean remove) {
if (closed) return;
removeFreeClient(this);
closed = true;
/*
* Close the internal client before killing threads. This will close all
* connections.
*/
try {
simpleHTTPClient.close();
} catch (IOException e) {
Log.error("Cannot close client connection. " + e.getMessage());
}
requester.stop();
responder.kill();
if (remove) {
removeClient(this);
}
}
/**
* Returns <code>true</code> if the client is closed and cannot be used for
* a request or <code>false</code> if the client can still be used.
*
* @return <code>true</code> if the client is closed and cannot be used for
* a request or <code>false</code> if the client can still be used.
*/
public synchronized boolean isClosed() {
return closed;
}
/**
* Eats the omitted bytes.
*
* @return the amount of bytes eaten.
* @throws IOException
*/
private int eat(InputStream body) throws IOException {
if (body == null) return 0;
int n = 0;
while (body.read() != -1) {
n++;
}
return n;
}
/*
* (non-Javadoc)
* @see org.ws4d.java.management.TimedEntry#timedOut()
*/
protected void timedOut() {
if (Log.isDebug()) {
TCPConnection connection = getTCPConnection();
if (connection != null) {
Log.debug("HTTP client timeout: " + connection.getProtocolData(), Log.DEBUG_LAYER_COMMUNICATION);
} else {
Log.debug("HTTP client timeout, no connection data available.", Log.DEBUG_LAYER_COMMUNICATION);
}
}
if (!removeFreeClient(this)) return;
close();
}
private synchronized void writerReady() {
if (readerReady) {
if (keepalive && requester.running && responder.running) {
readerReady = false;
addFreeClient(this);
} else {
closeAndProcessPendingRequest();
}
} else {
writerReady = true;
}
}
private synchronized void readerReady() {
if (writerReady) {
if (keepalive && requester.running && responder.running) {
writerReady = false;
addFreeClient(this);
} else {
closeAndProcessPendingRequest();
}
} else {
readerReady = true;
}
}
private static void addRequest(HTTPClientDestination dest, HTTPRequest request) {
/*
* Check for HTTP client.
*/
HTTPClient hc = null;
synchronized (freeClients) {
LinkedList frees = (LinkedList) freeClients.get(dest);
if (frees == null) {
LinkedList c = (LinkedList) allClients.get(dest);
int cons = (c == null ? 0 : c.size());
if (!multipleCons) {
if (c != null) {
hc = (HTTPClient) c.getFirst();
hc.queueRequest(request);
return;
}
} else if (cons >= dest.getMaxConnections()) {
if (c != null) {
hc = (HTTPClient) c.getFirst();
hc.queueRequest(request);
return;
}
}
hc = new HTTPClient(dest);
addClient(hc);
} else {
hc = (HTTPClient) frees.removeFirst();
WatchDog.getInstance().unregister(hc);
if (frees.size() == 0) {
freeClients.remove(dest);
}
}
}
/*
* Do not queue any requests if the client is already closed.
*/
if (hc.isClosed()) {
throw new RuntimeException("Cannot send request. HTTP client closed.");
}
hc.requester.setRequest(request);
}
private void queueRequest(HTTPRequest request) {
if (pendingRequests == null) {
pendingRequests = new Queue();
}
pendingRequests.enqueue(request);
}
private HTTPRequest getPendingRequest() {
if (pendingRequests == null) {
return null;
}
return (HTTPRequest) pendingRequests.get();
}
private void closeAndProcessPendingRequest() {
close();
HTTPRequest request = getPendingRequest();
if (request != null) {
HTTPClientDestination dest = simpleHTTPClient.getDestination();
HTTPClient hc = new HTTPClient(dest);
addClient(hc);
hc.pendingRequests = pendingRequests;
hc.requester.setRequest(request);
}
}
/**
* This threads waits until it is allowed to read blocks from the input
* stream. It is synchronized with the {@link AsyncRequesterWriter}.
*/
private class AsyncResponseReader implements Runnable {
/**
* Indicates whether this thread should work or not.
*/
private volatile boolean running = true;
/**
* This entry will be set by the {@link AsyncRequesterWriter} which
* allows this reader to know every thing about the request.
* <p>
*/
private volatile HTTPRequest request = null;
/**
* This object is used to wait until a request is made.
*/
private Object waitForRequest = new Object();
/**
* This object is used to wait until the whole response was read before
* stopping the thread.
*/
private Object lockResponse = new Object();
/**
* Reference of the outer class.
*/
private HTTPClient client = null;
/**
* Creates a reader and starts it as thread.
*/
AsyncResponseReader(HTTPClient client) {
this.client = client;
DPWSFramework.getThreadPool().execute(this);
}
/**
* This method is invoked by the {@link AsyncRequesterWriter} to notify
* it of a request.
* <p>
* This will put this thread into a blocking read on the input stream.
* </p>
*
* @param entry This entry contains every interesting information about
* the request made.
*/
public void notifyAboutRequest(HTTPRequest request) {
synchronized (waitForRequest) {
this.request = request;
waitForRequest.notifyAll();
}
}
public void justNotify() {
synchronized (waitForRequest) {
waitForRequest.notifyAll();
}
}
/*
* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
/*
* response
*/
try {
/*
* Should we work? ;-)
*/
RUNNING: while (running) {
ProtocolData pd = null;
/*
* Wait until the request sender notifies us.
*/
synchronized (waitForRequest) {
while (request == null) {
waitForRequest.wait(500);
/*
* Check for "stop". Maybe we should not continue
* sending. Check for keep alive too...
*/
if (!running) {
if (request != null) {
throw new RuntimeException("HTTP response was not handled. HTTP reader not running.");
}
break RUNNING;
}
}
}
MonitorStreamFactory monFac = DPWSFramework.getMonitorStreamFactory();
/*
* Try to read the response. This will block on the input
* stream.
*/
synchronized (lockResponse) {
try {
Sync streamLock = new Sync();
TCPClient c = simpleHTTPClient.getTCPClient();
TCPConnection connection = c.getConnection();
pd = connection.getProtocolData().createSwappedProtocolData();
MonitoringContext context = null;
if (monFac != null) {
context = monFac.getNewMonitoringContextIn(pd);
}
HTTPResponseHeader response = simpleHTTPClient.getResponseHeader();
InputStream in = simpleHTTPClient.getResponseBody(streamLock);
if (Log.isDebug()) {
Log.debug("<I> " + response + " from " + pd.getDestinationAddress() + ", " + connection, Log.DEBUG_LAYER_COMMUNICATION);
}
String encoding = response.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_TRANSFER_ENCODING);
int contentLength = (response.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_CONTENT_LENGTH) != null) ? Integer.parseInt(response.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_CONTENT_LENGTH).trim()) : -1;
String contenttype = response.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_CONTENT_TYPE);
InternetMediaType mediaType = new InternetMediaType(contenttype);
String con = response.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_CONNECTION);
if (HTTPConstants.HTTP_HEADERVALUE_CONNECTION_CLOSE.equals(con)) {
/*
* The server wishes to close the connection
* after the response is done.
*/
keepalive = false;
requester.notifyKeepAliveDisabled();
}
/*
* Check for response handler belonging to the
* request.
*/
HTTPResponseHandler handler = request.getResponseHandler(mediaType);
/*
* No handler found inside the request? Check the
* internal table.
*/
if (handler == null) {
handler = (HTTPResponseHandler) handlers.get(mediaType);
}
/*
* If no handler was found, the consumer thread will
* be started and will finish after eating all
* omitted bytes. This should NOT happen. A client
* should not start a request without a handler
* which can handle the incoming response.
*/
if ((HTTPConstants.HTTP_HEADERVALUE_TRANSFERCODING_CHUNKED.equals(encoding) || contentLength > 0)) {
/*
* HTTP response contains content, read it.
*/
StreamConsumerThread consumer = new StreamConsumerThread(handler, response, in, request, context);
/*
* Wait until the current response is fully
* read.
*/
streamLock.reset();
synchronized (streamLock) {
while (!streamLock.isNotified()) {
try {
DPWSFramework.getThreadPool().execute(consumer);
streamLock.wait();
} catch (InterruptedException e) {
streamLock.notifyNow();
}
}
}
Exception e = streamLock.getException();
if (e != null) {
if (e instanceof IOException) {
throw (IOException) e;
} else {
Log.error("A problem occured during stream read. " + e.getMessage());
}
}
} else {
/*
* This response has no HTTP body, we will pass
* "null" to the handler.
*/
StreamConsumerThread consumer = new StreamConsumerThread(handler, response, null, request, context);
DPWSFramework.getThreadPool().execute(consumer);
}
} catch (IOException e) {
if (!closed) {
/*
* We cannot handle response?
*/
Log.error("Cannot handle HTTP response. " + e.getMessage());
ExceptionNotification eNotification = new ExceptionNotification(pd, request, e, true);
eNotification.start();
}
}
request = null;
}
if (!keepalive) {
break;
}
client.readerReady();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
running = false;
client.readerReady();
}
/**
* Stops the response reader as soon as possible.
* <p>
* This method will wait to handle the response. If it is necessary to
* stop the reader immediately the {@link #kill()} method should be
* used.
* </p>
*/
public void stop() {
if (running == false) return;
synchronized (lockResponse) {
running = false;
}
synchronized (waitForRequest) {
waitForRequest.notifyAll();
}
}
/**
* Kill the response reader immediately.
*/
public void kill() {
if (running == false) return;
running = false;
synchronized (waitForRequest) {
waitForRequest.notifyAll();
}
}
}
/**
* This thread is used to queue the HTTP client requests.
*/
private class AsyncRequesterWriter implements Runnable {
/**
* The request which should be send.
*/
private volatile HTTPRequest request = null;
/**
* Indicates whether this thread should work or not.
*/
private volatile boolean running = true;
/**
* This object is used to wait until the whole request was sent before
* stopping the thread.
*/
private Object lockRequest = new Object();
/**
* The parent asynchronous HTTP client.
*/
private HTTPClient client = null;
/**
* Creates a writer and starts it as thread.
*/
AsyncRequesterWriter(HTTPClient client) {
this.client = client;
DPWSFramework.getThreadPool().execute(this);
}
/**
* Sets an HTTP request.
*
* @param request the HTTP request.
*/
public synchronized void setRequest(HTTPRequest request) {
synchronized (lockRequest) {
this.request = request;
lockRequest.notifyAll();
}
}
public void notifyKeepAliveDisabled() {
running = false;
}
/*
* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
try {
RUNNING: while (running) {
/*
* Wait until a element is queued.
*/
ProtocolData pd = null;
boolean gotException = false;
/*
* Try to send the request
*/
synchronized (lockRequest) {
while (request == null) {
lockRequest.wait(500);
/*
* Check for "stop". Maybe we should not continue
* sending. Check for keep alive too...
*/
if (!running) {
/*
* Unregister watch dog if leaving here.
*/
if (request != null) {
throw new RuntimeException("HTTP request was not send. HTTP writer was not running.");
}
break RUNNING;
}
}
MonitorStreamFactory monFac = DPWSFramework.getMonitorStreamFactory();
try {
HTTPRequestHeader header = request.getRequestHeader();
/*
* Open the connection if necessary. Notify the
* reader about the request. Send the request.
*/
simpleHTTPClient.explicitConnect();
responder.notifyAboutRequest(request);
TCPClient c = simpleHTTPClient.getTCPClient();
TCPConnection con = c.getConnection();
pd = con.getProtocolData();
if (Log.isDebug()) {
Log.debug("<O> " + header + " to " + pd.getDestinationAddress() + ", " + con, Log.DEBUG_LAYER_COMMUNICATION);
}
MonitoringContext context = null;
if (monFac != null) {
context = monFac.getNewMonitoringContextOut(pd);
}
String enc = header.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_TRANSFER_ENCODING);
int contentLength = (header.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_CONTENT_LENGTH) != null) ? Integer.parseInt(header.getHeaderFieldValue(HTTPConstants.HTTP_HEADER_CONTENT_LENGTH).trim()) : -1;
OutputStream requestBody = null;
/*
* No HTTP Content-Length set and it is not
* chunked?! We MUST calculate the content length to
* send a correct HTTP message. WILL NOT WORK FOR
* STREAMS!
*/
if (!HTTPConstants.HTTP_HEADERVALUE_TRANSFERCODING_CHUNKED.equals(enc) && contentLength == -1) {
header.removeHeaderFieldValue(HTTPConstants.HTTP_HEADER_TRANSFER_ENCODING);
enc = null;
requestBody = simpleHTTPClient.exchange(header, false);
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
request.serializeRequestBody(buffer, pd, context); // TODO
int size = buffer.size();
((HTTPOutputStream) requestBody).setLength(size);
header.addHeaderFieldValue(HTTPConstants.HTTP_HEADER_CONTENT_LENGTH, Integer.toString(size));
simpleHTTPClient.sendHeader();
byte[] b = buffer.toByteArray();
requestBody.write(b);
requestBody.flush();
} else {
requestBody = simpleHTTPClient.exchange(header, true);
request.serializeRequestBody(requestBody, pd, context); // TODO
}
/*
* Was chunked? Write last chunk.
*/
if (HTTPConstants.HTTP_HEADERVALUE_TRANSFERCODING_CHUNKED.equals(enc)) {
ChunkedOutputStream.writeLastChunk((ChunkedOutputStream) requestBody);
requestBody.flush();
}
if (monFac != null) {
Message m = context.getMessage();
if (m != null) {
monFac.send(pd, context, m);
}
monFac.resetMonitoringContextOut(pd);
}
} catch (IOException e) {
gotException = true;
if (!closed) {
Log.error("Cannot send HTTP request. " + e.getMessage() + ". Resetting TCP connection (" + ((pd == null) ? "no address known" : pd.toString()) + ").");
simpleHTTPClient.resetConnection();
ExceptionNotification eNotification = new ExceptionNotification(pd, request, e, false);
eNotification.start();
}
}
request = null;
}
if (!keepalive) {
break;
}
client.writerReady();
if (gotException) {
/*
* the reader may not receive any response for the
* current request; in consequence, it will not call
* readerReady() itself and this HTTPClient will not be
* cleaned-up by watch dog. that's why we do this from
* here
*/
readerReady();
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
running = false;
client.writerReady();
}
/**
* Stops the request writer as soon as possible.
* <p>
* This method allows the writer to complete queued requests. If it is
* necessary to kill the writer immediately the {@link #kill()} method
* should be used.
* </p>
*/
public void stop() {
if (running == false) return;
running = false;
synchronized (lockRequest) {
/*
* wait until request is done
*/
request = null;
lockRequest.notifyAll();
}
// Notify the reader
responder.justNotify();
}
}
/**
* This thread allows the handling of an incoming response independently
* from the thread handling the persistent HTTP connection.
*/
private class StreamConsumerThread implements Runnable {
private HTTPResponseHandler handler = null;
private HTTPResponseHeader header = null;
private InputStream body = null;
private HTTPRequest request = null;
private final MonitoringContext context;
StreamConsumerThread(HTTPResponseHandler handler, HTTPResponseHeader header, InputStream body, HTTPRequest request, MonitoringContext context) {
this.handler = handler;
this.header = header;
this.body = body;
this.request = request;
this.context = context;
}
public void run() {
if (handler != null) {
try {
handler.handle(header, body, request, simpleHTTPClient.getTCPClient().getConnection().getProtocolData(), context);
} catch (IOException e) {
try {
int n = eat(body);
if (n > 0) {
Log.warn("The registered handler has not consumed the HTTP body from the response because of an exception. Eating " + n + " bytes. Exception was: " + e.getMessage());
}
} catch (IOException e1) {
Log.error("Could not consume omitted bytes from HTTP response. " + e1.getMessage());
}
}
try {
int n = eat(body);
if (n > 0) {
Log.warn("The registered handler has not consumed the HTTP body from the response. Eating " + n + " bytes.");
}
} catch (IOException e) {
Log.error("Could not consume omitted bytes from HTTP response. " + e.getMessage());
}
} else {
/*
* We MUST eat the omitted bytes. Why? Because we started a
* request and nobody wants the response!? Thats should not
* happen!
*/
try {
int n = eat(body);
if (n > 0) {
Log.warn("No registered handler was found to consume the HTTP body from the response. Eating " + n + " bytes.");
header.toStream(System.err);
}
} catch (IOException e1) {
Log.error("Could not consume omitted bytes from HTTP response. " + e1.getMessage());
}
}
MonitorStreamFactory monFac = DPWSFramework.getMonitorStreamFactory();
if (monFac != null) {
monFac.resetMonitoringContextIn(context.getProtocolData());
}
}
}
/**
* This thread allows exception handling without blocking the HTTP request
* queue.
*/
private class ExceptionNotification implements Runnable {
private HTTPRequest request = null;
private Exception e = null;
private boolean response = false;
private ProtocolData pd = null;
ExceptionNotification(ProtocolData pd, HTTPRequest request, Exception e, boolean response) {
this.request = request;
this.e = e;
this.response = response;
this.pd = pd;
}
public void run() {
if (request != null && e != null) {
if (response) {
request.responseReceiveFailed(e, pd);
} else {
request.requestSendFailed(e, pd);
}
}
}
public void start() {
DPWSFramework.getThreadPool().execute(this);
}
}
}