用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: JHTTP
16  * @Description: JHTTP线程反复地接受入站连接,并将其放在RequestProcessor池中
17  * @author 任伟
18  * @date 2014-12-4 下午1:30:07
19  */
20 public class JHTTP extends Thread {
21     private File documentRootDirectory;                //文档根目录
22     private String indexFileName = "index.html";    //引导文件
23     private ServerSocket server;                    //Server
24     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(documentRootDirectory
30                     + " 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 exception
57             }
58         }
59     }
60 
61     /**
62      * @param args
63      */
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.html
105                 // GET /a/b/c/index.html HTTP/1.1
106                 String get = request.toString();
107                 System.out.println(get);
108 
109                 // 分析请求
110                 StringTokenizer st = new StringTokenizer(get);
111                 String method = st.nextToken();// 请求的方法 GET
112                 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 = 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.length
146                                     + "\r\n");
147                             out.write("Content-Type: " + contentType
148                                     + "\r\n\r\n");
149                             out.flush();
150                         }
151                         raw.write(theData);
152                         raw.flush();
153 
154                     } else {// 读取文件不成功
155                         if (version.startsWith("HTTP")) { // 是HTTP请求返回 响应头 404
156                             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 exception
187             } 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中,使之保存在内存中。使用低优先级的线程更新此缓存。

posted @ 2014-12-04 16:31  _NullPointer  阅读(407)  评论(0编辑  收藏  举报