用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中,使之保存在内存中。使用低优先级的线程更新此缓存。

时间: 2024-10-17 22:12:43

用Java编写你自己的简单HTTP服务器的相关文章

java 线程池socket实现简单http服务器

---恢复内容开始--- 要点: 1.只需回应Get请求,将本地的lena.jpg返回给客户端 2.使用最基本的socket编程 3.使用线程池进行线程管理 4.将lena.jpg读入内存中,减少IO次数 5.MyHttpServer作为服务器类,ServerThread作为服务处理线程类 1.加入线程池 ExecutorService pool = Executors.newFixedThreadPool(MaxClientNums); 2.有客户访问则开启服务线程并将其加入线程池 while

如何用Java编写一个简单的服务器和客户机

今天我要向大家介绍的是自己编写的一个比较简单的服务器和客户机程序,注意一下哦,比较简单.好了,闲话休提,砸门直入主题. 小编先从客户机和服务器的模型开始讲解.简单来说,我们实现的这种模型呢,我们每一个用户称为一个客户机,用户之间的通信之间需要一个中转,所有客户机的通信都依托于这个中转,很明显,这个中转,就是砸门的服务器了.整个模型比较简单明了,那么,接下来我们就直接进入实现阶段. 我们从实现服务器开始.Java提供了这样的一个类,ServerSocket.我们通过实例化它的方式来创建一个服务器.

java实现简单web服务器(分析+源代码)

在日常的开发中,我们用过很多开源的web服务器,例如tomcat.apache等等.现在我们自己实现一个简单的web服务器,基本的功能就是用户点击要访问的资源,服务器将资源发送到客户端的浏览器.为了简化操作,这里不考虑资源不存在等异常情况.web服务基于的是HTTP协议,用户在浏览器的地址栏输入要访问的地址,服务器如何得到该地址是个关键.先看下一般的HTTP请求和响应报文的一般格式: HTTP 请求报文 HTTP 响应报文 web服务器获取一个用户的连接时,会初始化一个线程和用户通信,代码如下:

基于Java实现简单Http服务器(转)

基于Java实现简单Http服务器(转) 本文将详细介绍如何基于java语言实现一个简单的Http服务器,文中将主要介绍三个方面的内容:1)Http协议的基本知识.2)java.net.Socket类.3)java.net.ServerSocket类,读完本文后你可以把这个服务器用多线程的技术重新编写一个更好的服务器.           由于Web服务器使用Http协议通信的因此也把它叫做Http服务器,Http使用可靠的TCP连接来工作,它是面向连接的通信方式,这意味着客户端和服务器每次通信

Java编写基于netty的RPC框架

一 简单概念 RPC: ( Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO :当阻塞I/O在调用InputStream.read()方法是阻塞的,一直等到数据到来时才返回,同样ServerSocket.accept()方法时,也是阻塞,直到有客户端连接才返回,I/O通信模式如下: 图片描述(最多50字) 缺点:当客户端多时,会创建大量的处理线程,并且为每一个线程分配一定的资源;阻塞

java编写Base64密码器

Base64加密算法,应用广泛,尤其是在电子邮件传输上,有很大的用途 用JAVA编写的程序代码如下 import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import javax.swing.JFrame; import javax.s

JAVA - Sql解析工具fdb-sql-parser简单使用

由于想要解决Mybatis分页插件中count查询效率问题,因为order by很影响效率,所以需要一种方式处理sql,将order by 语句去掉. 试了好几个sql解析工具,最后选择了fdb-sql-parser. Maven依赖: <dependency> <groupId>com.foundationdb</groupId> <artifactId>fdb-sql-parser</artifactId> <version>1.

Java 多线程编程两个简单的例子

/** * @author gao */ package gao.org; public class RunnableDemo implements Runnable{ @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<10;i++){ System.out.println("新线程输出:"+i); } } public static void main(String []

java中Color类的简单总结

java中Color类的简单总结 1.颜色的常识 任何颜色都是由三原色组成(RGB),JAVA中支持224为彩色,即红绿蓝分量取值 介于0-255之间(8位表示) 2.Color类中的常量 public final static Color black = new Color(0,0,0); public final static Color bule = new Color(0,0,255); . . 有很多这样的常量,可供我们直接类名去调用而不需要去实例化. 3.Color中的构造函数 pu