本文仅仅是实现一个功能非常有限的http服务器。我仅仅实现了请求一个html和请求一个jpg图片。方式只支持GET。不支持http选项。错误代码仅仅会返回200 400 404.支持xml对服务器的配置。本博客内容仅仅完全处于自身娱乐,高手可直接略过。
1用java的sax解析服务器配置文件。确定web服务器的root目录,和web服务器运行的端口号。
2启动一个serverSocket等待链接
3获取一个链接之后把所获得的socket传递给新的httpSolver线程。httpSolver负责解析客户端发来的http请求头。
4如果httpSolver根据服务器请求的文件建立一个HttpMessage类。这个类封装了http请求的消息,包括请求的文件,root目录等等。这个httpMessage传递给GetDisk类
5GetDisk负责从硬盘读取。如果java发现请求的文件不在,那么在返回404.如果请求文件成功那么构造消息头返回200在返回锁请求文件。
6httpSolver关闭socket结束一个回话。
server.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE server SYSTEM "server.dtd">
<server>
<port>80</port>
<root>d:\Root\</root>
</server>
server.dtd
<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT server (port,root?)>
<!ELEMENT port (#PCDATA)>
<!ELEMENT root (#PCDATA)>
Main.java
package httpServer;
public class Main {
public static void main(String args[])
{
ServerClass myServer=new ServerClass();
myServer.serverStart();
}
}
public class ServerClass {
private ServerSocket serverSocket;
private String root;
private boolean runing;
private int port;
public ServerClass()
{
runing=false;
SAXParserFactory saxpf=SAXParserFactory.newInstance();
SAXParser saxParser;
try (InputStream in=new FileInputStream("server.xml"))
{
saxParser=saxpf.newSAXParser();
saxParser.parse(in, new Handler());
}
catch (FileNotFoundException e) {
// TODO Auto-generated catch block
System.out.println("server.xml no find");
e.printStackTrace();
}
catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("parse server.xml error");
e.printStackTrace();
}
}
public void serverStart()
{
try {
serverSocket=new ServerSocket(port);
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("ServerSocket cann‘t establish");
e.printStackTrace();
}
runing=true;
System.out.println("System is runing");
while(runing)
{
try {
Socket incomingSocket=serverSocket.accept();
new Thread(new requestHandler(incomingSocket,root)).start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("System deal one request");
}
System.out.println("System is down");
}
public void shutDown()
{
runing=false;
}
private class Handler extends DefaultHandler
{
//单独介绍
}
}
上面的代码就是调用xml解析工具对服务器的一些基本的内容进行设置。因为我们只对配置文件进行读取操作,所以我们可以简单的使用sax作为简单的解析工具.不用使用dom.之后创建一个serversocket等待链接。
在serverclass里面有一个私有的内部嵌套类
private class Handler extends DefaultHandler
{
private boolean isPort;
private boolean isRoot;
public Handler()
{
super();
isPort=false;
isRoot=false;
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
super.characters(ch, start, length);
String temp=new String(ch,start,length);
if(isPort)
{
port=Integer.parseInt(temp);
isPort=false;
}
else if(isRoot)
{
root=temp;
isRoot=false;
}
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
if(qName.equals("port"))
{
isPort=true;
}
else if(qName.equals("root"))
{
isRoot=true;
}
else if(qName.equals("server"))
{
//不操作
}
else
{
System.out.println("error xml element");
}
}
这个类是用于处理sax解析过程的.因为sax在解析的时候是事件回调的方式.我们要重载DefaultHandler里面的函数.sax在解析的过程中,如果碰到element那么久调用startElement函数,并且把元素等等的名字作为参数传递到里面.当一个element结束的时候调用endElement函数.DefaultHandler里面的各个函数都是空操作.所以我们要进行重载来实现我们想要的操作.私有变量isroot等等是因为在进入元素的时候sax统一调用startElement,碰到里面的值得时候比如 < root > D:\root < / root >在碰到D:\root 的时候会调用characters。但是我们怎么知道D:\root是给root的?就是设置一个isroot变量。表示现在的在处理root元素。把里面的值给root。因为其他的元素比如< port > 80 < / port >碰到80的时候同样会调用characters。
class requestHandler implements Runnable
{
Socket incomingSocket;
String root;
public requestHandler(Socket incomingSocket,String root) {
this.incomingSocket=incomingSocket;
this.root=root;
}
@Override
public void run() {
new httpSolver(incomingSocket,root).serve();
}
}
这个主要是为了建立一个线程.用于同时处理很多很多请求.
public class httpSolver {
Socket incomingSocket;
String root;
public httpSolver(Socket incomingSocket,String root) {
this.incomingSocket=incomingSocket;
this.root=root;
}
public void serve()
{
Scanner in=null;
OutputStream out=null;
try {
in=new Scanner(incomingSocket.getInputStream());
// out=new PrintWriter(incomingSocket.getOutputStream(),true);
out=incomingSocket.getOutputStream();
HttpMessage httpMessage=new HttpMessage();
httpMessage.root=root;
while(in.hasNextLine())
{
String input=in.nextLine();
String splitResult[]=null;
if(input.startsWith("GET")||input.startsWith("get"))
{
splitResult=input.split(new String(" "));
if(!splitResult[2].equals("HTTP/1.1"))
{
new badRequest().work(out);
incomingSocket.close();
break;
}
if(splitResult[1].equals("/"))
{
httpMessage.targetFile="index.html";
}
else
{
httpMessage.targetFile=splitResult[1].substring(1,splitResult[1].length());
}
}
else
{
splitResult=input.split(new String(":"));
switch (splitResult[0]) {
case "If-Modified-Since":
httpMessage.If_Modified_Since=true;
httpMessage.If_Modified_SinceString=splitResult[1];
break;
case "":
new GetDisk(httpMessage).work(out);
incomingSocket.close();
break;
default:
break;
}
}
}
}
catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("socket io exception");
e.printStackTrace();
}
finally
{
try
{
if(in!=null)
{
in.close();
}
if(out!=null)
{
out.close();
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
}
这里面包括了一主要的读取循环。不断读取客户端发来的信息。通过解析字符串发现客户端请求的是什么。我们把消息封装到一个HttpMessage里面。这样便于消息的传递,可以把客户端传来的信息都放到HttpMessage里面传递给其他的处理类,比如GetDisk用于读入文件。HttpMessage可以理解为C语言里面的结构体
//HttpMessage.java
public class HttpMessage {
public String root;
public String targetFile;
public boolean If_Modified_Since=false;
public String If_Modified_SinceString=null;
}
这个类主要处理从磁盘读取文件。并且把应该返回的http头和http内容都拼装起来。其实这里可以使用 原型设计模式 但是我这里只有几个返回形式。所以直接写在了代码里。但这样对代码维护会造成困难。为了便于加快文件的读取速度。读入输出都是使用二进制方式进行。
public class GetDisk
{
private HttpMessage httpMessage;
private StringBuilder result;
private Path path;
public GetDisk(HttpMessage httpMessage)
{
result=new StringBuilder();
this.httpMessage=httpMessage;
path=Paths.get(this.httpMessage.root,this.httpMessage.targetFile);
}
public void work(OutputStream out)
{
if(!Files.exists(path))
{
result.append("HTTP/1.1 404 Not Found\r\nConnection: close\r\n");
result.append("\r\n");
try {
out.write(result.toString().getBytes());
out.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return;
}
byte[] fileContents=null;
try {
fileContents=Files.readAllBytes(path);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(fileContents!=null)
{
result.append("HTTP/1.1 200 OK\r\n");
if(httpMessage.targetFile.endsWith("html"))
{
result.append( "Content-Type: text/html; charset=utf-8\r\n");
}
else if(httpMessage.targetFile.endsWith("jpg"))
{
result.append( "Content-Type: image/jpeg\r\n");
}
result.append( "Keep-Alive: -1\r\n");
result.append("\r\n");
try {
out.write(result.toString().getBytes());
out.write(fileContents);
out.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return;
}
}
主要返回无法解析的指令错误。其实我们可以把每个返回代码的处理过程都实现为一个类。然后这些类都共同的继承一个超类。在超类中实现写好http返回头。然后参数有个个子类给出就是使用模板设计模式。方便代码的管理。
public class badRequest {
StringBuilder result;
public badRequest()
{
result=new StringBuilder();
}
public void work(OutputStream out)
{
result.append("HTTP/1.1 400 bad request\r\nConnection: close\r\n");
result.append("\r\n");
try {
out.write(result.toString().getBytes());
out.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return;
}
}
最终效果
想要下载全部文件我已经放到了gitoschina 上面地址
项目地址
原文地址:http://blog.csdn.net/bleuesprit/article/details/45645467