首页 > 代码库 > 用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 }
JHTTP.java
  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 }
RequestProcessor.java

 

测试结果:

图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服务器