分享
 
 
 

一个简单的 Web Server(build 2002-8-20)

王朝java/jsp·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

下面是源码,如果需要打包的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:大家尽量的公开源码有助于本行业的发展,尤其是在国内就更重要。

我以后会把我写的绝大部分程序的源码公开。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有