There are multiple use-cases for an embedded HTTP Server in an Android app, not the least of which is a hi-fidelity testing fake. Here's an example of how we adopted ServerSocket for use in sending downloaded, encrypted video streams to Chromecast by simply passing an HTTP Url stream.

First, we needed some simple HTTP classes to represent Request and Response objects:

package oreilly.queue.simplehttp;

import java.util.HashMap;
import java.util.Map;

/**
 * @author mdunn@oreilly.com
 */
public abstract class Message {

  public static final String DEFAULT_PROTOCOL = "HTTP";
  public static final String DEFAULT_VERSION = "1.1";

  public static final String LINE_TERMINATOR = "\r\n";

  private String mUri;
  private Map mHeaders = new HashMap<>();
  private String mProtocol = DEFAULT_PROTOCOL;
  private String mVersion = DEFAULT_VERSION;

  public abstract String getInitialLine();

  public String getUri() {
    return mUri;
  }

  public void setUri(String uri) {
    mUri = uri;
  }

  public void setHeader(String key, String value) {
    mHeaders.put(key, value);
  }

  public String getHeader(String key) {
    return mHeaders.get(key);
  }

  public String getProtocol() {
    return mProtocol;
  }

  public void setProtocol(String protocol) {
    mProtocol = protocol;
  }

  public String getVersion() {
    return mVersion;
  }

  public void setVersion(String version) {
    mVersion = version;
  }

  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append(getInitialLine());
    builder.append(LINE_TERMINATOR);
    for (Map.Entry entry : mHeaders.entrySet()) {
      builder.append(entry.getKey());
      builder.append(": ");
      builder.append(entry.getValue());
      builder.append(LINE_TERMINATOR);
    }
    builder.append(LINE_TERMINATOR);
    return builder.toString();
  }

  public byte[] getBytes() {
    return toString().getBytes();
  }

}


package oreilly.queue.simplehttp;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

import oreilly.queue.logging.QueueLogger;

/**
 * @author mdunn@oreilly.com
 */
public class Request extends Message {

  // don't use FileUtils.stringFromStream here, because that looks for EOF, http message uses a blank line to separate head from body
  public static Request from(InputStream stream) {
    Request request = new Request();
    InputStreamReader reader = new InputStreamReader(stream);
    BufferedReader bufferedReader = new BufferedReader(reader);
    String line;
    boolean hasReadInitialLine = false;
    try {
      while (true) {
        line = bufferedReader.readLine();
        if ("".equals(line) || line == null) {
          // just get out - we don't care about the body
          break;
        }
        if (hasReadInitialLine) {
          // if we've read the first line, and we haven't hit a blank line, that means we're reading headers
          int index = line.indexOf(":");
          String key = line.substring(0, index).toLowerCase();
          String value = line.substring(index + 1).replaceFirst("^\\s+", "");  // can be any number of spaces after the colon
          request.setHeader(key, value);
          QueueLogger.d("1276", "setting header " + key + " to " + value);
        } else {
          // GET /path/to/file/index.html HTTP/1.0
          String[] parts = line.split(" ");
          request.setMethod(parts[0]);
          String protocolAndVersion = parts[parts.length - 1];
          // we're looking for something like HTTP/1.0 here
          // it might have been omitted - do a light validation
          // to exclude a URI just check if it doesn't start with a /, like "/api/v1/blah"
          if (!protocolAndVersion.startsWith("/")) {
            String[] protocolAndVersionArray = protocolAndVersion.split("/");
            if (protocolAndVersionArray.length == 2) {
              request.setProtocol(protocolAndVersionArray[0]);
              request.setVersion(protocolAndVersionArray[1]);
            }
          }
          request.setUri(line.substring(parts[0].length() + 1, line.length() - protocolAndVersion.length() - 1));
          hasReadInitialLine = true;
          QueueLogger.d("1276", "setting initial line to " + request.getInitialLine());
        }
      }
    } catch (Exception e) {
      QueueLogger.d("1276", "Request.from error: " + e.getMessage());
      e.printStackTrace();
    }
    // do NOT close the streams here
    return request;
  }

  private String mMethod;

  // GET /path/to/file/index.html HTTP/1.0
  @Override
  public String getInitialLine() {
    return mMethod + " " + getUri() + " " + getProtocol() + "/" + getVersion();
  }

  public String getMethod() {
    return mMethod;
  }

  public void setMethod(String method) {
    mMethod = method;
  }

}


package oreilly.queue.simplehttp;

import java.io.InputStream;

/**
 * @author mdunn@oreilly.com
 */
public class Response extends Message {

  public static final String STATUS_OK = "200 OK";
  public static final String STATUS_PARTIAL_CONTENT = "206 Partial Content";

  private String mStatus = STATUS_OK;
  private InputStream mBody;

  // HTTP/1.0 200 OK
  @Override
  public String getInitialLine() {
    return getProtocol() + "/" + getVersion() + " " + mStatus;
  }

  public String getStatus() {
    return mStatus;
  }

  public void setStatus(String status) {
    mStatus = status;
  }

  public InputStream getBody() {
    return mBody;
  }

  public void setBody(InputStream body) {
    mBody = body;
  }

}

Then we'll use a ServerSocket to distribute out video:

package oreilly.queue.video;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import oreilly.queue.logging.QueueLogger;
import oreilly.queue.simplehttp.Request;
import oreilly.queue.simplehttp.Response;

/**
 * @author mdunn@oreilly.com
 */
public class SimpleVideoServer extends Thread {

  public static final int SERVER_PORT = 8888;

  private static final int BUFFER_SIZE = 64 * 1024;  // 64kb
  private static final int TIMEOUT = 10000;  // 10 seconds

  private static final String RANGE_PATTERN = "^(\\s+)?\\w+=(\\d+)-";

  public static String getIpAddress() {
    try {
      Enumeration enumNetworkInterfaces = NetworkInterface.getNetworkInterfaces();
      while (enumNetworkInterfaces.hasMoreElements()) {
        NetworkInterface networkInterface = enumNetworkInterfaces.nextElement();
        Enumeration enumInetAddress = networkInterface.getInetAddresses();
        while (enumInetAddress.hasMoreElements()) {
          InetAddress inetAddress = enumInetAddress.nextElement();
          if (inetAddress.isSiteLocalAddress() && inetAddress instanceof Inet4Address) {
            return inetAddress.getHostAddress();
          }
        }
      }
    } catch (SocketException e) {
      e.printStackTrace();
    }
    return null;
  }

  interface StreamProvider {
    InputStream getInputStream(Request request) throws Exception;
  }

  private ServerSocket mServerSocket;
  private Socket mClient;
  private InputStream mInputStream;
  private StreamProvider mStreamProvider;
  private boolean mIsRunning;
  private long mStartByte;
  private String mUrl;
  private String mIp;

  public SimpleVideoServer(StreamProvider streamProvider) {
    mStreamProvider = streamProvider;
    try {
      mIp = getIpAddress();
      InetAddress address = InetAddress.getByName(mIp);
      mServerSocket = new ServerSocket(SERVER_PORT, 0, address);
      mServerSocket.setSoTimeout(TIMEOUT);
    } catch (Exception e) {
      Log.d("1276",  "exception initializing server: " + e.getMessage());
    }
  }

  public String getUrl() {
    if (mUrl == null) {
      mUrl = String.format(Locale.US, "http://%s:%d/", mIp, SERVER_PORT);
    }
    return mUrl;
  }

  public void shutdown() {
    mIsRunning = false;
    try {
      join(5000);
    } catch (InterruptedException e) {
      Log.d("1276",  "failed to join thread: " + e.getMessage());
    }
  }

  @Override
  public void run() {
    mIsRunning = true;
    while (mIsRunning) {
      if (mServerSocket != null) {
        try {
          mClient = mServerSocket.accept();
          serveResponseOnValidRequest();
        } catch (Exception e) {
          Log.d("1276", "Error connecting to client: " + e.getMessage());
        }
      }
    }
    QueueLogger.d("1276",  "shutting down");
  }

  private void serveResponseOnValidRequest() throws Exception {
    if (processAndValidateRequest()) {
      serveResponse();
    }
  }

  private boolean processAndValidateRequest() {
    if (mClient == null) {
      return false;
    }
    try {
      Request request = Request.from(mClient.getInputStream());
      setupStream(request);
      parseRange(request);
    } catch (Exception e) {
      return false;
    }
    return true;
  }

  private void serveResponse() throws Exception {
    Response response = getResponseFromClient();
    writeResponseToOutputStream(response);
  }

  private void setupStream(Request request) throws Exception {
    mInputStream = mStreamProvider.getInputStream(request);
  }

  private void parseRange(Request request) {
    // bytes=1234-5678
    String range = request.getHeader("range");
    QueueLogger.d("1276",  "range=" + range);
    if (range != null) {
      Matcher matcher = Pattern.compile(RANGE_PATTERN).matcher(range);
      matcher.find();
      if (matcher.groupCount() >= 2) {
        range = matcher.group(2);
      }
      Log.d("1276",  "after match, range=" + range);
      if (range != null) {
        mStartByte = Long.parseLong(range);
      }
    }
  }

  private Response getResponseFromClient() throws IOException {
    long fileSize = mInputStream.available();
    Response response = new Response();
    response.setHeader("Content-Type", "video/mp4");
    response.setHeader("Accept-Ranges", "bytes");
    response.setHeader("Connection", "Keep-Alive");
    response.setHeader("Content-Length", String.valueOf(fileSize - mStartByte));
    // if it's a seek we need range header
    if (mStartByte > 0) {
      response.setStatus(Response.STATUS_PARTIAL_CONTENT);
      response.setHeader("Content-Range", "bytes " + mStartByte + "-" + (fileSize - 1) + "/" + fileSize);
    } else {
      response.setStatus(Response.STATUS_OK);
    }
    return response;
  }

  private void writeResponseToOutputStream(Response response) throws IOException {
    if (!mIsRunning) {
      return;
    }
    OutputStream outputStream = mClient.getOutputStream();
    byte[] buffer = new byte[BUFFER_SIZE];
    if (!mIsRunning) {
      return;
    }
    try {
      outputStream = new BufferedOutputStream(outputStream, BUFFER_SIZE);
      if (!mIsRunning) {
        return;
      }
      // write the HTTP response initial line and headers
      outputStream.write(response.getBytes());
      if (!mIsRunning) {
        return;
      }
      // skip to the byte range requested
      mInputStream.skip(mStartByte);  // TODO: may need to return to "forceSkip" (in this POC, it's just "skip" overridden)
      // Loop as long as there's stuff to send and mClient has not closed
      while (mIsRunning && !mClient.isClosed()) {
        int bytesRead = mInputStream.read(buffer, 0, buffer.length);
        if (bytesRead == -1) {
          break;
        }
        outputStream.write(buffer, 0, bytesRead);
      }
    } catch (Exception e) {
      Log.d("1276",  "Exception thrown from streaming task: " + e.getMessage());
    } finally {
      try {
        if (outputStream != null) {
          outputStream.close();
        }
        if (mClient != null) {
          mClient.close();  // this should close the InputStream as well
        }
      } catch (IOException e) {
        QueueLogger.d("1276",  "IOException while cleaning up streaming task: " + e.getMessage());
      }
    }
  }

}