package ian.webserver; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.RandomAccessFile; import java.io.UnsupportedEncodingException; import java.net.ServerSocket; import java.net.Socket; import java.net.URLDecoder; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.regex.Pattern; public class HttpServer { Executor pool = Executors.newCachedThreadPool(); public static class FileReadReference implements IReadReference { File file; String mimeType; int baseLength; public FileReadReference(final File file) { baseLength = 0; this.file = file; } public FileReadReference(final File base, final File file) { baseLength = base.getAbsolutePath().length(); this.file = file; } public boolean exists() { return file.exists(); } public ReadableByteChannel getByteChannel() throws IOException { return new RandomAccessFile(file, "r").getChannel(); } public long getCreated() { return file.lastModified(); } public long getLength() { return file.length(); } public String getType() { if (mimeType == null) { mimeType = MimeMap.getContentTypeFor(file.getName()); } return mimeType; } } public static interface IReadReference { boolean exists(); ReadableByteChannel getByteChannel() throws Exception; long getCreated(); long getLength(); String getType(); } public static interface ITask { void execute(String path, RequestResponse rr) throws Exception; } public static class MimeMap { public static HashMap defaultMap = new HashMap(); static { defaultMap.put("txt", "text/plain; charset=utf-8"); defaultMap.put("css", "text/css; charset=utf-8"); defaultMap.put("html", "text/html; charset=utf-8"); defaultMap.put("htm", "text/html; charset=utf-8"); defaultMap.put("gif", "image/gif"); defaultMap.put("jpg", "image/jpeg"); defaultMap.put("jpe", "image/jpeg"); defaultMap.put("jpeg", "image/jpeg"); defaultMap.put("bmp", "image/bmp"); defaultMap.put("ico", "image/bmp"); defaultMap.put("png", "image/png"); defaultMap.put("java", "text/plain; charset=utf-8"); defaultMap.put("body", "text/html; charset=utf-8"); defaultMap.put("rtx", "text/richtext"); defaultMap.put("tsv", "text/tab-separated-values"); defaultMap.put("etx", "text/x-setext"); defaultMap.put("ps", "application/x-postscript"); defaultMap.put("class", "application/java"); defaultMap.put("csh", "application/x-csh"); defaultMap.put("sh", "application/x-sh"); defaultMap.put("tcl", "application/x-tcl"); defaultMap.put("tex", "application/x-tex"); defaultMap.put("texinfo", "application/x-texinfo"); defaultMap.put("texi", "application/x-texinfo"); defaultMap.put("t", "application/x-troff"); defaultMap.put("tr", "application/x-troff"); defaultMap.put("roff", "application/x-troff"); defaultMap.put("man", "application/x-troff-man"); defaultMap.put("me", "application/x-troff-me"); defaultMap.put("ms", "application/x-wais-source"); defaultMap.put("src", "application/x-wais-source"); defaultMap.put("zip", "application/zip"); defaultMap.put("bcpio", "application/x-bcpio"); defaultMap.put("cpio", "application/x-cpio"); defaultMap.put("gtar", "application/x-gtar"); defaultMap.put("shar", "application/x-shar"); defaultMap.put("sv4cpio", "application/x-sv4cpio"); defaultMap.put("sv4crc", "application/x-sv4crc"); defaultMap.put("tar", "application/x-tar"); defaultMap.put("ustar", "application/x-ustar"); defaultMap.put("dvi", "application/x-dvi"); defaultMap.put("hdf", "application/x-hdf"); defaultMap.put("latex", "application/x-latex"); defaultMap.put("bin", "application/octet-stream"); defaultMap.put("oda", "application/oda"); defaultMap.put("pdf", "application/pdf"); defaultMap.put("ps", "application/postscript"); defaultMap.put("eps", "application/postscript"); defaultMap.put("ai", "application/postscript"); defaultMap.put("rtf", "application/rtf"); defaultMap.put("nc", "application/x-netcdf"); defaultMap.put("cdf", "application/x-netcdf"); defaultMap.put("cer", "application/x-x509-ca-cert"); defaultMap.put("exe", "application/octet-stream"); defaultMap.put("gz", "application/x-gzip"); defaultMap.put("Z", "application/x-compress"); defaultMap.put("z", "application/x-compress"); defaultMap.put("hqx", "application/mac-binhex40"); defaultMap.put("mif", "application/x-mif"); defaultMap.put("ief", "image/ief"); defaultMap.put("tiff", "image/tiff"); defaultMap.put("tif", "image/tiff"); defaultMap.put("ras", "image/x-cmu-raster"); defaultMap.put("pnm", "image/x-portable-anymap"); defaultMap.put("pbm", "image/x-portable-bitmap"); defaultMap.put("pgm", "image/x-portable-graymap"); defaultMap.put("ppm", "image/x-portable-pixmap"); defaultMap.put("rgb", "image/x-rgb"); defaultMap.put("xbm", "image/x-xbitmap"); defaultMap.put("xpm", "image/x-xpixmap"); defaultMap.put("xwd", "image/x-xwindowdump"); defaultMap.put("au", "audio/basic"); defaultMap.put("snd", "audio/basic"); defaultMap.put("aif", "audio/x-aiff"); defaultMap.put("aiff", "audio/x-aiff"); defaultMap.put("aifc", "audio/x-aiff"); defaultMap.put("wav", "audio/x-wav"); defaultMap.put("mpeg", "video/mpeg"); defaultMap.put("mpg", "video/mpeg"); defaultMap.put("mpe", "video/mpeg"); defaultMap.put("qt", "video/quicktime"); defaultMap.put("mov", "video/quicktime"); defaultMap.put("avi", "video/x-msvideo"); defaultMap.put("movie", "video/x-sgi-movie"); defaultMap.put("avx", "video/x-rad-screenplay"); defaultMap.put("wrl", "x-world/x-vrml"); defaultMap.put("mpv2", "video/mpeg2"); /* Add XML related MIMEs */ defaultMap.put("xml", "text/xml; charset=utf-8"); defaultMap.put("xsl", "text/xml; charset=utf-8"); defaultMap.put("svg", "image/svg+xml"); defaultMap.put("svgz", "image/svg+xml"); defaultMap.put("wbmp", "image/vnd.wap.wbmp"); defaultMap.put("wml", "text/vnd.wap.wml"); defaultMap.put("wmlc", "application/vnd.wap.wmlc"); defaultMap.put("wmls", "text/vnd.wap.wmlscript"); defaultMap.put("wmlscriptc", "application/vnd.wap.wmlscriptc"); defaultMap.put("jsp", "text/html; charset=utf-8"); defaultMap.put("php", "text/html; charset=utf-8"); defaultMap.put("do", "text/html; charset=utf-8"); defaultMap.put("asp", "text/html; charset=utf-8"); defaultMap.put("vm", "text/html; charset=utf-8"); defaultMap.put("cfm", "text/html; charset=utf-8"); } public static String getContentType(final String extn) { String type = defaultMap.get(extn.toLowerCase()); if (type == null) { type = "application/" + extn; } return type; } public static String getContentTypeFor(final String fileName) { final String extn = getExtension(fileName); if (extn != null) { return getContentType(extn); } return "unknown"; } public static String getExtension(final String fileName) { final int length = fileName.length(); int newEnd = fileName.lastIndexOf('#'); if (newEnd == -1) { newEnd = length; } final int i = fileName.lastIndexOf('.', newEnd); if (i != -1) { return fileName.substring(i + 1, newEnd); } return "unknown"; } } public static class RequestResponse { public Map prop; public IReadReference reference; public RequestResponse(Map prop) { this.prop = prop; } } public static class StringReadReference implements IReadReference { private static final long serialVersionUID = -542476297604486521L; byte content[]; String type; long created = System.currentTimeMillis(); public StringReadReference(final String str, final String mimeType) { type = mimeType; if (str != null) { try { content = str.getBytes("utf-8"); } catch (final UnsupportedEncodingException e) { e.printStackTrace(); } } } @Override public boolean exists() { return content != null; } public ReadableByteChannel getByteChannel() throws IOException { return Channels.newChannel(new ByteArrayInputStream(content)); } public long getCreated() { return created; } public long getLength() { return content.length; } public String getType() { return type; } } public static void copy(final ReadableByteChannel from, final WritableByteChannel to) { final ByteBuffer buf = ByteBuffer.allocate(8192); int read; try { read = from.read(buf); while (read > -1) { buf.flip(); to.write(buf); buf.flip(); read = from.read(buf); } } catch (final IOException e) { e.printStackTrace(); try { to.close(); } catch (final IOException e1) { e1.printStackTrace(); } } finally { try { from.close(); } catch (final IOException e) { e.printStackTrace(); } } } final Map map = new HashMap(); final ArrayList arr = new ArrayList(); ServerSocket ss; Thread main; public HttpServer(final int port) throws Exception { ss = new ServerSocket(port); main = new Thread(){ @Override public void run() { while (!isInterrupted()) { try { final Socket s = ss.accept(); pool.execute(new Runnable(){ @Override public void run() { handle(s); } }); } catch (final IOException e) { e.printStackTrace(); } } } }; main.start(); } @Override protected void finalize() throws Throwable { if (main.isAlive()) { free(); } } public void free() { try { main.interrupt(); ss.close(); } catch (final Exception e) { e.printStackTrace(); } } public void handle(Socket s) { InputStream in = null; OutputStream out = null; try { s.setSoTimeout(30000); in = s.getInputStream(); final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String line = null; String method = "get"; String url = "/"; while (true) { final Map prop = new HashMap(); boolean first = true; while ((line = reader.readLine().toLowerCase()).length() > 0) { if (first) { first = false; final StringTokenizer tokenizer = new StringTokenizer(line, " "); method = tokenizer.nextToken().toLowerCase(); url = URLDecoder.decode(tokenizer.nextToken(), "ISO8859-1"); prop.put("url", url); prop.put("method", method); final int indexOfQuestionMark = url.indexOf("?"); if (indexOfQuestionMark >= 0) { final String parametersToParse = url.substring(indexOfQuestionMark + 1); url = url.substring(0, indexOfQuestionMark); final StringTokenizer parameterTokenizer = new StringTokenizer( parametersToParse, "&"); while (parameterTokenizer.hasMoreTokens()) { final String[] keyAndValue = parameterTokenizer.nextToken().split("="); final String key = URLDecoder.decode(keyAndValue[0], "ISO8859-1"); final String value = URLDecoder.decode(keyAndValue[1], "ISO8859-1"); prop.put(key, value); } } } } final RequestResponse rr = new RequestResponse(prop); if (rr.reference == null) { rr.reference = new StringReadReference("Task did not return any content", "text/plain"); } try { processEvent(url, rr); } catch (Exception e) { rr.reference = new StringReadReference(e.getMessage(), "text/plain"); } if (rr.reference == null || !rr.reference.exists()) { rr.reference = new StringReadReference("No data found at that path", "text/plain"); } final StringBuffer headers = new StringBuffer(); headers.append("HTTP/1.1 200 OK\r\n"); headers.append("Accept-Encoding: \r\n"); headers.append("Content-Type: ").append(rr.reference.getType()).append("\r\n"); headers.append("Content-Length: ").append(rr.reference.getLength()).append("\r\n"); headers.append("\r\n"); out = s.getOutputStream(); out.write(headers.toString().getBytes("UTF-8")); if (!method.equals("head")) { copy(rr.reference.getByteChannel(), Channels.newChannel(out)); } } } catch (final Exception e) { System.out.println(e.getMessage()); } finally { if (in != null) { try { in.close(); } catch (final IOException e) {} } if (out != null) { try { out.close(); } catch (final IOException e) {} } } } public void link(Pattern p, ITask task) { map.put(p.pattern(), task); arr.add(p); } public void unlink(Pattern p, ITask task) { map.remove(p.pattern()); for (int i = 0; i < arr.size(); ++i) { if (arr.get(i).pattern().equals(p.pattern())) { arr.remove(i); } } } public void processEvent(String path, RequestResponse rr) { ITask task = null; for (Pattern x : arr) { if (x.matcher(path).matches()) { task = map.get(x.pattern()); break; } } if (task == null) { System.out.println("No tasks to handle " + path); return; } try { task.execute(path, rr); } catch (final Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws Exception { final HttpServer web = new HttpServer(80); class ResourceHandler implements ITask { private final File dir; int length; public ResourceHandler(final File dir) { this.dir = dir; length = dir.getAbsolutePath().length(); } @Override public void execute(String path, RequestResponse rr) throws Exception { File f = new File(dir, path); if (!f.exists()) { rr.reference = new StringReadReference("No file found here ", "text/plain"); } else if (f.isDirectory()) { StringBuffer buf = new StringBuffer(); buf.append("
    "); File list[] = f.listFiles(); if (list == null) { buf.append("
  • Empty
  • "); } else { for (File x : list) { buf.append("
  • ") .append(x.getName()).append("
  • \n"); } } buf.append("
"); rr.reference = new StringReadReference(buf.toString(), "text/html"); } else { rr.reference = new FileReadReference(new File(dir, path)); } } } web.link(Pattern.compile(".*"), new ResourceHandler(new File(System.getProperty("user.home")))); } }