JAVA实现简易HTTP服务器
http://blog.csdn.net/aaron_yang666/article/details/52795978
- 1
- 说实话,之前完全没有想过,我还能写出服务器。感觉服务器这么高端的东西,能会用就不错了,还能写。
不吐槽了,开始了。
这次的作业是搭建一个服务器,要能接收请求,并给浏览器返回正确响应。
项目的下载地址
项目目标:实现一个简易的多线程服务器,可以处理来自浏览器的请求(GET/POST),并做出正确的回应。
请求分以下四种类型:
1. 无参数,文本文件类型
2. 无参数,图片文件类型
3. 有参数,GET方式请求,并完成表单验证(登陆验证)
4. 有参数,POST方式请求,并完成表单验证(登陆验证)
首先,应该明确这个项目的基本实现原理,从浏览器读入用户请求的信息,服务器解析并记录返回的文件名和参数列表,如果文件存在,用流读取文件,并返回到浏览器上,如果不存在,返回相应的提示信息,参数列表和服务器存储的相同的话,返回登陆成功,否则返回失败。
第一步,既然要解析从浏览器传过来的信息,那就要明白传过来信息的所使用的协议HTTP\UDP\FTP 和 URL的组成元素,因为是简易服务器,我们就只解析HTTP协议先。
Request是指从客户端到服务器端的请求消息
Request 消息分为3部分,第一部分叫请求行, 第二部分叫http header, 第三部分是body. header和body之间有个空行,结构如下图
Method表示请求方法,比如”POST”,”GET”,
Path-to-resoure表示请求的资源,
Http/version-number 表示HTTP协议的版本号,
当使用的是”GET” 方法的时候, body是为空的,当使用”POST”,body不为空,但是没有换行,readline()方法不能读
Response是指服务器端到客户端的响应信息
和Request消息的结构基本一样。 同样也分为三部分,第一部分叫request line, 第二部分叫request header,第三部分是body. header和body之间也有个空行, 结构如下图
状态码用来告诉HTTP客户端,HTTP服务器是否产生了预期的Response. HTTP/1.1中定义了5类状态码,1XX 提示信息 - 表示请求已被成功接收,继续处理;2XX 成功 - 表示请求已被成功接收,理解,接受;3XX 重定向 - 要完成请求必须进行更进一步的处理;4XX 客户端错误 - 请求有语法错误或请求无法实现;5XX 服务器端错误 - 服务器未能实现合法的请求,当然不写也是可以得,状态码就是便于程序员去分析当前页面是正确响应还是错误的。
第二步,在了解URL和HTTP协议之后,就可以开始构建项目了。
目前这个项目的UML图
第三步,准备文本、图片、HTML文件,然后开始编编编
效果图:(端口号:23333)2333…
默认访问
aaron.txt
a.jpg
GET/POST请求
注意地址栏的变化
login.html(GET)
login.html(GET) (登陆失败情况)
login.html(GET) (登陆成功情况)
login.html(POST)
login.html(POST) (登陆失败情况)
login.html(POST) (登陆成功情况)
部分源码:(全部源码去上面下载)
//Server.java
package cn.net.sight.server;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Properties;
import cn.net.sight.thread.ServerThread;
public class Server {
private static ServerSocket server;
private static Properties properties;
private int port;
static {
properties = new Properties();
try {
properties.load(new FileInputStream(new File("src/resources/property.proterties")));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//initialize the server
public void init() {
try {
port = Integer.parseInt(properties.getProperty("port"));
server = new ServerSocket(port);
} catch (IOException e) {
e.printStackTrace();
}
}
//receive the request from the web browser
public void receive() {
Socket clientSocket = new Socket();
try {
clientSocket = server.accept();
} catch (IOException e) {
e.printStackTrace();
}
ServerThread thread = new ServerThread(clientSocket);
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//startup server
public static void main(String[] args) {
Server Aaroncat = new Server();
Aaroncat.init();
System.out.println("----Aaroncat has startup----");
while(true){
Aaroncat.receive();
}
}
}
- 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
//Request.java
package cn.net.sight.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import cn.net.sight.util.MessageUtil;
public class Request {
private InputStream input; // Socket --> InputStream
private Socket socket; // client --> socket
private BufferedReader buffer; // InputStream --> BufferedReader
private String schema; // the schema of the request GET or POST
private String requestFileName; // exact file name
private String requestData; // file name + <key=value>...
private String values_Str; // a string the user input in the form
private int paramLength; // using in the POST: the length of parameters
private Map<String, String> socketValues;// values_str --> MAP
private PrintStream print;
protected MessageUtil messageUtil = new MessageUtil();
//省略了全部的setter() getter()
private void doSchema(String firstLineInData) throws IOException {
socketValues = new HashMap<String, String>();
if (this.schema.equals("GET")) {
// GET请求 --> 包含文件名和参数键值对
// 实现了对FileName、SocketValues的赋值
this.setRequestData(messageUtil.getRequestData(firstLineInData));
if (this.requestData.contains("?")) {
this.setRequestFileName(messageUtil.getFileName(this.getRequestData()));
this.setSocketValues(messageUtil.getValues(this.getRequestData()));
} else {
// GET请求 -->只包含文件名
// 实现了对FileName的赋值
this.setRequestFileName(requestData);
}
} else {
// POST请求 第一行只包含文件名
// 实现了对FileName、SocketValues的赋值
this.setRequestFileName(messageUtil.getRequestData(firstLineInData));
this.getUserInfo(buffer);
}
}
private void getUserInfo(BufferedReader br) throws IOException {
while (this.buffer.ready()) {
String remained = buffer.readLine();
if (remained.contains("Content-Length")) {
String[] temp = remained.split(" ");
this.setParamLength(Integer.parseInt(temp[1]));
break;
}
}
buffer.readLine();
String userInfo = "";
for (int i = 0; i < this.getParamLength(); i++) {
userInfo += (char) buffer.read();
}
this.setValues_Str(userInfo);
this.setSocketValues(messageUtil.getValues(this.getValues_Str()));
}
public Request(Socket clientSocket) throws IOException {
this.setSocket(clientSocket);
this.setPrint(new PrintStream(clientSocket.getOutputStream()));
this.setInput(clientSocket.getInputStream());
this.setBuffer(new BufferedReader(new InputStreamReader(clientSocket.getInputStream())));
// get something from the first line
String firstLineInData = buffer.readLine();
this.setSchema(messageUtil.getSchema(firstLineInData)); // 获得请求方式Schema
doSchema(firstLineInData); // 对Schema进行判断
}
}
- 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
//Response.java
package cn.net.sight.server;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Date;
import java.util.Map;
import cn.net.sight.util.FileUtil;
import cn.net.sight.util.LoginUtil;
public class Response {
private String fileName;
private Map<String, String> userValues;
private PrintStream ps;
private Request request;
private Socket clientSocket;
protected FileUtil fileUtil = new FileUtil();
protected LoginUtil loginUtil = new LoginUtil();
public String getFileName() {
return fileName;
}
//省略了全部的setter() 和 getter()
public Response(Request request) {
this.setRequest(request);
this.setClientSocket(request.getSocket());
this.setFileName(request.getRequestFileName());
userValues = this.request.getSocketValues();
try {
this.init();
} catch (IOException e) {
e.printStackTrace();
}
}
public void showup(String fileName) throws IOException {
this.ps = this.request.getPrint();
// 要处理正常文件名和空文件名
if (!fileName.equals("") && !fileName.equals("error.html")) {
this.ps.println("HTTP/1.1 200 OK");
this.ps.println();
fileUtil.readFile(fileName, ps);
} else if (fileName.equals("error.html")) {
ps.println("HTTP/1.1 404 fileNotFound");
ps.println();
fileUtil.readFile("error.html", ps);
} else {
ps.println(new Date().toString());
}
if (ps != null){
ps.close();
}
}
public void init() throws IOException {
//如果信息MAP是空,则代表是普通文件或者是默认访问
if (userValues.isEmpty()) {
if (fileName != "") {
this.showup(fileName);
} else {
this.ps = this.request.getPrint();
ps.println(new Date().toString());
if(ps != null) ps.close();
if(clientSocket != null)clientSocket.close();
}
} else {
//如果信息MAP不为空,代表是GET/POST请求,并带有参数键值对
this.Check(userValues, fileName);
}
}
public void Check(Map<String, String> values_list, String respFileName) throws IOException {
// 验证用户输入信息的合法性
if (loginUtil.isValid(values_list)) {
this.showup(respFileName);
} else {
this.showup("error.html");
}
}
}
- 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
//ServerThread.java
package cn.net.sight.thread;
import java.io.IOException;
import java.net.Socket;
import cn.net.sight.server.Response;
import cn.net.sight.server.Request;
public class ServerThread extends Thread {
private Socket clientSocket;
private Request request;
private Response response;
public ServerThread() {
super();
}
public ServerThread(Socket clientSocket) {
super();
this.clientSocket = clientSocket;
}
public Socket getClientSocket() {
return clientSocket;
}
public void setClientSocket(Socket clientSocket) {
this.clientSocket = clientSocket;
}
public Request getRequest() {
return request;
}
public void setRequest(Request request) {
this.request = request;
}
public Response getResponse() {
return response;
}
public void setResponse(Response response) {
this.response = response;
}
public void run(){
super.run();
try {
this.setRequest(new Request(clientSocket));
this.response = new Response(request);
this.setResponse(response);
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 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
//FileUtil.java
package cn.net.sight.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
public class FileUtil {
private static final int BUFFER_SIZE = 1024;
public void readFile(String file_Name, PrintStream ps) throws IOException {
byte[] buffer = new byte[BUFFER_SIZE];
int length;
File file = new File("src/resources/" + file_Name);
FileInputStream fis = null;
if (file.exists()) {
try {
fis = new FileInputStream(file);
while ((length = fis.read(buffer)) != -1) {
ps.write(buffer, 0, length);
ps.flush();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
} else {
ps.println("File not found");
ps.println();
}
}
}
- 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
//LoginUtil.java
package cn.net.sight.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
public class LoginUtil {
protected boolean flag = false;
protected Map<String, String> values;
private static Properties properties;
static {
properties = new Properties();
try {
properties.load(new FileInputStream(new File("src/resources/property.proterties")));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public Map<String, String> getValues() {
return values;
}
public void setValues(Map<String, String> values) {
this.values = values;
}
// 验证用户信息的合法性(应用JDBC桥,连接数据库)
public boolean isValid(Map<String, String> values) {
String username = properties.getProperty("username");
String password = properties.getProperty("password");
if (values.get("username").equals(username)) {
if (values.get("password").equals(password)) {
flag = true;
System.out.println("The user " + values.get("username") + " was log the server.");
return flag;
}
} else {
System.out.println("Forbide the " + values.get("username") + " log the server");
return flag;
}
return false;
}
}
- 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
//MessageUtil.java
package cn.net.sight.util;
import java.util.HashMap;
import java.util.Map;
public class MessageUtil {
// schema : GET or POST
public String getSchema(String requestMsg) {
String[] result = new String[1];
if (requestMsg.contains(" ")) {
result = requestMsg.split(" ");
requestMsg = result[0];
}
return requestMsg;
}
// get the resquestData = (filename + map<S,S>)
public String getRequestData(String firstLineInData) {
String[] result = new String[10];
result = firstLineInData.split(" ");
firstLineInData = result[1].substring(1);
return firstLineInData;
}
// get the filename from the requestData
public String getFileName(String requestData) {
String[] result = new String[10];
result = requestData.split("[?]");
return result[0];
}
// save the info into the map<S,S>
public Map<String, String> getValues(String requestData) {
Map<String, String> values = new HashMap<String, String>();
String[] result = new String[10];
String regex = "[&=]";
if (requestData.contains("?")) {
result = requestData.split("[?]");
String data_List = result[1];
result = data_List.split(regex);
for (int i = 0; i < result.length - 1; i += 2) {
values.put(result[i], result[i + 1]);
}
return values;
} else {
result = requestData.split(regex);
for (int i = 0; i < result.length - 1; i += 2) {
values.put(result[i], result[i + 1]);
}
return values;
}
}
}
- 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
整个项目结构
上一篇: 校园逗段:学校,你得不到我的心!
下一篇: 统计用区划和城乡划分代码,在线爬取代码