本文仅仅是实现一个功能非常有限的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结束一个回话。
工程文件管理方式
xml配置文件
server.xml
1
2
3
4
5
6
|
<code
class
=
"language-xml"
hljs=
""
><!--?xml version=
1.0
encoding=UTF-
8
?-->
<server>
<port>
80
</port>
<root>d:Root</root>
</server></code>
|
server.dtd
1
2
3
4
5
|
<code
class
=
"language-xml"
hljs=
""
><!--?xml version=
1.0
encoding=UTF-
8
?-->
<!--ELEMENT server (port,root?)-->
<!--ELEMENT port (#PCDATA)-->
<!--ELEMENT root (#PCDATA)--></code>
|
Main.java
Main.java
1
2
3
4
5
6
7
8
9
|
<code
class
=
"language-java"
hljs=
""
>
package
httpServer;
public
class
Main {
public
static
void
main(String args[])
{
ServerClass myServer=
new
ServerClass();
myServer.serverStart();
}
}</code>
|
ServerClass.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
<code
class
=
"language-java"
hljs=
""
>
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
{
//单独介绍
}
}</code>
|
上面的代码就是调用xml解析工具对服务器的一些基本的内容进行设置。因为我们只对配置文件进行读取操作,所以我们可以简单的使用sax作为简单的解析工具.不用使用dom.之后创建一个serversocket等待链接。
在serverclass里面有一个私有的内部嵌套类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
<code
class
=
"language-java"
hljs=
""
>
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);
}
}
</code>
|
这个类是用于处理sax解析过程的.因为sax在解析的时候是事件回调的方式.我们要重载DefaultHandler里面的函数.sax在解析的过程中,如果碰到element那么久调用startElement函数,并且把元素等等的名字作为参数传递到里面.当一个element结束的时候调用endElement函数.DefaultHandler里面的各个函数都是空操作.所以我们要进行重载来实现我们想要的操作.私有变量isroot等等是因为在进入元素的时候sax统一调用startElement,碰到里面的值得时候比如 < root > D: oot < / root >在碰到D: oot 的时候会调用characters。但是我们怎么知道D: oot是给root的?就是设置一个isroot变量。表示现在的在处理root元素。把里面的值给root。因为其他的元素比如< port > 80 < / port >碰到80的时候同样会调用characters。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<code
class
=
"language-java"
hljs=
""
>
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();
}
}</code>
|
这个主要是为了建立一个线程.用于同时处理很多很多请求.
httpSolver.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
<code
class
=
"language-java"
hljs=
""
>
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();
}
}
}
}
</code>
|
这里面包括了一主要的读取循环。不断读取客户端发来的信息。通过解析字符串发现客户端请求的是什么。我们把消息封装到一个HttpMessage里面。这样便于消息的传递,可以把客户端传来的信息都放到HttpMessage里面传递给其他的处理类,比如GetDisk用于读入文件。HttpMessage可以理解为C语言里面的结构体
HttpMessage.java
1
2
3
4
5
6
7
|
<code
class
=
"language-java"
hljs=
""
>
//HttpMessage.java
public
class
HttpMessage {
public
String root;
public
String targetFile;
public
boolean
If_Modified_Since=
false
;
public
String If_Modified_SinceString=
null
;
}</code>
|
GetDisk.java
这个类主要处理从磁盘读取文件。并且把应该返回的http头和http内容都拼装起来。其实这里可以使用 原型设计模式 但是我这里只有几个返回形式。所以直接写在了代码里。但这样对代码维护会造成困难。为了便于加快文件的读取速度。读入输出都是使用二进制方式进行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
<code
class
=
"language-java"
hljs=
""
>
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
Connection: close
);
result.append(
);
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
);
if
(httpMessage.targetFile.endsWith(html))
{
result.append( Content-Type: text/html; charset=utf-
8
);
}
else
if
(httpMessage.targetFile.endsWith(jpg))
{
result.append( Content-Type: image/jpeg
);
}
result.append( Keep-Alive: -
1
);
result.append(
);
try
{
out.write(result.toString().getBytes());
out.write(fileContents);
out.flush();
}
catch
(IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return
;
}
}</code>
|
badRequest.java
主要返回无法解析的指令错误。其实我们可以把每个返回代码的处理过程都实现为一个类。然后这些类都共同的继承一个超类。在超类中实现写好http返回头。然后参数有个个子类给出就是使用模板设计模式。方便代码的管理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<code
class
=
"language-java"
hljs=
""
>
public
class
badRequest {
StringBuilder result;
public
badRequest()
{
result=
new
StringBuilder();
}
public
void
work(OutputStream out)
{
result.append(HTTP/
1.1
400
bad request
Connection: close
);
result.append(
);
try
{
out.write(result.toString().getBytes());
out.flush();
}
catch
(IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return
;
}
}</code>
|