/*
* Copyright (C) 2008 Josh Guilfoyle <jasta@devtcg.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
package org.devtcg.five.util.streaming;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashSet;
import org.apache.http.HttpServerConnection;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.protocol.HttpRequestHandlerRegistry;
import org.apache.http.protocol.HttpService;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;
import android.os.Process;
import android.util.Log;
public abstract class LocalHttpServer extends Thread
{
public static final String TAG = "LocalHttpServer";
protected final HashSet<WorkerThread> mWorkers =
new HashSet<WorkerThread>();
protected ServerSocket mSocket;
protected HttpParams mParams;
private HttpRequestHandler mReqHandler;
public LocalHttpServer()
{
super("LocalHttpServer");
mParams = new BasicHttpParams();
mParams
.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000)
.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK,
false)
.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true);
setDaemon(true);
}
public LocalHttpServer(int port)
throws IOException
{
this();
bind(port);
}
public void bind(int port)
throws IOException
{
bind(new InetSocketAddress(InetAddress.getLocalHost(), port));
}
public void bind(InetSocketAddress addr)
throws IOException
{
mSocket = new ServerSocket();
mSocket.bind(addr);
Log.i(TAG, "Bound to port " + mSocket.getLocalPort());
}
public void setRequestHandler(HttpRequestHandler handler)
{
mReqHandler = handler;
}
public int getPort()
{
if (mSocket == null)
throw new IllegalStateException("Not bound.");
return mSocket.getLocalPort();
}
public void reset()
{
WorkerThread[] workersCopy;
synchronized(mWorkers) {
/* Copied because shutdown() will try to access mWorkers. */
workersCopy =
mWorkers.toArray(new WorkerThread[mWorkers.size()]);
}
for (WorkerThread t: workersCopy)
{
t.shutdown();
t.joinUninterruptibly();
}
assert mWorkers.isEmpty() == true;
}
public void shutdown()
{
reset();
interrupt();
try {
mSocket.close();
} catch (IOException e) {}
}
public void run()
{
if (mReqHandler == null)
throw new IllegalStateException("Request handler not set.");
if (mSocket == null)
throw new IllegalStateException("Not bound.");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (Thread.interrupted() == false)
{
try {
Socket sock = mSocket.accept();
DefaultHttpServerConnection conn =
new DefaultHttpServerConnection();
conn.bind(sock, mParams);
BasicHttpProcessor proc = new BasicHttpProcessor();
proc.addInterceptor(new ResponseContent());
proc.addInterceptor(new ResponseConnControl());
HttpRequestHandlerRegistry reg =
new HttpRequestHandlerRegistry();
reg.register("*", mReqHandler);
HttpService svc = new HttpService(proc,
new DefaultConnectionReuseStrategy(),
new DefaultHttpResponseFactory());
svc.setParams(mParams);
svc.setHandlerResolver(reg);
WorkerThread t;
synchronized(mWorkers) {
t = new WorkerThread(svc, conn);
mWorkers.add(t);
}
t.setDaemon(true);
t.start();
} catch (IOException e) {
Log.e(TAG, "I/O error initializing connection thread: " +
e.getMessage());
break;
}
}
}
private class WorkerThread extends Thread
{
private HttpService mService;
private HttpServerConnection mConn;
public WorkerThread(HttpService svc, HttpServerConnection conn)
{
super();
mService = svc;
mConn = conn;
}
public void run()
{
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
HttpContext ctx = new BasicHttpContext(null);
try {
while (isInterrupted() == false && mConn.isOpen())
mService.handleRequest(mConn, ctx);
} catch (Exception e) {
Log.e(TAG, "HTTP server disrupted: " + e.toString());
} finally {
if (Thread.interrupted() == false) {
try {
mConn.shutdown();
} catch (IOException e) {}
synchronized(mWorkers) {
mWorkers.remove(this);
}
}
}
}
public void shutdown()
{
interrupt();
try {
mConn.shutdown();
} catch (IOException e) {}
}
public void joinUninterruptibly()
{
while (true)
{
try {
join();
break;
} catch (InterruptedException e) {}
}
}
}
}