package com.dianping.util; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; public class EmbedHttpServer implements Runnable { private int port; private ServerSocket serverSocket; public EmbedHttpServer(int port) { this.port = port; } public void start() throws IOException { if (serverSocket == null) { serverSocket = new ServerSocket(port); new Thread(this, "embed-http-server").start(); } } public void stop() throws IOException { if (serverSocket != null) { serverSocket.close(); serverSocket = null; } } protected void handle(String method, String path, HashMap<String, String> headers, InputStream input, ResponseOutputStream response) throws Exception { } @Override public void run() { final ServerSocket ss = serverSocket; while (ss == serverSocket) { Socket conn = null; try { conn = ss.accept(); String method = null; String path = null; HashMap<String, String> headers = new HashMap<String, String>(); InputStream ins = conn.getInputStream(); StringBuilder sb = new StringBuilder(512); int l; while ((l = ins.read()) != -1) { if (l == '\n') { if (sb.length() > 0 && sb.charAt(sb.length() - 1) == '\r') sb.setLength(sb.length() - 1); if (sb.length() == 0) { // header end break; } else if (method == null) { int i = sb.indexOf(" "); method = sb.substring(0, i); int j = sb.lastIndexOf(" HTTP/"); path = sb.substring(i + 1, j).trim(); } else { int i = sb.indexOf(":"); String name = sb.substring(0, i).trim(); String val = sb.substring(i + 1).trim(); headers.put(name, val); } sb.setLength(0); } else { sb.append((char) l); } } int contentLength = 0; String str = headers.get("Content-Length"); if (str != null) { contentLength = Integer.parseInt(str); } OutputStream os = conn.getOutputStream(); str = headers.get("Expect"); if ("100-Continue".equalsIgnoreCase(str)) { os.write("HTTP/1.1 100 Continue\r\n\r\n".getBytes("ASCII")); os.flush(); } BodyInputStream input = new BodyInputStream(ins, contentLength); ResponseOutputStream response = new ResponseOutputStream(os); handle(method, path, headers, input, response); response.close(); conn.close(); conn = null; } catch (Exception e) { if (conn != null) { try { conn.close(); } catch (Exception ee) { } } } if (!ss.isBound() || ss.isClosed()) { serverSocket = null; } } } private static class BodyInputStream extends InputStream { private InputStream ins; private int n; public BodyInputStream(InputStream ins, int n) { this.ins = ins; this.n = n; } @Override public int available() throws IOException { return n; } @Override public int read() throws IOException { if (n <= 0) return -1; int r = ins.read(); if (r != -1) n--; return r; } @Override public int read(byte[] b, int off, int len) throws IOException { if (n <= 0) return -1; int l = ins.read(b, off, len < n ? len : n); if (l != -1) n -= l; return l; } @Override public long skip(long n) throws IOException { throw new IOException("unsupported"); } @Override public void close() throws IOException { ins.close(); } @Override public synchronized void mark(int readlimit) { throw new UnsupportedOperationException(); } @Override public synchronized void reset() throws IOException { throw new IOException("unsupported"); } @Override public boolean markSupported() { return false; } } public static class ResponseOutputStream extends OutputStream { private static final byte[] CRLF = { (byte) '\r', (byte) '\n' }; private OutputStream os; private int lv; // 0:statusLine, 1:headers, 2:body, 3:closed public ResponseOutputStream(OutputStream os) { this.os = os; } public void setStatusCode(int statusCode) throws IOException { switch (statusCode) { case 200: setStatusLine("200 OK"); break; case 201: setStatusLine("201 Created"); break; case 202: setStatusLine("202 Accepted"); break; case 301: setStatusLine("301 Moved Permanently"); break; case 304: setStatusLine("304 Not Modified"); break; case 400: setStatusLine("400 Bad Request"); break; case 401: setStatusLine("401 Unauthorized"); break; case 403: setStatusLine("403 Forbidden"); break; case 404: setStatusLine("404 Not Found"); break; case 405: setStatusLine("405 Method Not Allowed"); break; case 500: setStatusLine("500 Internal Server Error"); break; case 501: setStatusLine("501 Not Implemented"); break; default: setStatusLine(String.valueOf(statusCode)); break; } } /** * like "200 OK" */ public void setStatusLine(String statusLine) throws IOException { if (lv == 0) { os.write("HTTP/1.1 ".getBytes("ASCII")); os.write(statusLine.getBytes("ASCII")); os.write(CRLF); lv = 1; } else { throw new IOException("status line is already set"); } } public void setHeader(String name, String value) throws IOException { if (lv < 1) { setStatusCode(200); } if (lv == 1) { os.write(name.getBytes("ASCII")); os.write(':'); os.write(' '); os.write(value.getBytes("ASCII")); os.write(CRLF); } else { throw new IOException("headers is already set"); } } /** * probably set if has body */ public void setContentLength(int value) throws IOException { setHeader("Content-Length", String.valueOf(value)); } /** * like gzip */ public void setContentEncoding(String value) throws IOException { setHeader("Content-Encoding", value); } public void setContentType(String value) throws IOException { setHeader("Content-Type", value); } /** * Content-Type: text/plain */ public void setContentTypeText() throws IOException { setContentType("text/plain"); } /** * Content-Type: text/plain; charset=utf-8 */ public void setContentTypeTextUtf8() throws IOException { setContentType("text/plain; charset=utf-8"); } /** * Content-Type: text/html */ public void setContentTypeHtml() throws IOException { setContentType("text/html"); } /** * Content-Type: text/html; charset=utf-8 */ public void setContentTypeHtmlUtf8() throws IOException { setContentType("text/html; charset=utf-8"); } /** * Content-Type: application/octet-stream */ public void setContentTypeBinary() throws IOException { setContentType("application/octet-stream"); } /** * Content-Type: application/json */ public void setContentTypeJson() throws IOException { setContentType("application/json"); } /** * Content-Type: text/xml */ public void setContentTypeXml() throws IOException { setContentType("text/xml"); } /** * Content-Type: application/zip */ public void setContentTypeZip() throws IOException { setContentType("application/zip"); } /** * Content-Type: image/jpeg */ public void setContentTypeJpeg() throws IOException { setContentType("image/jpeg"); } /** * Content-Type: image/png */ public void setContentTypePng() throws IOException { setContentType("image/png"); } @Override public void write(int b) throws IOException { if (lv < 1) { setStatusCode(200); } if (lv < 2) { os.write(CRLF); lv = 2; } os.write(b); } @Override public void write(byte[] b, int off, int len) throws IOException { if (lv < 1) { setStatusCode(200); } if (lv < 2) { os.write(CRLF); lv = 2; } os.write(b, off, len); } @Override public void flush() throws IOException { os.flush(); } @Override public void close() throws IOException { if (lv < 1) { setStatusCode(404); } if (lv < 2) { os.write(CRLF); lv = 2; } if (lv < 3) { os.close(); lv = 3; } } } }