下面是源码,如果需要打包的class文件,请与俺联系:[email protected] (注:程序中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 = "<html><title>File Not Found !</title><body bgcolor=\"#FFFFFF\"><h1>ERROR 404 : <br>File Not Found!</h1><br><br><hr><center><font face=\"Verdana\">Simple Web Server "+VERSION+"<b> by hcc</b></font></center></body></html>"; private static final String default_BAD_REQUEST = "<html><title>Bad Request !</title><body bgcolor=\"#FFFFFF\"><h1>ERROR 400 : <br>Bad Request !</h1><br><br><hr><center><font face=\"Verdana\">Simple Web Server "+VERSION+"<b> by hcc</b></font></center></body></html>"; private static final String default_INTERNAL_ERROR = "<html><title>Internal Error !</title><body bgcolor=\"#FFFFFF\"><h1>ERROR 500 : <br>Internal Error !</h1><br><br><hr><center><font face=\"Verdana\">Simple Web Server "+VERSION+"<b> by hcc</b></font></center></body></html>"; private static final String default_FORBIDDEN = "<html><title>Forbidden !</title><body bgcolor=\"#FFFFFF\"><h1>ERROR 403 : <br>Forbidden !</h1><br><br><hr><center><font face=\"Verdana\">Simple Web Server "+VERSION+"<b> by hcc</b></font></center></body></html>";
////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) buffSize = 256 * 1024; ////默认256KB byte[] buffer = new byte[buffSize]; int b;
while ((b=in.read(buffer)) != -1) { if (out.checkError()) { this.doLog(INFO,"传输被中止!"); break; } else out.write(buffer, 0, b); }
buffer = null; }
////高速output流(为实现206响应-断点续传) private void fastOutput(InputStream in, PrintStream out, int bufferSize, long skipBytes) throws IOException { in.skip(skipBytes); this.fastOutput(in, out, bufferSize); }
/************************* private method end ************************/
/************************* protected method start: *******************/
/** * 执行GET * * @exception IOException 网络原因 */ protected void doGet() throws IOException { ////this.doLog(DEBUG,"doGet(start) requestStr="+requestStr); sendFile(getRequestFileName()); ////this.doLog(DEBUG,"doGet(end) requestStr="+requestStr); }
/**验证请求文件路径是否合法(是否超出rootPath范围) * * @param path 相对路径 * @exception IOException 文件读取错误 */ protected boolean isAllowAccess(String path) throws IOException { ////this.doLog(DEBUG,"isAllowAccess() requestStr="+requestStr); return getAbsolutePath(path).indexOf(new File(rootPath).getCanonicalPath()) >= 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<indexFiles.length; i++) { if (new File(path+File.separator+indexFiles[i]).exists()) { index = path+File.separator+indexFiles[i]; break; } }
if (index != null) { try { this.fin = new BufferedInputStream(new FileInputStream(index)); int finLength = this.fin.available(); if (finLength >= 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("<html>"+separator+"<title>Directory List</title>"+separator+"<body bgcolor=\"#FFFFFF\">"+separator); sb.append("<font face=\"Times New Roman\"><h1>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("</h1></font><br><hr><br><br>"+separator); ////上级目录链接。如果d.length()==0说明已经是根目录了,则不显示上级目录链接 if (d.length() != 0) sb.append("<-- <a href=\""+d.substring(0, d.lastIndexOf("/")+1)+"\">Parent Directory</a><br><br>"+separator);
for (int i=0; i<fileList.length; i++) { if ((new File(getAbsolutePath(d+"/"+fileList[i]))).isDirectory()) sb.append("Directory--<a href=\""+fileList[i]+"/\">"+fileList[i]+"/</a><br><br>"+separator); else sb.append("<a href=\""+fileList[i]+"\">"+fileList[i]+"</a><br><br>"+separator); } sb.append("<br><hr><center><font face=\"Verdana\">Simple Web Server "+VERSION+"<b> by hcc</b></font></center>"+separator+"</body>"+separator+"</html>"); 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.length; 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<n;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:大家尽量的公开源码有助于本行业的发展,尤其是在国内就更重要。
我以后会把我写的绝大部分程序的源码公开。 
|