请求类
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
// 表示一个 HTTP 请求, 并负责解析.
public class HttpRequest {
private String method;
// /index.html?a=10&b=20
private String url;
private String version;
private Map<String, String> headers = new HashMap<>();
private Map<String, String> parameters = new HashMap<>();
// 请求的构造逻辑, 也使用工厂模式来构造.
// 此处的参数, 就是从 socket 中获取到的 InputStream 对象
// 这个过程本质上就是在 "反序列化"
public static HttpRequest build(InputStream inputStream) throws IOException {
HttpRequest request = new HttpRequest();
// 此处的逻辑中, 不能把 bufferedReader 写到 try ( ) 中.
// 一旦写进去之后意味着 bufferReader 就会被关闭, 会影响到 clientSocket 的状态.
// 等到最后整个请求处理完了, 再统一关闭
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
// 此处的 build 的过程就是解析请求的过程.
// 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) {
// 看看 url 中是否有 ? . 如果没有, 就说明不带参数, 也就不必解析了
// 此处的 parameters 是希望包含整个 参数 部分的内容
// pos 表示 ? 的下标
// /index.html?a=10&b=20
// parameters 的结果就相当于是 a=10&b=20
String parameters = request.url.substring(pos + 1);
// 切分的最终结果, key a, value 10; key b, value 20;
parseKV(parameters, 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. 解析 body (暂时先不考虑)
return request;
}
private static void parseKV(String input, Map<String, String> output) {
// 1. 先按照 & 切分成若干组键值对
String[] kvTokens = input.split("&");
// 2. 针对切分结果再分别进行按照 = 切分, 就得到了键和值
for (String kv : kvTokens) {
String[] result = kv.split("=");
output.put(result[0], result[1]);
}
}
// 给这个类构造一些 getter 方法. (不要搞 setter).
// 请求对象的内容应该是从网络上解析来的. 用户不应该修改.
public String getMethod() {
return method;
}
public String getUrl() {
return url;
}
public String getVersion() {
return version;
}
// 此处的 getter 手动写, 自动生成的版本是直接得到整个 hash 表.
// 而我们需要的是根据 key 来获取值.
public String getHeader(String key) {
return headers.get(key);
}
public String getParameter(String key) {
return parameters.get(key);
}
@Override
public String toString() {
return "HttpRequest{" +
"method='" + method + '\'' +
", url='" + url + '\'' +
", version='" + version + '\'' +
", headers=" + headers +
", parameters=" + parameters +
'}';
}
}
响应类:
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;
// 表示一个 HTTP 响应, 负责构造
// 进行序列化操作
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(); // 方便一会进行拼接.
// 当代码需要把响应写回给客户端的时候, 就往这个 OutputStream 中写就好了.
private OutputStream outputStream = null;
public static HttpResponse build(OutputStream outputStream) {
HttpResponse response = new HttpResponse();
response.outputStream = 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);
}
// 以上的设置属性的操作都是在内存中倒腾.
// 还需要一个专门的方法, 把这些属性 按照 HTTP 协议 都写到 socket 中.
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();
}
}
http服务器:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class HttpServerV2 {
private ServerSocket serverSocket = null;
public HttpServerV2(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());
System.out.println("request: " + request);
HttpResponse response = HttpResponse.build(clientSocket.getOutputStream());
response.setHeader("Content-Type", "text/html");
// 2. 根据请求计算响应
if (request.getUrl().startsWith("/hello")) {
response.setStatus(200);
response.setMessage("OK");
response.writeBody("<h1>hello</h1>");
} else if (request.getUrl().startsWith("/calc")) {
// 这个逻辑要根据参数的内容进行计算
// 先获取到 a 和 b 两个参数的值
String aStr = request.getParameter("a");
String bStr = request.getParameter("b");
// System.out.println("a: " + aStr + ", b: " + bStr);
int a = Integer.parseInt(aStr);
int b = Integer.parseInt(bStr);
int result = a + b;
response.setStatus(200);
response.setMessage("OK");
response.writeBody("<h1> result = " + result + "</h1>");
} else if (request.getUrl().startsWith("/cookieUser")) {
response.setStatus(200);
response.setMessage("OK");
// HTTP 的 header 中允许有多个 Set-Cookie 字段. 但是
// 此处 response 中使用 HashMap 来表示 header 的. 此时相同的 key 就覆盖
response.setHeader("Set-Cookie", "user=tz");
response.writeBody("<h1>set cookieUser</h1>");
} else if (request.getUrl().startsWith("/cookieTime")) {
response.setStatus(200);
response.setMessage("OK");
// HTTP 的 header 中允许有多个 Set-Cookie 字段. 但是
// 此处 response 中使用 HashMap 来表示 header 的. 此时相同的 key 就覆盖
response.setHeader("Set-Cookie", "time=" + (System.currentTimeMillis() / 1000));
response.writeBody("<h1>set cookieTime</h1>");
} else {
response.setStatus(200);
response.setMessage("OK");
response.writeBody("<h1>default</h1>");
}
// 3. 把响应写回到客户端
response.flush();
} catch (IOException | NullPointerException e) {
e.printStackTrace();
} finally {
try {
// 这个操作会同时关闭 getInputStream 和 getOutputStream 对象
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
HttpServerV2 server = new HttpServerV2(9090);
server.start();
}
}