此版本的特点:
- 支持返回一个静态的html文件
- 解析处理cookie(把cookie处理成键值对结构)
- 解析处理body(把body中的数据处理成键值对结构)
- 实现一个完整的登陆功能(session的简单实现)
构造请求类
package Java_0629.Test1;
import javax.print.attribute.standard.RequestingUserName;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
public class HttpRequest {
private String method;
private String url;
private String version;
private Map<String, String> headers = new HashMap<>();
// url 中的参数和 body 中的参数都放到这个 parameters hash 表中.
private Map<String, String> parameters = new HashMap<>();
private Map<String, String> cookies = new HashMap<>();
private String body;
public static HttpRequest build(InputStream inputStream) throws IOException {
HttpRequest request = new HttpRequest();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
// 1. 处理首行
String firstLine = bufferedReader.readLine();
String[] firstLineTokens = firstLine.split(" ");
request.method = firstLineTokens[0];
request.url = firstLineTokens[1];
request.version = firstLineTokens[2];
// 2. 解析 url
int pos = request.url.indexOf("?");
if (pos != -1) {
String queryString = request.url.substring(pos + 1);
parseKV(queryString, request.parameters);
}
// 3. 循环处理 header 部分
String line = "";
while ((line = bufferedReader.readLine()) != null && line.length() != 0) {
String[] headerTokens = line.split(": ");
request.headers.put(headerTokens[0], headerTokens[1]);
}
// 4. 解析 cookie
String cookie = request.headers.get("Cookie");
if (cookie != null) {
// 把 cookie 进行解析
parseCookie(cookie, request.cookies);
}
// 5. 解析 body
if ("POST".equalsIgnoreCase(request.method)
|| "PUT".equalsIgnoreCase(request.method)) {
// 这两个方法需要处理 body, 其他方法暂时不考虑
// 需要把 body 读取出来.
// 需要先知道 body 的长度. Content-Length 就是干这个的.
// 此处的长度单位是 "字节"
int contentLength = Integer.parseInt(request.headers.get("Content-Length"));
// 注意体会此处的含义~~
// 例如 contentLength 为 100 , body 中有 100 个字节.
// 下面创建的缓冲区长度是 100 个 char (相当于是 200 个字节)
// 缓冲区不怕长. 就怕不够用. 这样创建的缓冲区才能保证长度管够~~
char[] buffer = new char[contentLength];
int len = bufferedReader.read(buffer);
request.body = new String(buffer, 0, len);
// body 中的格式形如: username=tanglaoshi&password=123
parseKV(request.body, request.parameters);
}
return request;
}
private static void parseCookie(String cookie, Map<String, String> cookies) {
// 1. 按照 分号空格 拆分成多个键值对
String[] kvTokens = cookie.split("; ");
// 2. 按照 = 拆分每个键和值
for (String kv : kvTokens) {
String[] result = kv.split("=");
cookies.put(result[0], result[1]);
}
}
private static void parseKV(String queryString, Map<String, String> parameters) {
// 1. 按照 & 拆分成多个键值对
String[] kvTokens = queryString.split("&");
// 2. 按照 = 拆分每个键和值
for (String kv : kvTokens) {
String[] result = kv.split("=");
parameters.put(result[0], result[1]);
}
}
public String getMethod() {
return method;
}
public String getUrl() {
return url;
}
public String getVersion() {
return version;
}
public String getBody() {
return body;
}
public String getParameter(String key) {
return parameters.get(key);
}
public String getHeader(String key) {
return headers.get(key);
}
public String getCookie(String key) {
return cookies.get(key);
}
}
构造响应:
package Java_0629.Test1;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;
public class HttpResponse {
private String version = "HTTP/1.1";
private int status;
private String message;
private Map<String, String> headers = new HashMap<>();
private StringBuilder body = new StringBuilder();
private OutputStream outputStream = null;
public static HttpResponse build(OutputStream outputStream) {
HttpResponse response = new HttpResponse();
response.outputStream = outputStream;
return response;
}
public void setVersion(String version) {
this.version = version;
}
public void setStatus(int status) {
this.status = status;
}
public void setMessage(String message) {
this.message = message;
}
public void setHeader(String key, String value) {
headers.put(key, value);
}
public void writeBody(String content) {
body.append(content);
}
public void flush() throws IOException {
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write(version + " " + status + " " + message + "\n");
headers.put("Content-Length", body.toString().getBytes().length + "");
for (Map.Entry<String, String> entry : headers.entrySet()) {
bufferedWriter.write(entry.getKey() + ": " + entry.getValue() + "\n");
}
bufferedWriter.write("\n");
bufferedWriter.write(body.toString());
bufferedWriter.flush();
}
}
构造主程序:
package Java_0629.Test1;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class HttpServerV3 {
private ServerSocket serverSocket = null;
public HttpServerV3(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
ExecutorService executorService = Executors.newCachedThreadPool();
while (true) {
Socket clientSocket = serverSocket.accept();
executorService.execute(new Runnable() {
@Override
public void run() {
process(clientSocket);
}
});
}
}
public void process(Socket clientSocket) {
// 处理核心逻辑
try {
// 1. 读取请求并解析
HttpRequest request = HttpRequest.build(clientSocket.getInputStream());
HttpResponse response = HttpResponse.build(clientSocket.getOutputStream());
// 2. 根据请求计算响应
// 此处按照不同的 HTTP 方法, 拆分成多个不同的逻辑
if ("GET".equalsIgnoreCase(request.getMethod())) {
doGet(request, response);
} else if ("POST".equalsIgnoreCase(request.getMethod())) {
doPost(request, response);
} else {
// 其他方法, 返回一个 405 这样的状态码
response.setStatus(405);
response.setMessage("Method Not Allowed");
}
// 3. 把响应写回到客户端
response.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void doGet(HttpRequest request, HttpResponse response) throws IOException {
// 1. 能够支持返回一个 html 文件.
if (request.getUrl().startsWith("/index.html")) {
// 这种情况下, 就让代码读取一个 index.html 这样的文件.
// 要想读文件, 需要先知道文件路径. 而现在只知道一个 文件名 index.html
// 此时这个 html 文件所属的路径, 可以自己来约定(约定某个 d:/...) 专门放 html .
// 把文件内容写入到响应的 body 中
InputStream inputStream = HttpServerV3.class.getClassLoader().getResourceAsStream("index.html");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
// 按行读取内容, 把数据写入到 response 中
String line = null;
while ((line = bufferedReader.readLine()) != null) {
response.writeBody(line + "\n");
}
bufferedReader.close();
}
}
private void doPost(HttpRequest request, HttpResponse response) {
// 2. 实现 /login 的处理
if (request.getUrl().startsWith("/login")) {
// 读取用户提交的用户名和密码
String userName = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("userName: " + userName);
System.out.println("password: " + password);
}
}
public static void main(String[] args) throws IOException {
HttpServerV3 serverV3 = new HttpServerV3(9090);
serverV3.start();
}
}