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

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

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

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值