In this article, I give some example code on how to implement a webserver in an Android app, a functionality that can be handy if you want to give your app a web interface that can be accessed from a laptop or computer, which could be useful to enable quicker data entry amongst other things.
I also show some example code on how to parse a multipart message, in case you want the client to be able to sent form data/upload files to the webserver.

WebServer class

Everything that we are going to use is already present in the Android API. We are not going to need any additional libs.

A pragmatic implementation looks like this:

package com.integratingstuff.android.webserver;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

import org.apache.http.HttpException;
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.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
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 org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;

import android.content.Context;

public class WebServer {

	public static boolean RUNNING = false;
	public static int serverPort = 8080;

	private static final String ALL_PATTERN = "*";
	private static final String EXCEL_PATTERN = "/*.xls";
	private static final String HOME_PATTERN = "/home.html";

	private Context context = null;

	private BasicHttpProcessor httpproc = null;
	private BasicHttpContext httpContext = null;
	private HttpService httpService = null;
	private HttpRequestHandlerRegistry registry = null;

	public WebServer(Context context) {
		this.setContext(context);

		httpproc = new BasicHttpProcessor();
		httpContext = new BasicHttpContext();

		httpproc.addInterceptor(new ResponseDate());
		httpproc.addInterceptor(new ResponseServer());
		httpproc.addInterceptor(new ResponseContent());
		httpproc.addInterceptor(new ResponseConnControl());

		httpService = new HttpService(httpproc,
		    new DefaultConnectionReuseStrategy(), new DefaultHttpResponseFactory());

		registry = new HttpRequestHandlerRegistry();

		//registry.register(HOME_PATTERN, new HomeCommandHandler(context));

		httpService.setHandlerResolver(registry);
	}

	private ServerSocket serverSocket;

	public void runServer() {
		try {
			serverSocket = new ServerSocket(serverPort);

			serverSocket.setReuseAddress(true);

			while (RUNNING) {
				try {
					final Socket socket = serverSocket.accept();

					DefaultHttpServerConnection serverConnection = new DefaultHttpServerConnection();

					serverConnection.bind(socket, new BasicHttpParams());

					httpService.handleRequest(serverConnection, httpContext);

					serverConnection.shutdown();
				} catch (IOException e) {
					e.printStackTrace();
				} catch (HttpException e) {
					e.printStackTrace();
				}
			}

			serverSocket.close();
		} catch (SocketException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		RUNNING = false;
	}

	public synchronized void startServer() {
		RUNNING = true;
		runServer();
	}

	public synchronized void stopServer() {
		RUNNING = false;
		if (serverSocket != null) {
			try {
				serverSocket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public void setContext(Context context) {
		this.context = context;
	}

	public Context getContext() {
		return context;
	}
}

In the WebServer contructor we are initializing an HttpService. We are using all the default implementations here. The interesting part is the construction of the HttpRequestHandlerRegistry. As we will see, we will add a custom requesthandler to this registry which shall build the response for a particular request.

In the runServer method we actually start the server by initiating a low level ServerSocket and make it accept connections. We then continiously bind the socket to our webserver implementation and tell it to handle the request.

The startServer and stopServer methods are the methods actually used to start and stop the server.

In Android apps, a server like this should be run as an Android Service. So, we are implementing the following WebServerService:

package com.integratingstuff.android.webserver;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class WebServerService extends Service {

	private WebServer server = null;

	@Override
	public void onCreate() {
		Log.i("HTTPSERVICE", "Creating and starting httpService");
		super.onCreate();
		server = new WebServer(this);
		server.startServer();
	}

	@Override
	public void onDestroy() {
		Log.i("HTTPSERVICE", "Destroying httpService");
		server.stopServer();
		super.onDestroy();
	}

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}
}

Which we can start from the Android application like this:

Intent webServerService = new Intent(context, WebServerService.class);
context.startService(webServerService);

HttpRequestHandler

Our WebServer is now running, but we did not register any requesthandlers yet. In our webserver, we commented the following line:

registry.register(HOME_PATTERN, new HomeCommandHandler(context));

Let’s uncomment it and implement the HomeCommandHandler:

package com.integratingstuff.android.webserver;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.entity.ContentProducer;
import org.apache.http.entity.EntityTemplate;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;

import android.content.Context;

public class HomeCommandHandler implements HttpRequestHandler {
	private Context context = null;

	public HomeCommandHandler(Context context) {
		this.context = context;
	}

	@Override
	public void handle(HttpRequest request, HttpResponse response,
	    HttpContext httpContext) throws HttpException, IOException {
		HttpEntity entity = new EntityTemplate(new ContentProducer() {
			public void writeTo(final OutputStream outstream) throws IOException {
				OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8");
				String resp = "<html><head></head><body><h1>Home<h1><p>This is the homepage.</p></body></html>";

				writer.write(resp);
				writer.flush();
			}
		});
		response.setHeader("Content-Type", "text/html");
		response.setEntity(entity);
	}

	public Context getContext() {
		return context;
	}
}

We are mapping the pattern home.html to this requesthandler. This handler will send some basic html back as its response.

If you now surf to http://<ip-of-device>:8080/home.html, you should see the homepage with the “This is the homepage” message.

So now, our webserver is up and running and responding to home.html requests. You can easily add more requesthandlers to the registry, that match specific patterns or wildcard * patterns.

MultipartParser

The above can be easily found elsewhere, so to give this article some added value, I will quickly indicate below how to parse form date/get the contents of files contained in the request.

Code

We will need to parse a multipart request manually. I am just going to give the code snippets and I am not going into the parsing intrinsics, but basically, the http protocol defines boundary delimiters for multipart messages. What we are doing with the following MultipartParser is read the entire sent form into a Multivalued map(a map of which the value is a list of a certain type of object, so every key is mapped to a list of 1 or more values). Note that the MultipartParser actually uses a CaseInsensitiveMultivaluedMap that wraps a regular MultivaluedMap, because keys like “name” and “NAME” map to the same formproperty.

MultipartParser

package com.integratingstuff.android.webserver;

import java.io.IOException;
import java.io.InputStream;

public class MultipartParser {

	public final static String SEP = "\n";

	private InputStream is;
	private byte[] boundaryBA;
	static private byte[] boundaryDelimiterBA = "--".getBytes();

	private MultivaluedMap partHeaders;
	private PartInputStream partIS;

	private static int BOUNDARY_TYPE_START = 0;
	private static int BOUNDARY_TYPE_END = 1;
	// private static int BOUNDARY_TYPE_INVALID = 2;

	private byte[] buff;
	// the next byte to return
	private int buffIdx = 0;
	// The number of bytes that were set on the buffer (red from is);
	private int buffSize = 0;
	// the position of the next boundary ("--boundary"), -1 if does not exist in
	// this buffer
	private int boundryIdx = -1;
	// The index of the byte that is suspected of been the boundary
	private int saveIdx = 0;

	// This is a temp array that is used to read stuff into it.
	private byte[] temp = new byte[1024];

	public MultipartParser(InputStream is, String boundary) {
		this.is = is;
		boundaryBA = ("--" + boundary).getBytes();
		// make sure to allocate a buffer that is at least double then the
		// boundary length
		int buffLength = Math.max(8192, boundaryBA.length * 2);
		buff = new byte[buffLength];

	}

	private void shiftBuff() {
		System.arraycopy(buff, buffIdx, buff, 0, buffSize - buffIdx);

		buffSize -= buffIdx;
		saveIdx -= buffIdx;
		if (saveIdx < 0) 			saveIdx = 0; 		/* 		 * throw new RuntimeException("This should never happend, we found a bug."); 		 */ 		boundryIdx = Math.max(-1, boundryIdx - buffIdx); 		buffIdx = 0;  		// for debug purposes 		// Arrays.fill(buff, buffIdx, buff.length-1, (byte)0);  	}  	public boolean nextPart() throws IOException { 		// if this is the first next just get rid of the PREAMBLE 		if (partIS == null) { 			partIS = new PartInputStream(); 		} 		// clear the part/preamble bytes that were not read 		digestPartStream(); 		if (digestBoundary() == BOUNDARY_TYPE_END) 			return false; 		partIS.setState(PartInputStream.STATE_NOT_ACTIVE); 		partIS = new PartInputStream(); 		partHeaders = parseHeaders(); 		return partHeaders != null; 	}  	public InputStream getPartBodyStream() { 		return partIS; 	}  	public MultivaluedMap getPartHeaders() {
		return partHeaders;
	}

	// read till end of stream (next boundary)
	private void digestPartStream() throws IOException {
		while (partIS.read(temp) != -1) {
		}
	}

	private boolean compareByte(byte[] a, int aOffset, byte[] b, int bOffset,
	    int length) {
		for (int i = 0; i < length; i++) { 			if (a[aOffset + i] != b[bOffset + i]) 				return false; 		} 		return true; 	}  	private int digestBoundary() throws IOException { 		// it might be that there is a new line before the boundary 		digestNewLine();  		// promote pointers to the end of the boundary 		buffIdx += boundaryBA.length; 		saveIdx += boundaryBA.length; // DO NOT DELETE  		// check if this is an end boundary 		int unredBytes = verifyByteReadyForRead(2); 		if (unredBytes >= 2) {
			if (compareByte(buff, buffIdx, boundaryDelimiterBA, 0,
			    boundaryDelimiterBA.length))
				return BOUNDARY_TYPE_END;
		}
		// OK
		digestNewLine();
		boundryIdx = -1;
		findBounderyIfNeeded();
		return BOUNDARY_TYPE_START;
	}

	private void findBounderyIfNeeded() {
		if (boundryIdx == -1) {
			boundryIdx = indexOf(buff, saveIdx, buffSize, boundaryBA);
			if (boundryIdx != -1) {
				int nlSize = 0;
				if (boundryIdx > 1) {
					if (buff[boundryIdx - 2] == '\r' && buff[boundryIdx - 1] == '\n')
						nlSize = 2;
					else
						nlSize = 1;
				}
				if (boundryIdx == 1) {
					nlSize = 1;
				}

				saveIdx = boundryIdx - nlSize;
			} else {
				// the boundary was not found, but we can promote the save till
				// boundary size + NL size
				saveIdx = Math.max(saveIdx, buffSize - (boundaryBA.length + 2));
			}
		}
	}

	private int verifyByteReadyForRead(int required) throws IOException {
		int unreadBytes = buffSize - buffIdx - 1;
		if (unreadBytes < required) { 			fetch(required - unreadBytes); 			unreadBytes = buffSize - buffIdx; 		} 		return unreadBytes; 	}  	private int fetch(int minmum) throws IOException { 		int res = 0; 		int max2featch = buff.length - buffSize;  		if (max2featch < minmum) { 			shiftBuff(); 			max2featch = buff.length - buffSize; 		}  		while (res < minmum && max2featch > 0) {
			max2featch = buff.length - buffSize;

			int read = is.read(buff, buffSize, max2featch);
			if (read == -1) {
				if (res == 0)
					return -1;
				else
					break;
			}
			res += read;
			buffSize += read;
		}
		findBounderyIfNeeded();
		return res;

	}

	private void digestNewLine() throws IOException {
		// make sure we have enough byte to read
		int unreadBytes = verifyByteReadyForRead(2);
		int size = 0;

		if (unreadBytes >= 2 && buff[buffIdx] == '\r' && buff[buffIdx + 1] == '\n')
			size = 2;
		else if (buff[buffIdx] == '\r')
			size = 1;
		else if (buff[buffIdx] == '\n')
			size = 1;
		buffIdx += size;
		if (saveIdx < buffIdx) 			saveIdx = buffIdx; 	}  	private int indexOf(byte[] ba, int start, int end, byte[] what) { 		for (int i = start; i < end - what.length + 1; i++) { 			// only if the first byte equals do the compare (to improve 			// performance) 			if (ba[i] == what[0]) 				if (compareByte(ba, i, what, 0, what.length)) 					return i; 		} 		return -1; 	}  	private MultivaluedMap parseHeaders() throws IOException {

		MultivaluedMap headers = new CaseInsensitiveMultivaluedMap();

		String line;
		do {
			line = readLine();
			if (line == null || line.equals(""))
				break;
			int semIdx = line.indexOf(":");
			headers.add(line.substring(0, semIdx).trim(), line.substring(semIdx + 1)
			    .trim());

		} while (true);
		if (saveIdx < buffIdx) 			saveIdx = buffIdx; 		return headers; 	}  	private String readLine() throws IOException {  		int lineIdx = 0; 		int breakeSize = 0; 		while (lineIdx <= verifyByteReadyForRead(lineIdx)) { 			if (buff[buffIdx + lineIdx] == '\n') { 				breakeSize = 1; 				break; 			} 			if (buff[buffIdx + lineIdx] == '\r') { 				if ((verifyByteReadyForRead(lineIdx + 1) >= lineIdx + 1)
				    && (buff[buffIdx + lineIdx + 1] == '\n')) {
					breakeSize = 2;
					break;
				} else {
					breakeSize = 1;
					break;
				}
			}
			lineIdx++;
		}

		// got to the end of input without NL
		if (lineIdx == 0) {
			buffIdx += breakeSize;
			return null;
		}

		String hdr = new String(buff, buffIdx, lineIdx);
		buffIdx += lineIdx + breakeSize;
		return hdr;
	}

	public class PartInputStream extends InputStream {
		// The state of the part Stream
		// 0 active
		// 1 not active (the Parser already moved to the next part.)
		private int state = 0;
		public final static int STATE_ACTIVE = 0;
		public final static int STATE_NOT_ACTIVE = 1;

		public void setState(int status) {
			this.state = status;

		}

		@Override
		public int read(byte[] b, int off, int len) throws IOException {
			if (state == STATE_NOT_ACTIVE) {
				throw new IOException(
				    "Stream allready closed: the PartInputStream is not accesable after moving to the next part");
			}
			int available = verifyNumOfByteToReadB4Boundary(len);
			if (available < 1) { 				return available                         } 			int size2copy = Math.min(len, available); 			System.arraycopy(buff, buffIdx, b, off, size2copy); 			buffIdx += size2copy; 			return size2copy; 		}  		@Override 		public int read() throws IOException { 			if (state == STATE_NOT_ACTIVE) { 				throw new IOException( 				    "Stream allready closed: the PartInputStream is not accesable after moving to the next part"); 			} 			int i = verifyNumOfByteToReadB4Boundary(1); 			if (i < 1) 				return -1; 			// make sure that the return value is 0 - 255 			int res = buff[buffIdx] & 0xff; 			if (res < 0) { 				int t = 0; 				t++; 			} 			buffIdx++; 			return res;  		}  		private int verifyNumOfByteToReadB4Boundary(int minmum) throws IOException { 			int availabe = saveIdx - buffIdx; 			if (availabe >= minmum)
				return availabe;
			//
			if (saveIdx <= boundryIdx) { 				if (availabe == 0) 					return -1; 				return availabe; 			} 			int fetched = fetch(minmum - availabe); 			availabe = saveIdx - buffIdx; 			if (availabe == 0 && fetched == -1) 				return -1;  			return availabe;  		}  		@Override 		public int available() { 			return saveIdx - buffIdx; 		} 	} }