java实现简单的http服务器

前言

之前的一次课设上面做了一个作业。其中的java不使用tomcat来进行通信的部分。
首先来说一说“java实现简单的http服务器”这个东西是什么意思。我们使用浏览器进行访问时我们是客户端,向服务器寻求我们需要的网页资料。
我们打开的html网页,这是一个静态的文件,还有一众Css文件,js文件以及图片文件等等。我们通过向服务器发送get等方式的请求进行获取,然后接受服务器发回来的文件,通过浏览器的组织和加载之后就可以得到我们所看到的网页的内容了。
我们服务器是通过输入一个网址(URL)来进行内容的定位,然后发送过去一些http请求,服务器上有一个服务来对这些http请求进行处,查找客户端所需要的内容。将这些内容反馈发给客户端。


预备知识提纲

首先需要了解HTTP协议,我们不使用tomcat服务器,不使用Java已经封装好的HttpServlet类来进行get方法以及post方法的反馈。我们写的这个功能就是自主实现了非常小的tomcat的功能。
因为我们的网络通信都是基于socket的,仔细想一下,其实所有的内容都是通过socket完成的。浏览器之所以能够进行数据的传输是因为使用80端口(Apache等使用的端口),所有的软件的使用都是通过socket的传输数据在应用层上进行了内容的开发,然后完成的某些功能成为了一个应用(个人拙见)。我们这里要做的就是通过8080端口(Tomcat使用的端口)来完成原本tomcat的工作。

  • socket基本使用
  • HTTP协议

socket基本使用

这个东西也就不多讲了吧。java基础,在C语言里面socket只有一种数据结构类型,使用的时候分为Server的socket和普通的socket使用。但是在java由于比较好的封装性分为ServerSocket和Socket两个类,照着写就差不多了。

HTTP协议

这个协议比较重要,因为在B/S架构下浏览器和服务器之间的数据传递是根据HTTP协议来完成的。需要了解http的发送和接收的报文结构,还有响应结构,如下图所示。
客户端请求消息
服务器响应消息
可以参照下面教程的例子进行学习。在我们的实现中不需要写太多的报文首部内容,能够让浏览器进行正确的识别即可。学习内容如下

  • HTTP报文格式 //HTTP的基本报文格式
  • GET,POST报文格式 //其实就是信息放在url和放在最后的区别

菜鸟教程写的还不错->点我跳转


代码部分

主要调用的地方在第73行来完成我的工作。其他的部分就是通过报文格式来发送文件给浏览器进行组织和呈现。

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import java.io.*;
import java.net.*;
import java.util.StringTokenizer;

public class SimpleHttpServer implements Runnable {

ServerSocket serverSocket;//服务器Socket
public static int PORT=8080;//监听8080端口

// 开始服务器 Socket 线程.
public SimpleHttpServer() {
try {
serverSocket=new ServerSocket(PORT);
} catch(Exception e) {
System.out.println("无法启动HTTP服务器:"+e.getLocalizedMessage());
}
if(serverSocket==null) System.exit(1);//无法开始服务器
new Thread(this).start();
System.out.println("HTTP服务器正在运行,端口:"+PORT);
}

// 运行服务器主线程, 监听客户端请求并返回响应.
public void run() {
while(true) {
try {
Socket client=null;//客户Socket
client=serverSocket.accept();//客户机(这里是 IE 等浏览器)已经连接到当前服务器
if(client!=null) {
System.out.println("连接到服务器的用户:"+client);
try {
// 第一阶段: 打开输入流
BufferedReader in=new BufferedReader(new InputStreamReader(
client.getInputStream()));

System.out.println("客户端发送的请求信息: ***************");
// 读取第一行, 请求地址
String line=in.readLine();
System.out.println(line);
String resource=line.substring(line.indexOf('/')+1,line.lastIndexOf('/')-5);
//获得请求的资源的地址
resource=URLDecoder.decode(resource, "UTF-8");//反编码 URL 地址
String method = new StringTokenizer(line).nextElement().toString();// 获取请求方法, GET 或者 POST

// 读取所有浏览器发送过来的请求参数头部信息
while( (line = in.readLine()) != null) {
// System.out.println(line);
if(line.equals("")) break; //读到尾部即跳出
}

System.out.println("请求信息结束 ***************");
System.out.println("用户请求的资源是:"+resource);
System.out.println("请求的类型是: " + method);

//如果请求的是空则返回首页
if(resource.equals("")&&method.equals("GET")){
String ConTentType="Content-Type: text/html;charset=UTF-8"; //发送文本形式的首页
fileService("WebContent/index.html",client,ConTentType);
closeSocket(client);
}
//如果请求的是js文件则按照js返回
if(resource.endsWith(".js")&&method.equals("GET")) {
String ConTentType="Content-Type: application/javascript;charset=UTF-8"; //js的内容发送表明类型
fileService("WebContent/"+resource, client,ConTentType);
closeSocket(client);
continue;
}
} catch(Exception e) {
System.out.println("HTTP服务器错误:"+e.getLocalizedMessage());
}
}
//System.out.println(client+"连接到HTTP服务器");//如果加入这一句,服务器响应速度会很慢
} catch(Exception e) {
System.out.println("HTTP服务器错误:"+e.getLocalizedMessage());
}
}
}


//关闭客户端 socket 并打印一条调试信息.
void closeSocket(Socket socket) {
try {
socket.close();
} catch (IOException ex) {
ex.printStackTrace();
}
System.out.println(socket + "离开了HTTP服务器");
}

/**
* 读取一个文件的内容并返回给浏览器端.
* @param fileName 文件名
* @param socket 客户端 socket.
*/
void fileService(String fileName, Socket socket,String ConTentType)
{
try
{
PrintStream out = new PrintStream(socket.getOutputStream(), true);
File fileToSend = new File(fileName);
if(fileToSend.exists() && !fileToSend.isDirectory())
{
out.println("HTTP/1.0 200 OK");//返回应答消息,并结束应答
out.println(ConTentType); //返回文件的格式
out.println("Content-Length: " + fileToSend.length());// 返回内容字节数
out.println();// 根据 HTTP 协议, 空行将结束头信息

FileInputStream fis = new FileInputStream(fileToSend);
byte data[] = new byte[fis.available()];
fis.read(data);
out.write(data);
out.close();
fis.close();
}
}catch(Exception e){
System.out.println("传送文件时出错:" + e.getLocalizedMessage());
}
}

//命令行打印用途说明.
private static void usage() {
System.out.println("Usage: java HTTPServer <port> Default port is 80.");
}

/**
* 启动简易 HTTP 服务器
*/
public static void main(String[] args) {
try {
if(args.length != 1) {
usage();
} else if(args.length == 1) {
PORT = Integer.parseInt(args[0]);
}
} catch (Exception ex) {
System.err.println("Invalid port arguments. It must be a integer that greater than 0");
}

new SimpleHttpServer(); //创建一个
}
}

后记

如果对具体的实例想自己操作一下,我的内容放在github上面,第一次做写的有点烂–>socket-web分支
这个分支就是把之前的那个不用tomcat,使用socket来完成的。
emm…这篇貌似写的挺粗略的,懒得写了。要研究下的可以直接去给gayhub看代码吧。关键就是理解http协议的格式,并不难。


参考资料

这篇文章给了很大的帮助,实现了很大的一部分功能

http://blog.csdn.net/overmaker/article/details/2194921

当然需要掌握的只是还有Socket和http协议,我还看了一下《图解HTTP》这本书的内容

菜鸟教程的HTTP内容
http://www.runoob.com/http/http-messages.htmls

如果对您有帮助,请我喝杯咖啡?