您的位置:首页 > 理论基础 > 计算机网络

用Java编写你自己的简单HTTP服务器

2014-12-04 16:31 239 查看
来源:http://blog.csdn.net/yanghua_kobe/article/details/7296156

   HTTP是个大协议,完整功能的HTTP服务器必须响应资源请求,将URL转换为本地系统的资源名。响应各种形式的HTTP请求(GET、POST等)。处理不存在的文件请求,返回各种形式的状态码,解析MIME类型等。但许多特定功能的HTTP服务器并不需要所有这些功能。例如,很多网站只是想显示“建设中“的消息。很显然,Apache对于这样的网站是大材小用了。这样的网站完全可以使用只做一件事情的定制服务器。Java网络类库使得编写这样的单任务服务器轻而易举。 定制服务器不只是用于小网站。大流量的网站如Yahoo,也使用定制服务器,因为与一般用途的服务器相比,只做一件事情的服务器通常要快得多。针对某项任务来优化特殊用途的服务器很容易;其结果往往比需要响应很多种请求的一般用途服务器高效得多。例如,对于重复用于多页面或大流量页面中的图标和图片,用一个单独的服务器处理会更好(并且还可以避免在请求时携带不必要的Cookie,因而可以减少请求/响应数据,从而减少下载带宽,提升速度);这个服务器在启动时把所有图片文件读入内存,从RAM中直接提供这些文件,而不是每次请求都从磁盘上读取。此外,如果你不想在包含这些图片的页面请求之外单独记录这些图片,这个单独服务器则会避免在日志记录上浪费时间。

  

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.Socket;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

/**
* @Title: RequestProcessor.java
* @Package
* @author 任伟
* @date 2014-12-4 下午1:42:22
* @version V1.0
*/

/**
* @ClassName: RequestProcessor
* @Description: 请求处理程序
* @author 任伟
* @date 2014-12-4 下午1:42:22
*/
public class RequestProcessor implements Runnable {
private static List pool = new LinkedList();
private File documentRootDirectory;
private String indexFileName = "index.html";

// 构造方法
public RequestProcessor(File documentRootDirectory, String indexFileName) {
if (documentRootDirectory.isFile()) {
throw new IllegalArgumentException();
}
this.documentRootDirectory = documentRootDirectory;
try {
this.documentRootDirectory = documentRootDirectory
.getCanonicalFile();
} catch (IOException e) {
}

if (indexFileName != null) {
this.indexFileName = indexFileName;
}
}

// 向请求池加入请求
public static void processRequest(Socket request) {
synchronized (pool) {
pool.add(pool.size(), request);
pool.notifyAll();
}
}

/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
// 无限循环处理
while (true) {

// 先进性安全性检测,然后从连接池获取一个链接
Socket connection;
synchronized (pool) {
while (pool.isEmpty()) {
try {
pool.wait();
} catch (Exception e) {
}
}
connection = (Socket) pool.remove(0);
}

// 开始处理
try {
// 获得输入输出流
OutputStream raw = new BufferedOutputStream(
connection.getOutputStream());
Writer out = new OutputStreamWriter(raw);
Reader in = new InputStreamReader(new BufferedInputStream(
connection.getInputStream()));

// 拼接请求字符串
StringBuffer request = new StringBuffer(80);
while (true) {
int c = in.read();
if (c == '\t' || c == '\n' || c == -1) {
break;
}
request.append((char) c);
}

// 记录日志 eg:
// localhost:port/a/b/c/index.html
// GET /a/b/c/index.html HTTP/1.1
String get = request.toString();
System.out.println(get);

// 分析请求
StringTokenizer st = new StringTokenizer(get);
String method = st.nextToken();// 请求的方法 GET
String fileName;// 请求的文件名
String version = "";// 协议版本
String contentType;// 相应返回的内容类型

if (method.equals("GET")) {// 方法是“GET”
fileName = st.nextToken();
if (fileName.endsWith("/")) {
fileName += indexFileName;
}
contentType = guessContentTypeFromName(fileName);
if (st.hasMoreTokens()) {
version = st.nextToken();
}

// 根据文件目录读出文件,并返回响应
File theFile = new File(documentRootDirectory,
fileName.substring(1, fileName.length()));
String root = documentRootDirectory.getPath();
if (theFile.canRead()
&& theFile.getCanonicalPath().startsWith(root)) {// 读取文件成功
DataInputStream fis = new DataInputStream(
new BufferedInputStream(new FileInputStream(
theFile)));
byte[] theData = new byte[(int) theFile.length()];
fis.readFully(theData);
fis.close();

// HTTP请求,返回响应头
if (version.startsWith("HTTP")) {
out.write("HTTP/1.0 200 OK\r\n");
Date now = new Date();
out.write("Date: " + now + "\r\n");
out.write("Server: JHTTP 1.0\r\n");
out.write("Content-length: " + theData.length
+ "\r\n");
out.write("Content-Type: " + contentType
+ "\r\n\r\n");
out.flush();
}
raw.write(theData);
raw.flush();

} else {// 读取文件不成功
if (version.startsWith("HTTP")) { // 是HTTP请求返回 响应头 404
out.write("HTTP/1.0 404 File Not Found\r\n");
Date now = new Date();
out.write("Date: " + now + "\r\n");
out.write("Server: JHTTP 1.0\r\n");
out.write("Content-Type: text/html\r\n\r\n");
}
out.write("<HTML>\r\n");
out.write("<HEAD><TITLE>File Not Found</TITLE></HRAD>\r\n");
out.write("<BODY>\r\n");
out.write("<H1>HTTP Error 404: File Not Found</H1>");
out.write("</BODY></HTML>\r\n");
out.flush();
}
} else {// 方法不是“GET”
if (version.startsWith("HTTP")) {
out.write("HTTP/1.0 501 Not Implemented\r\n");
Date now = new Date();
out.write("Date: " + now + "\r\n");
out.write("Server: JHTTP 1.0\r\n");
out.write("Content-Type: text/html\r\n\r\n");
}
out.write("<HTML>\r\n");
out.write("<HEAD><TITLE>Not Implemented</TITLE></HRAD>\r\n");
out.write("<BODY>\r\n");
out.write("<H1>HTTP Error 501: Not Implemented</H1>");
out.write("</BODY></HTML>\r\n");
out.flush();
}

} catch (Exception e) {
// TODO: handle exception
} finally {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

}

// 根据文件名字猜测返回的文件的内容类型
public static String guessContentTypeFromName(String name) {
if (name.endsWith(".html") || name.endsWith(".htm")) {
return "text/html";
} else if (name.endsWith(".txt") || name.endsWith(".java")) {
return "text/plain";
} else if (name.endsWith(".class")) {
return "application/octet-stream";
} else if (name.endsWith(".gif")) {
return "image/gif";
} else if (name.endsWith(".jpg") || name.endsWith(".jpeg")) {
return "image/jpeg";
} else if (name.endsWith(".png")) {
return "image/png";
} else {
return "text/plain";
}
}

}


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中,使之保存在内存中。使用低优先级的线程更新此缓存。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: