下面是源码,如果需要打包的class文件,请与俺联系:hcc4hcc@163.com
(注:程序中import另外的工具包,这里没有列出)
readme.txt ====================================================
0.85版更新:
优化接收请求的速度,但不明显;
支持断点续传,支持多线程下载,但不支持IE的断点续传(不知道为什么);目前在FlashGet上测试通过。
0.8版更新:
增加线程池;
独立出log功能(class:WebServerLogger);
优化部分代码;
增加可自定义日志文件名;
增加可自定义线程池中线程max和min个数;
增加可自定义发送缓存大小;(增加下载速度最重要的设置,推荐128-256KB)
增加超时设置;(目前是5秒)
除掉了有时出现页面未找到的BUG(原因:原来是每个线程都执行了两遍run()。:P);
修正了HTTP协议中日期的格式错误
修正了HTTP协议中换行符
修正与FlashGet的兼容性,FlashGet可以正常下载了
(原因:FlashGet与本服务器建立Socket连接后,没有立即发数据,有很短暂的延迟,因为本服务器经过
速度优化,响应很快,而且没有设超时,所以认为FlashGet没有连接上,故切断了连接。注:IE的下载
和NetAnts没有此问题。);
总共改动了近1/2代码。
0.7版全部特性:
可以自定义监听端口;
可以自定义是否列出目录列表;
可以自定义服务器名字;
可以自定义index文件名;
不重新启动服务器,更改参数文件(webserver.ini)立即生效;(监听端口除外)
高速的,经过优化的IO处理,速度不亚于apache;
-->需要jar打包文件的可与俺联系:经过打包(jar包),使用很方便,只需2个文件:一个jar文件和一个参数文件;(生成的log文件除外)
LOG记录详尽,包括客户端IP地址、端口号、机器名,而且包含连接服务的线程ID;
线程安全,耗费资源不大。
WebServer.java ================================================
/*******************
*
* Simple Web Server
*
* @author : hcc
*
* @version : 0.85 build 2002-8-20
*
*******************/
package hcc;
import java.io.*;
import java.net.*;
import java.util.*;
import java.text.*;
public class WebServer
{
/**
* WebServer构造器
*/
public WebServer()
{
////出现较严重错误,打印到控制台上,不记录log
boolean existError = false;
////读取控制台命令
BufferedReader readCmd = null;
SimpleWebServerManager swsm = null;
try
{
swsm = SimpleWebServerManager.getInstance();
System.out.println("\n:) === Simple Web Server ===");
System.out.println(":) Running on port "+swsm.getPort()+" ...");
readCmd = new BufferedReader(new InputStreamReader(System.in));
String cmd = null;
System.out.print("\nSimple Web Server Command >");
////接收输入命令
while ((cmd=readCmd.readLine()) != null)
{
if (cmd.equals("shutdown") || cmd.equals("sd")) break;
else if (cmd.equals("?") || cmd.equals("help")) System.out.println("\nsyntax: \"shutdown\" --- Shutdown Simple Web Server\n");
else System.out.println("\n:( Bad Command !\n\"?\" or \"help\" --- help\n");
System.out.print("Simple Web Server Command >");
}
System.out.print("\n:) Shutdown Simple Web Server ... ");
swsm.interrupt();
swsm.destroy();
System.out.println("OK");
}
catch (Exception e)
{
existError = true;
e.printStackTrace();
}
finally
{
try { readCmd.close(); } catch (Exception ignored) {}
readCmd = null;
if (existError) System.exit(1);
else System.exit(0);
}
}
public static void main(String[] args)
{
new WebServer();
}
}
SimpleWebServer.java ===========================================
package hcc;
import java.io.*;
import java.net.*;
import java.util.*;
import java.text.*;
import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap;
public class SimpleWebServer extends Thread
{
////当前版本号
private static final String VERSION = "0.85";
////是否进行参数校验等调试
public static boolean Debug = false;
////URL的Encode
private static String Encode = null;
////ID
private String ID;
private Socket socket = null;
////服务器IP
private String serverIP = null;
////服务器名字
private String serverName = null;
////index file name,按从先到后的顺序查找
private static String[] indexFiles = null;
////root path
private static String rootPath = null;
////服务器所在操作系统的换行符
private static String separator = null;
////是否允许列出当前目录下的文件
private static boolean allowListFiles = true;
////客户IP
private String clientIP = null;
////客户port
private int clientPort;
////客户机 host name
private String clientName = null;
////HTTP协议中的日期格式
private static SimpleDateFormat dateFormat = null;
////Output缓存大小(byte)
private int outputBuffer = 256 * 1024;
////客户请求信息
private String requestStr = null;
////存放配置参数
private static ConcurrentReaderHashMap properties = null; //所有线程可以并发读取,但只能同步写入的HashMap
////socket读取buffer
public static final int READ_BUFFER_SIZE = 1 * 1024;
////log信息类型
private static final int ERROR = 0;
private static final int WARNING = 1;
private static final int INFO = 2;
private static final int DEBUG = 3;
//////常量参数:webserver.ini文件中存放的错误页面key;也用来标识错误类型
private static final String NOT_FOUND = "NOT_FOUND_404";
private static final String BAD_REQUEST = "BAD_REQUEST_400"; ////请求信息错误
private static final String INTERNAL_ERROR = "INTERNAL_ERROR_500"; ////服务器内部错误
private static final String FORBIDDEN = "FORBIDDEN_403"; ////禁止访问
/////默认错误页面
private static final String default_NOT_FOUND = "File Not Found !ERROR 404 :
File Not Found!
Simple Web Server "+VERSION+" by hcc";
private static final String default_BAD_REQUEST = "Bad Request !ERROR 400 :
Bad Request !
Simple Web Server "+VERSION+" by hcc";
private static final String default_INTERNAL_ERROR = "Internal Error !ERROR 500 :
Internal Error !
Simple Web Server "+VERSION+" by hcc";
private static final String default_FORBIDDEN = "Forbidden !ERROR 403 :
Forbidden !
Simple Web Server "+VERSION+" by hcc";
////log
private WebServerLogger log = null;
////socket的I/O流
private PrintStream out = null;
private BufferedReader in = null;
////读文件用的
private BufferedInputStream fin = null;
/**构造器*/
public SimpleWebServer(Socket socket, String ID, ConcurrentReaderHashMap p)
throws NullPointerException, IOException
{
////Debug==true,进行调试
if (Debug)
{
if (ID == null) throw new NullPointerException("参数出错!ID 为 null (Property Error : \"ID\" is null)");
if (socket == null) throw new NullPointerException("参数出错!Socket 为 null (Property Error : \"Socket\" is null)");
}
this.ID = ID;
this.socket = socket;
properties = p;
////index files
indexFiles = (String[])properties.get("Index");
////root path
rootPath = (String)properties.get("Root");
////服务器IP
serverIP = (String)properties.get("ServerIP");
////服务器名字
serverName = (String)properties.get("ServerName");
if (serverName==null || serverName.equals("")) serverName = serverIP;
////服务器所在操作系统的换行符
separator = (String)properties.get("Separator");
////是否允许列出当前目录文件
String lf = (String)properties.get("ListFiles");
allowListFiles = lf.equalsIgnoreCase("yes");
////HTTP协议中的日期格式
dateFormat = (SimpleDateFormat)properties.get("DateFormat");
////URL的Encode
Encode = (String)properties.get("Encode");
////log
this.log = (WebServerLogger)properties.get("Logger");
////Output缓存大小(byte)
this.outputBuffer = Integer.parseInt((String)properties.get("OutputBuffer")) * 1024;
this.clientIP = this.socket.getInetAddress().getHostAddress(); ////client IP
this.clientPort = this.socket.getPort(); ////client port
this.clientName = this.socket.getInetAddress().getHostName(); ////client name
this.in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
this.out = new PrintStream(new BufferedOutputStream(this.socket.getOutputStream()), true);
}
/********************** private mothed start: **************************/
/**
* 记录日志
*
* @param messageType 日志类型:ERROR,WARNING,INFO
* @param message 要记录的信息
*/
private void doLog(int messageType, String message)
{
StringBuffer messTmp = new StringBuffer();
messTmp.append("(ConnectionID:"+this.ID+") ");
messTmp.append(this.clientIP+":"+this.clientPort+" ("+this.clientName+") ");
messTmp.append(message);
switch (messageType)
{
case ERROR : this.log.error(messTmp.toString());break;
case WARNING : this.log.warning(messTmp.toString());break;
case INFO : this.log.info(messTmp.toString());break;
case DEBUG : this.log.debug(messTmp.toString());break;
}
}
/**
* 发送HTTP头信息,添加HTTP协议规定的回车换行符("\r\n")
*/
private void println(String s)
{
this.out.print(s + "\r\n");
}
/**
* 发送HTTP协议规定的回车+换行符("\r\n")
*/
private void println()
{
this.out.print("\r\n");
}
/**
* 读取请求信息头,以“\r\n\r\n”结束
*
* @return request信息
* @exception IOException 网络原因
*/
private String getRequestString() throws IOException
{
////this.doLog(DEBUG,"getRequestString() requestStr="+requestStr);
StringBuffer sb = new StringBuffer(500);
char[] cBuf = new char[READ_BUFFER_SIZE];
char[] CRLF = new char[4];
char c = 0;
int CRLFpos = 0;
int currpos = 0;////当前位置
int maxpos = 0;////最大位置
while (true)
{
if (currpos == maxpos)
{
maxpos = this.in.read(cBuf, 0, READ_BUFFER_SIZE);
if (maxpos == -1) break;
currpos = 0;
sb.append(cBuf, 0, maxpos);
}
c = cBuf[currpos++];
if (c == ´\r´)
{
try
{
CRLF[CRLFpos++] = c;
}
catch (ArrayIndexOutOfBoundsException e)
{
CRLFpos -= 2;
}
}
else if (c == ´\n´)
{
try
{
CRLF[CRLFpos++] = c;
}
catch (ArrayIndexOutOfBoundsException e)
{
CRLFpos -= 2;
}
if (CRLF[0]==´\r´ && CRLF[1]==´\n´ && CRLF[2]==´\r´ && CRLF[3]==´\n´) break;
}
else
{
CRLFpos = 0;
CRLF[0] = 0;
CRLF[1] = 0;
CRLF[2] = 0;
CRLF[3] = 0;
}
}
/** readLine()方式:
String str = this.in.readLine();
while(str!=null && !str.equals(""))
{
sb.append(str+"\r\n");
str = this.in.readLine();
}
*/
return sb.substring(0, sb.indexOf("\r\n\r\n")+4);
}
////高速output流
private void fastOutput(InputStream in, PrintStream out, int bufferSize)
throws IOException
{
////因为用PrintStream一个字节一个字节写很慢,所以使用本地缓存加快速度
int buffSize = bufferSize;
if (buffSize = 0;
}
/**发送文件*/
protected void sendFile(String fileName)
throws IOException
{
////this.doLog(DEBUG,"sendFile(start) requestStr="+requestStr);
if (!isAllowAccess(fileName))
{
////超出rootPath范围,不允许访问
this.doLog(INFO, getRequestMethod()+" "+fileName+" failed : 403 Forbidden!"); ////写log
sendError(FORBIDDEN);
}
else
{
String fileFullPath = getAbsolutePath(fileName);
if (fileName==null || fileName.charAt(0)!=´/´) ////请求文件名为null或第一个字符不是"/"
{
this.doLog(INFO, getRequestMethod()+" "+fileName+" failed : 400 Bad Request!"); ////写log
sendError(BAD_REQUEST);
}
////如果fileName以"/"结尾或fileName为一个目录,则列出indexFiles中的文件
else if (fileName.endsWith("/") || (new File(fileFullPath)).isDirectory())
{
this.doLog(INFO, getRequestMethod()+" "+fileName); ////写log
if (fileName.endsWith("/")) sendIndexFile(fileName.substring(0, fileName.lastIndexOf(´/´)));
else sendIndexFile(fileName);
}
else
{
File f = new File(fileFullPath);
String key = "range: bytes=";
int pos = this.requestStr.toLowerCase().indexOf(key);
if (pos > 0) ////断点续传
{
String startPos = this.requestStr.substring(pos+key.length(), this.requestStr.indexOf("-", pos));
try
{
long start = Long.parseLong(startPos);
try
{
this.fin = new BufferedInputStream(new FileInputStream(f));
int finLength = this.fin.available();
////如果finLength<0,相当于文件未找到,所以仍旧抛出FileNotFoundException
if (finLength < 0) throw new FileNotFoundException();
else
{
this.doLog(INFO, getRequestMethod()+" "+fileName+" 续传!"); ////写log
this.println("HTTP/1.0 206 Partial Content");
this.println("Server: Simple Web Server");
this.println("Connection: close");
this.println("Date: "+dateFormat.format(new Date()));
this.println("Content-Type: "+f.toURL().openConnection().getContentType());
this.println("Last-Modified: "+dateFormat.format(new Date(f.lastModified())));
this.println("Content-Length: "+(finLength-(int)start));
this.println("Content-Range: bytes "+start+"-"+finLength+"/"+finLength);
this.println();
fastOutput(this.fin, this.out, this.outputBuffer, start);
}
}
catch (FileNotFoundException e)
{
this.doLog(INFO, getRequestMethod()+" "+fileName+" failed : 404 Not Found!"); ////写log
sendError(NOT_FOUND);
}
}
catch (NumberFormatException nfe)
{
this.doLog(INFO, getRequestMethod()+" "+fileName+" failed : 400 Bad Request!"); ////写log
sendError(BAD_REQUEST);
}
} ////end if 断点续传
else
{
try
{
this.fin = new BufferedInputStream(new FileInputStream(f));
int finLength = this.fin.available();
////如果finLength<0,相当于文件未找到,所以仍旧抛出FileNotFoundException
if (finLength < 0) throw new FileNotFoundException();
else
{
this.doLog(INFO, getRequestMethod()+" "+fileName); ////写log
this.println("HTTP/1.0 200 OK");
this.println("Server: Simple Web Server");
this.println("Connection: close");
this.println("Date: "+dateFormat.format(new Date()));
this.println("Content-Type: "+f.toURL().openConnection().getContentType());
this.println("Accept-Ranges: bytes");
this.println("Last-Modified: "+dateFormat.format(new Date(f.lastModified())));
this.println("Content-Length: "+finLength);
this.println();
fastOutput(this.fin, this.out, this.outputBuffer);
}
}
catch (FileNotFoundException e)
{
this.doLog(INFO, getRequestMethod()+" "+fileName+" failed : 404 Not Found!"); ////写log
sendError(NOT_FOUND);
}
}
}
}
////this.doLog(DEBUG,"sendFile(end) requestStr="+requestStr);
}
/**寻找并发送indexFiles列表里的文件
* @param dir 目录相对路径(结尾没有"/")*/
protected void sendIndexFile(String dir) throws IOException
{
////this.doLog(DEBUG,"sendIndexFile(start) requestStr="+requestStr);
String index = null;
////取得dir的绝对路径
String path = getAbsolutePath(dir);
////在dir目录下检索第一个存在的index文件
for (int i=0; i= 0)
{
this.doLog(INFO, getRequestMethod()+" "+dir+"/"+index.substring(index.lastIndexOf(File.separator)+1)+" OK : 200"); ////写log
this.println("HTTP/1.0 200 OK");
this.println("Server: Simple Web Server");
this.println("Date: "+dateFormat.format(new Date()));
this.println("Content-Type: text/html");
this.println("Accept-Ranges: bytes");
this.println("Last-Modified: "+dateFormat.format(new Date(new File(index).lastModified())));
this.println("Content-Length: "+finLength);
this.println();
fastOutput(this.fin, this.out, this.outputBuffer);
}
}
catch (FileNotFoundException e)
{
this.doLog(INFO, getRequestMethod()+" "+dir+"/"+index.substring(index.lastIndexOf(File.separator)+1)+" failed : 404 Not Found!"); ////写log
////如果allowListFiles为true则列出文件目录下的所有文件
if (allowListFiles) listFiles(dir);
else sendError(NOT_FOUND);
}
}
else
{
////如果allowListFiles为true则列出文件目录下的所有文件
if (allowListFiles) listFiles(dir);
else sendError(NOT_FOUND);
}
////this.doLog(DEBUG,"sendIndexFile(end) requestStr="+requestStr);
}
/**发送错误*/
protected void sendError(String errorType)
throws IOException
{
////this.doLog(DEBUG,"sendError() requestStr="+requestStr);
////NOT_FOUND
if (errorType.equals(NOT_FOUND))
{
this.println("HTTP/1.0 404 Not Found");
this.println("Server: Simple Web Server");
this.println("Date: "+dateFormat.format(new Date()));
this.println("Content-Type: text/html");
try
{
String errorFile = (String)properties.get(NOT_FOUND);
if (errorFile == null) throw new FileNotFoundException();
else
{
this.fin = new BufferedInputStream(new FileInputStream(errorFile));
int finLength = this.fin.available();
////如果finLength为0,相当于文件未找到,所以仍旧抛出FileNotFoundException
if (finLength <= 0) throw new FileNotFoundException();
else
{
this.println("Accept-Ranges: bytes");
this.println("Content-Length: "+finLength);
this.println();
fastOutput(this.fin, this.out, this.outputBuffer);
}
}
}
catch (FileNotFoundException e)
{
this.println("Content-Length: "+default_NOT_FOUND.getBytes().length);
this.println();
this.println(default_NOT_FOUND);
}
}
////BAD_REQUEST
else if (errorType.equals(BAD_REQUEST))
{
this.println("HTTP/1.0 400 Bad Request");
this.println("Server: Simple Web Server");
this.println("Date: "+dateFormat.format(new Date()));
this.println("Content-Type: text/html");
try
{
String errorFile = (String)properties.get(BAD_REQUEST);
if (errorFile == null) throw new FileNotFoundException();
else
{
this.fin = new BufferedInputStream(new FileInputStream(errorFile));
int finLength = this.fin.available();
////如果finLength为0,相当于文件未找到,所以仍旧抛出FileNotFoundException
if (finLength <= 0) throw new FileNotFoundException();
else
{
this.println("Accept-Ranges: bytes");
this.println("Content-Length: "+finLength);
this.println();
fastOutput(this.fin, this.out, this.outputBuffer);
}
}
}
catch (FileNotFoundException e)
{
this.println("Content-Length: "+default_BAD_REQUEST.getBytes().length);
this.println();
this.println(default_BAD_REQUEST);
}
}
////FORBIDDEN
else if (errorType.equals(FORBIDDEN))
{
this.println("HTTP/1.0 400 Bad Request");
this.println("Server: Simple Web Server");
this.println("Date: "+dateFormat.format(new Date()));
this.println("Content-Type: text/html");
try
{
String errorFile = (String)properties.get(FORBIDDEN);
if (errorFile == null) throw new FileNotFoundException();
else
{
this.fin = new BufferedInputStream(new FileInputStream(errorFile));
int finLength = this.fin.available();
////如果finLength为0,相当于文件未找到,所以仍旧抛出FileNotFoundException
if (finLength <= 0) throw new FileNotFoundException();
else
{
this.println("Accept-Ranges: bytes");
this.println("Content-Length: "+finLength);
this.println();
fastOutput(this.fin, this.out, this.outputBuffer);
}
}
}
catch (FileNotFoundException e)
{
this.println("Content-Length: "+default_FORBIDDEN.getBytes().length);
this.println();
this.println(default_FORBIDDEN);
}
}
}
/************************* protected method end **********************/
/************************* public method start: **********************/
/**返回请求信息*/
public String getRequestMessage()
{ return this.requestStr; }
/**返回请求method,如GET POST HEAD OPTIONS PUT DELETE TRACE等*/
public String getRequestMethod()
{
////this.doLog(DEBUG,"getRequestMethod() requestStr="+requestStr);
try
{
return this.requestStr.substring(0, this.requestStr.indexOf(´ ´)).toUpperCase();
}
catch (Exception e)
{
return null;
}
}
/**返回请求文件名,如:"/index.html"(已decode)*/
public String getRequestFileName() throws UnsupportedEncodingException
{
////this.doLog(DEBUG,"getRequestFileName() requestStr="+requestStr);
try
{
String s = this.requestStr.substring(this.requestStr.indexOf(´ ´)+1, this.requestStr.indexOf(´\n´));
////如果包含?号后面的参数则只取?号前面的文件名
if (s.indexOf(´?´) < 0) s = s.substring(0, s.lastIndexOf(´ ´));
else s = s.substring(0, s.indexOf(´?´));
return URLDecoder.decode(s, Encode);
}
catch (Exception e)
{
return null;
}
}
/**解析出附在URL路径后面的key=value对(已decode)*/
public String getQueryString() throws UnsupportedEncodingException
{
////this.doLog(DEBUG,"getQueryString() requestStr="+requestStr);
try
{
String s = this.requestStr.substring(this.requestStr.indexOf(´ ´)+1, this.requestStr.indexOf(´\n´));
////如果URL后面没有key=value对则返回null
if (s.indexOf(´?´) < 0) return null;
else s = s.substring(s.indexOf(´?´)+1, s.lastIndexOf(´ ´));
return URLDecoder.decode(s, Encode);
}
catch (Exception e)
{
return null;
}
}
/**解析出path在本地机上完整的路径*/
public String getAbsolutePath(String path) throws IOException
{
////this.doLog(DEBUG,"getAbsolutePath() requestStr="+requestStr);
return new File(rootPath + path.replace(´/´,File.separatorChar)).getCanonicalPath();
}
/**将指定目录文件列出
* @param d 目录相对路径(结尾没有"/")*/
public void listFiles(String d) throws IOException
{
////this.doLog(DEBUG,"listFiles(start) requestStr="+requestStr);
if (!isAllowAccess(d))
{
////超出rootPath范围,不允许访问
sendError(FORBIDDEN);
}
else
{
File dir = new File(getAbsolutePath(d));
if (dir.exists())
{
this.doLog(INFO, getRequestMethod()+" "+d+"/ list dirctory files !"); ////写log
String[] fileList = dir.list();
StringBuffer sb = new StringBuffer(""+separator+"Directory List"+separator+""+separator);
sb.append("http://");
sb.append(serverName);
String portTmp = (String)properties.get("Port");
////如果端口为80,则不显示端口号
if (!portTmp.equals("80")) sb.append(":"+portTmp);
if (d.length() == 0)
{
sb.append("/");
sb.append(d);
}
else
{
sb.append(d);
sb.append("/");
}
sb.append("
"+separator);
////上级目录链接。如果d.length()==0说明已经是根目录了,则不显示上级目录链接
if (d.length() != 0) sb.append("
"+separator);
for (int i=0; i { if ((new File(getAbsolutePath(d+"/"+fileList[i]))).isDirectory()) sb.append("Directory--"+fileList[i]+"/ "+separator); else sb.append(""+fileList[i]+" "+separator); } sb.append(" Simple Web Server "+VERSION+" by hcc"+separator+""+separator+""); String sTemp = sb.toString(); this.println("HTTP/1.0 200 OK"); this.println("Server: Simple Web Server"); this.println("Date: "+dateFormat.format(new Date())); this.println("Content-Type: text/html"); this.println("Content-Length: "+sTemp.getBytes().length); this.println(); this.println(sTemp); } else sendError(NOT_FOUND); } ////this.doLog(DEBUG,"listFiles(end) requestStr="+requestStr); } /**返回唯一标识*/ public String getID() { return this.ID; } /**equals()*/ public boolean equals(Object o) { if (o instanceof SimpleWebServer) return this.ID.equals(((SimpleWebServer)o).getID()); else return false; } /**hashCode()*/ public int hashCode() { return this.ID.hashCode(); } /**全部的运行代码都在这里面*/ public void run() { try { if (!interrupted()) ////如果没被中断就执行 { this.requestStr = getRequestString(); String rm = getRequestMethod(); ////this.doLog(DEBUG,"run(start) requestStr="+requestStr); if (rm != null) ////请求为null,则不做任何响应(如客户端已断开或刷新) { ////执行GET请求 if (rm.equals("GET")) doGet(); else sendError(BAD_REQUEST); this.out.flush(); } } } catch (Exception e) { ////打印出错信息到LOG文件 ////如果是InterruptedException,则什么也不做,因为WebServer shutdown时此线程被Interrupted if (!(e instanceof InterruptedException)) this.log.error(e); } finally { destroy(); } ////this.doLog(DEBUG,"run(end) requestStr="+requestStr); } /**destroy():关闭WebServer Class中定义的所有I/O流*/ public void destroy() { try { this.out.close(); } catch (Exception ignored) {} ////out不throw IOException this.out = null; try { this.fin.close(); } catch (Exception ignored) {} this.fin = null; try { this.in.close(); } catch (Exception ignored) {} this.in = null; } } SimpleWebServerManager ========================================= package hcc; import java.io.*; import java.net.*; import java.util.*; import java.text.*; import EDU.oswego.cs.dl.util.concurrent.*; /*************负责建立ServerSocket并创建、销毁SimpleWebServer***************/ public class SimpleWebServerManager extends Thread { ////URL的Encode(默认为UTF-8) private static String Encode = "UTF-8"; ////默认日志文件名 private static final String defaultLogFileName = "log.txt"; ////port private int port = 80; ////参数文件 private String iniFile = "webserver.ini"; ////日志文件名 private String logFileName = null; ////用于读取参数文件的 private BufferedInputStream readini = null; ////存放参数 private static ConcurrentReaderHashMap ini = new ConcurrentReaderHashMap(); //所有线程可以并发读取,但只能同步写入的HashMap ////线程池 private PooledExecutor pool = null; ////线程池中的最多线程数 private int maxPoolSize = 10; ////线程池中的最少线程数 private int minPoolSize = 1; ////请求队列大小,超出此数的请求被拒绝 private static int requestQueueSize = 50; ////HTTP协议中的日期格式 private static final SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz",Locale.US); ////index private static String[] indexFiles = null; ////超时时间,指对方连接后的不发送任何数据的超时时间(5秒) private static final int TIMEOUT = 5 * 1000; private static int ID = 0; private ServerSocket ss = null; private Socket socket = null; ////参数文件最后修改时间 private long iniFileLastModified = 0L; ////此类的唯一实例引用 private static SimpleWebServerManager swmInstance = null; ////log private WebServerLogger log = null; private static final Object lock = new Object(); /** * 构造器 * * @exception IllegalArgumentException 参数错误 * @exception FileNotFoundException 参数文件未找到 * @exception IOException 读取参数文件出错 */ public SimpleWebServerManager() throws IllegalArgumentException, FileNotFoundException, IOException { this.initProperties(); this.pool = new PooledExecutor(new BoundedBuffer(10), this.maxPoolSize); this.pool.setKeepAliveTime(1000 * 60 * 5); ////线程池里的线程存活时间 this.pool.setMinimumPoolSize(this.minPoolSize); this.pool.createThreads(3); start(); } /** * 返回唯一实例。采用线程安全的Lazy Singleton设计模式 * * @return SimpleWebServerManager实例 * @exception IllegalArgumentException 参数错误 * @exception FileNotFoundException 参数文件未找到 * @exception IOException 读取参数文件出错 */ public static SimpleWebServerManager getInstance() throws IllegalArgumentException, FileNotFoundException, IOException { if (swmInstance == null) { synchronized(lock) { ////双重保护,保证只实例化一次 if (swmInstance == null) swmInstance = new SimpleWebServerManager(); } } return swmInstance; } /** * 读取参数文件,并检查参数。 * * 首先使用Properties载入参数,然后存入异步读取的HashMap,以提高并发线程的访问效率 * * @exception IllegalArgumentException 参数语法错误 * @exception FileNotFoundException 参数文件未找到 * @exception IOException 读取参数文件出错 */ protected void initProperties() throws IllegalArgumentException, FileNotFoundException, IOException { //// 检查属性文件是否被其它程序或手动修改过,如果是,重新读取此文件 File f = new File(this.iniFile); if (f.exists()) { long newLastModified = f.lastModified(); if (newLastModified == 0) throw new IOException("参数文件:"+this.iniFile+" 读取出错!"); else if (newLastModified > this.iniFileLastModified) ////文件已更改 { this.iniFileLastModified = newLastModified; Properties iniTmp = new Properties(); ////用于载入参数 this.readini = new BufferedInputStream(new FileInputStream(this.iniFile)); iniTmp.load(this.readini); ConcurrentReaderHashMap c = new ConcurrentReaderHashMap(iniTmp); iniTmp = null; /******************** 检查参数合法性 ***********************/ /******** 必须重启才能改变的参数 *******/ if (ini.isEmpty()) ////首次读取参数文件 { ////port try { this.port = Integer.parseInt((String)c.get("Port")); } catch (NumberFormatException nfe) { throw new IllegalArgumentException("参数出错!端口号必须为整数 (Property Error : \"Port\")"); } ////log file name this.logFileName = (String)c.get("LogFile"); if (this.logFileName==null || this.logFileName.equals("")) this.logFileName = defaultLogFileName; ////线程池中最多线程数 try { this.maxPoolSize = Integer.parseInt((String)c.get("MaxPoolSize")); } catch (NumberFormatException nfe) { throw new IllegalArgumentException("参数出错!线程池最多线程数必须为整数 (Property Error : \"MaxPoolSize\")"); } ////线程池中最少线程数 try { this.minPoolSize = Integer.parseInt((String)c.get("MinPoolSize")); } catch (NumberFormatException nfe) { throw new IllegalArgumentException("参数出错!线程池最少线程数必须为整数 (Property Error : \"MinPoolSize\")"); } /******************** 添加其它全局参数 ***********************/ /**直接添加进ini即可**/ ////启动WebServerLogger this.log = new WebServerLogger(this.logFileName); ini.put("Logger", this.log); ////URL的Encode ini.put("Encode", Encode); ////添加服务器IP地址到ini参数里 ini.put("ServerIP", InetAddress.getLocalHost().getHostAddress()); ////添加HTTP协议中的日期格式到参数里(SimpleDateFormat类型) sdf.setTimeZone(TimeZone.getTimeZone("GMT")); ini.put("DateFormat", sdf); ////添加服务器所在操作系统的换行符 ini.put("Separator", System.getProperty("line.separator")); /******************** 添加完毕 ***********************/ } else { ////从c中删除重启才能改变的参数 c.remove("Port"); c.remove("LogFile"); c.remove("MaxPoolSize"); c.remove("MinPoolSize"); } /******** 无须重启即可改变的参数 *******/ ////root path String rp = (String)c.get("Root"); if (rp!=null && !(new File(rp).exists())) throw new IllegalArgumentException("参数出错!根路径不存在 (Property Error : \"Root\" not exist)"); ////index files String indexs = (String)c.get("Index"); if (indexs!=null && !indexs.equals("")) { StringTokenizer token = new StringTokenizer(indexs, ","); indexFiles = new String[token.countTokens()]; for (int i=0; i { indexFiles[i] = token.nextToken(); } ////把参数里String型的index替换成String[]型 c.put("Index", indexFiles); } else throw new IllegalArgumentException("参数出错!index 文件名不能为空 (Property Error : Index Files name must not null)"); ////是否允许列出当前目录文件 String listFiles = (String)c.get("ListFiles"); if (!listFiles.equalsIgnoreCase("yes") && !listFiles.equalsIgnoreCase("no")) throw new IllegalArgumentException("参数出错!ListFiles 参数不能为空 (Property Error : \"ListFiles\" must \"yes\" or \"no\")"); ////output缓存大小 try { String o = (String)c.get("OutputBuffer"); if (o==null || o.equals("")) o = "128"; Integer.parseInt(o); ////只需检查一下即可 } catch (NumberFormatException nfe) { throw new IllegalArgumentException("参数出错!发送缓存大小必须为整数 (Property Error : \"OutputBuffer\")"); } /******************** 检查完毕 ***********************/ ini.putAll(c); ////存入参数HashMap中,替换掉原参数 c = null; } } else throw new FileNotFoundException("参数文件:"+this.iniFile+" 被删除了!"); } /** * get端口号 * * @return 端口号 */ public int getPort() { return this.port; } /** * get参数HashMap * * @return 参数HashMap */ protected Map getProperties() { return ini; } /** * get参数文件名 * * @return 参数文件名 */ public String getPropertiesFileName() { return this.iniFile; } public void run() { try { this.ss = new ServerSocket(this.port, requestQueueSize); ////不停接收客户请求 while(!interrupted()) { try { this.socket = this.ss.accept(); this.socket.setSoTimeout(TIMEOUT); this.initProperties(); pool.execute(new SimpleWebServer(this.socket, String.valueOf(++this.ID), ini)); } catch (SocketException e) {/*无需处理此Exception,程序中止时会关闭ServerSocket,所以accept()会throw此Exception*/} catch (Exception e) { ////其它例外 log.error(e.getMessage()); } } } catch (Exception e) { /*////如果是InterruptedException,则什么也不做,因为WebServer shutdown时此线程被Interrupted if (!(e instanceof InterruptedException)) { System.out.println("\nError: "+e.getMessage()+" Please shutdown!"); System.out.println(); }*/ log.error(e.getMessage()); System.err.println(); System.err.println("Error: "+e.getMessage()+" Please shutdown!"); System.err.println(); } } public void destroy() { ////中止线程池里的所有线程 this.pool.shutdownAfterProcessingCurrentlyQueuedTasks(); try { this.socket.close(); } catch (Exception ignored) {} this.socket = null; try { this.ss.close(); } catch (Exception ignored) {} this.ss = null; try { this.readini.close(); } catch (Exception ignored) {} this.readini = null; this.log.closeLog(); this.log = null; } } WebServerLogger:===================================================== package hcc; import java.io.*; import java.text.*; import java.util.*; public class WebServerLogger { ////暂存log信息 private StringBuffer messTmp = null; ////用于log中的时间格式 private static final DateFormat logDateFormat = DateFormat.getDateTimeInstance(); ////用于写log的流(多个实例共用) private static PrintWriter log = null; //注意:PrintWriter已经synchronized了 /** * WebServerLogger的构造器 * * @param logFileName 日志文件名(可以包含路径) * @exception IOException 打开log文件时出错 */ public WebServerLogger(String logFileName) throws IOException { log = new PrintWriter(new BufferedWriter(new FileWriter(logFileName, true)), true); } /** * 输出log * * @param messageType 信息类型,比如error,warning * @param message 信息内容 */ protected void doLog(String messageType, String message) { try { messTmp = new StringBuffer(); messTmp.append(messageType+this.logDateFormat.format(new Date())); messTmp.append(" ---- "); messTmp.append(message); log.println(messTmp.toString()); log.flush(); } catch (Exception ignored) {} } /** * 输出log * * @param messageType 信息类型,比如error,warning * @param exception 错误 */ protected void doLog(String messageType, Throwable exception) { try { messTmp = new StringBuffer(); messTmp.append(messageType+this.logDateFormat.format(new Date())); messTmp.append(" ---- "); messTmp.append(exception.toString()); messTmp.append(System.getProperty("line.separator")); StackTraceElement[] ste = exception.getStackTrace(); for (int i=0,n=ste.length;i { messTmp.append(ste[i].toString() + System.getProperty("line.separator")); } log.println(messTmp.toString()); log.flush(); } catch (Exception ignored) {} } /** * error信息 * * @param message 信息内容 */ public void error(String message) { this.doLog("Error: ", message); } /** * error信息 * * @param throwable 错误 */ public void error(Throwable throwable) { this.doLog("Error: ", throwable); } /** * warning信息 * * @param message 信息内容 */ public void warning(String message) { this.doLog("Warning: ", message); } /** * warning信息 * * @param throwable 错误 */ public void warning(Throwable throwable) { this.doLog("Warning: ", throwable); } /** * 普通信息 * * @param message 信息内容 */ public void info(String message) { this.doLog("", message); } /** * 普通信息 * * @param throwable 错误 */ public void info(Throwable throwable) { this.doLog("", throwable); } /** * 普通信息 * * @param message 信息内容 */ public void debug(String message) { this.doLog("DEBUG: ", message); } /** * 普通信息 * * @param throwable 错误 */ public void debug(Throwable throwable) { this.doLog("DEBUG: ", throwable); } /** * 关闭log流(连接线程不调用) */ public void closeLog() { try { log.close(); } catch (Exception ignored) {} ////log 不 throw IOException } } webserver.ini ================================================== ############## 修改此部分参数,需要重新启动才生效 ############## #服务端口号 Port=80 #日志文件名 LogFile=log.txt #线程池中最多和最少线程数(一般无需改动) MaxPoolSize=10 MinPoolSize=3 ############## 修改此部分参数,无需重启,立即生效 ############## #serverName : 服务器名字,如:www.mywebsite.com ServerName=mywebsite #请用"/"或"\\"代替"\" Root=e:/SHARE #index文件名 Index=index.html,index.htm #是否列出目录下文件 ListFiles=yes #发送缓存大小,单位:KB(一般无需改动) OutputBuffer=256 补充说明:===================================================== 可以自定义错误页面,只需在webserver.ini文件中加入如下内容: NOT_FOUND=“文件未找到”出错页面的文件名。如:not_found.html BAD_REQUEST=“错误的请求参数”出错页面的文件名。 INTERNAL_ERROR=“服务器内部错误”出错页面的文件名。 FORBIDDEN=“无权访问”出错页面的文件名。 其他错误类型可以在代码中自己添加。 ================================================================ BTW:大家尽量的公开源码有助于本行业的发展,尤其是在国内就更重要。 我以后会把我写的绝大部分程序的源码公开。

