首页 > 代码库 > 用Java编写你自己的简单HTTP服务器
用Java编写你自己的简单HTTP服务器
来源:http://blog.csdn.net/yanghua_kobe/article/details/7296156
HTTP是个大协议,完整功能的HTTP服务器必须响应资源请求,将URL转换为本地系统的资源名。响应各种形式的HTTP请求(GET、POST等)。处理不存在的文件请求,返回各种形式的状态码,解析MIME类型等。但许多特定功能的HTTP服务器并不需要所有这些功能。例如,很多网站只是想显示“建设中“的消息。很显然,Apache对于这样的网站是大材小用了。这样的网站完全可以使用只做一件事情的定制服务器。Java网络类库使得编写这样的单任务服务器轻而易举。 定制服务器不只是用于小网站。大流量的网站如Yahoo,也使用定制服务器,因为与一般用途的服务器相比,只做一件事情的服务器通常要快得多。针对某项任务来优化特殊用途的服务器很容易;其结果往往比需要响应很多种请求的一般用途服务器高效得多。例如,对于重复用于多页面或大流量页面中的图标和图片,用一个单独的服务器处理会更好(并且还可以避免在请求时携带不必要的Cookie,因而可以减少请求/响应数据,从而减少下载带宽,提升速度);这个服务器在启动时把所有图片文件读入内存,从RAM中直接提供这些文件,而不是每次请求都从磁盘上读取。此外,如果你不想在包含这些图片的页面请求之外单独记录这些图片,这个单独服务器则会避免在日志记录上浪费时间。
1 import java.io.File; 2 import java.io.IOException; 3 import java.net.ServerSocket; 4 import java.net.Socket; 5 6 /** 7 * @Title: JHTTP.java 8 * @Package 9 * @author 任伟10 * @date 2014-12-4 下午1:30:07 11 * @version V1.0 12 */13 14 /**15 * @ClassName: JHTTP16 * @Description: JHTTP线程反复地接受入站连接,并将其放在RequestProcessor池中17 * @author 任伟18 * @date 2014-12-4 下午1:30:0719 */20 public class JHTTP extends Thread {21 private File documentRootDirectory; //文档根目录22 private String indexFileName = "index.html"; //引导文件23 private ServerSocket server; //Server24 private int numThreads = 50; //线程数量25 26 public JHTTP(File documentRootDirectory, int port, String indexFileName)27 throws IOException {28 if (!documentRootDirectory.isDirectory()) {29 throw new IOException(documentRootDirectory30 + " does not exist as a directory ");31 }32 this.documentRootDirectory = documentRootDirectory;33 this.indexFileName = indexFileName;34 this.server = new ServerSocket(port);35 }36 37 private JHTTP(File documentRootDirectory, int port) throws IOException {38 this(documentRootDirectory, port, "index.html");39 }40 41 public void run() {42 //新建numThreads个请求处理线程,并开启线程43 for(int i=0; i<numThreads; i++){44 Thread t = new Thread(new RequestProcessor(documentRootDirectory, indexFileName));45 t.start();46 }47 System.out.println("Accepting connection on port "+server.getLocalPort());48 System.out.println("Document Root: "+documentRootDirectory);49 50 //无限循环接受请求,收到一个请求将其放入RequestProcessor的请求池51 while(true){52 try{53 Socket request=server.accept();54 RequestProcessor.processRequest(request); 55 }catch (Exception e) {56 // TODO: handle exception57 }58 }59 }60 61 /**62 * @param args63 */64 public static void main(String[] args) {65 //设置文档根目录66 File docroot;67 try {68 docroot = new File(args[0]);69 } catch (ArrayIndexOutOfBoundsException e) {70 System.out.println("Usage: java JHTTP docroot port indexfile");71 return;72 }73 74 //读取端口号75 int port;76 try {77 port = Integer.parseInt(args[1]);78 if (port < 0 || port > 65535) {79 port = 80;80 }81 } catch (Exception e) {82 port = 80;83 }84 85 //构造一个新的JHTTP线程并启动86 try {87 JHTTP webserver = new JHTTP(docroot, port);88 webserver.start();89 } catch (IOException e) {90 System.out.println("Server could not start because of an "91 + e.getClass());92 System.out.println(e);93 }94 95 }96 97 }
1 import java.io.BufferedInputStream; 2 import java.io.BufferedOutputStream; 3 import java.io.DataInputStream; 4 import java.io.File; 5 import java.io.FileInputStream; 6 import java.io.IOException; 7 import java.io.InputStreamReader; 8 import java.io.OutputStream; 9 import java.io.OutputStreamWriter; 10 import java.io.Reader; 11 import java.io.Writer; 12 import java.net.Socket; 13 import java.util.Date; 14 import java.util.LinkedList; 15 import java.util.List; 16 import java.util.StringTokenizer; 17 18 /** 19 * @Title: RequestProcessor.java 20 * @Package 21 * @author 任伟 22 * @date 2014-12-4 下午1:42:22 23 * @version V1.0 24 */ 25 26 /** 27 * @ClassName: RequestProcessor 28 * @Description: 请求处理程序 29 * @author 任伟 30 * @date 2014-12-4 下午1:42:22 31 */ 32 public class RequestProcessor implements Runnable { 33 private static List pool = new LinkedList(); 34 private File documentRootDirectory; 35 private String indexFileName = "index.html"; 36 37 // 构造方法 38 public RequestProcessor(File documentRootDirectory, String indexFileName) { 39 if (documentRootDirectory.isFile()) { 40 throw new IllegalArgumentException(); 41 } 42 this.documentRootDirectory = documentRootDirectory; 43 try { 44 this.documentRootDirectory = documentRootDirectory 45 .getCanonicalFile(); 46 } catch (IOException e) { 47 } 48 49 if (indexFileName != null) { 50 this.indexFileName = indexFileName; 51 } 52 } 53 54 // 向请求池加入请求 55 public static void processRequest(Socket request) { 56 synchronized (pool) { 57 pool.add(pool.size(), request); 58 pool.notifyAll(); 59 } 60 } 61 62 /* 63 * (non-Javadoc) 64 * 65 * @see java.lang.Runnable#run() 66 */ 67 @Override 68 public void run() { 69 // 无限循环处理 70 while (true) { 71 72 // 先进性安全性检测,然后从连接池获取一个链接 73 Socket connection; 74 synchronized (pool) { 75 while (pool.isEmpty()) { 76 try { 77 pool.wait(); 78 } catch (Exception e) { 79 } 80 } 81 connection = (Socket) pool.remove(0); 82 } 83 84 // 开始处理 85 try { 86 // 获得输入输出流 87 OutputStream raw = new BufferedOutputStream( 88 connection.getOutputStream()); 89 Writer out = new OutputStreamWriter(raw); 90 Reader in = new InputStreamReader(new BufferedInputStream( 91 connection.getInputStream())); 92 93 // 拼接请求字符串 94 StringBuffer request = new StringBuffer(80); 95 while (true) { 96 int c = in.read(); 97 if (c == ‘\t‘ || c == ‘\n‘ || c == -1) { 98 break; 99 }100 request.append((char) c);101 }102 103 // 记录日志 eg:104 // localhost:port/a/b/c/index.html105 // GET /a/b/c/index.html HTTP/1.1106 String get = request.toString();107 System.out.println(get);108 109 // 分析请求110 StringTokenizer st = new StringTokenizer(get);111 String method = st.nextToken();// 请求的方法 GET112 String fileName;// 请求的文件名113 String version = "";// 协议版本114 String contentType;// 相应返回的内容类型115 116 if (method.equals("GET")) {// 方法是“GET”117 fileName = st.nextToken();118 if (fileName.endsWith("/")) {119 fileName += indexFileName;120 }121 contentType = guessContentTypeFromName(fileName);122 if (st.hasMoreTokens()) {123 version = st.nextToken();124 }125 126 // 根据文件目录读出文件,并返回响应127 File theFile = new File(documentRootDirectory,128 fileName.substring(1, fileName.length()));129 String root = documentRootDirectory.getPath();130 if (theFile.canRead()131 && theFile.getCanonicalPath().startsWith(root)) {// 读取文件成功132 DataInputStream fis = new DataInputStream(133 new BufferedInputStream(new FileInputStream(134 theFile)));135 byte[] theData = http://www.mamicode.com/new byte[(int) theFile.length()];136 fis.readFully(theData);137 fis.close();138 139 // HTTP请求,返回响应头140 if (version.startsWith("HTTP")) {141 out.write("HTTP/1.0 200 OK\r\n");142 Date now = new Date();143 out.write("Date: " + now + "\r\n");144 out.write("Server: JHTTP 1.0\r\n");145 out.write("Content-length: " + theData.length146 + "\r\n");147 out.write("Content-Type: " + contentType148 + "\r\n\r\n");149 out.flush();150 }151 raw.write(theData);152 raw.flush();153 154 } else {// 读取文件不成功155 if (version.startsWith("HTTP")) { // 是HTTP请求返回 响应头 404156 out.write("HTTP/1.0 404 File Not Found\r\n");157 Date now = new Date();158 out.write("Date: " + now + "\r\n");159 out.write("Server: JHTTP 1.0\r\n");160 out.write("Content-Type: text/html\r\n\r\n");161 }162 out.write("<HTML>\r\n");163 out.write("<HEAD><TITLE>File Not Found</TITLE></HRAD>\r\n");164 out.write("<BODY>\r\n");165 out.write("<H1>HTTP Error 404: File Not Found</H1>");166 out.write("</BODY></HTML>\r\n");167 out.flush();168 }169 } else {// 方法不是“GET”170 if (version.startsWith("HTTP")) {171 out.write("HTTP/1.0 501 Not Implemented\r\n");172 Date now = new Date();173 out.write("Date: " + now + "\r\n");174 out.write("Server: JHTTP 1.0\r\n");175 out.write("Content-Type: text/html\r\n\r\n");176 }177 out.write("<HTML>\r\n");178 out.write("<HEAD><TITLE>Not Implemented</TITLE></HRAD>\r\n");179 out.write("<BODY>\r\n");180 out.write("<H1>HTTP Error 501: Not Implemented</H1>");181 out.write("</BODY></HTML>\r\n");182 out.flush();183 }184 185 } catch (Exception e) {186 // TODO: handle exception187 } finally {188 try {189 connection.close();190 } catch (IOException e) {191 e.printStackTrace();192 }193 }194 }195 196 }197 198 // 根据文件名字猜测返回的文件的内容类型199 public static String guessContentTypeFromName(String name) {200 if (name.endsWith(".html") || name.endsWith(".htm")) {201 return "text/html";202 } else if (name.endsWith(".txt") || name.endsWith(".java")) {203 return "text/plain";204 } else if (name.endsWith(".class")) {205 return "application/octet-stream";206 } else if (name.endsWith(".gif")) {207 return "image/gif";208 } else if (name.endsWith(".jpg") || name.endsWith(".jpeg")) {209 return "image/jpeg";210 } else if (name.endsWith(".png")) {211 return "image/png";212 } else {213 return "text/plain";214 }215 }216 217 }
测试结果:
图1
JHTTP类的main()方法根据args[0]设置文档的根目录。端口从args[1]读取,或者使用默认的80.然后构造一个新的JHTTP线程并启动。此JHTTP线程生成50个RequestProcessor线程处理请求,每个线程在可用时从RequestProcessor池获取入站连接请求。JHTTP线程反复地接受入站连接,并将其放在RequestProcessor池中。每个连接由下例所示的RequestProcessor类的run()方法处理。此方法将一直等待,直到从池中得到一个Socket。一旦得到Socket,就获取输入和输出流,并链接到阅读器和书写器。接着的处理,除了多出文档目录、路径的处理,其他的同单文件服务器。
最后,花点时间考虑一下可以采用什么方法来优化此服务器。如果真的希望使用JHTTP运行高流量的网站,还可以做一些事情来加速此服务器。第一点也是最重要的一点就是使用即时编译器(JIT),如HotSpot。JIT可以将程序的性能提升大约一个数量级。第二件事就是实现智能缓存。记住接受的请求,将最频繁的请求文件的数据存储在Hashtable中,使之保存在内存中。使用低优先级的线程更新此缓存。
用Java编写你自己的简单HTTP服务器