下面是源码,如果需要打包的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 = "<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:大家尽量的公开源码有助于本行业的发展,尤其是在国内就更重要。
我以后会把我写的绝大部分程序的源码公开。