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;
private String url;
private String version;
private Map<String, String> headers = new HashMap<>();
private Map<String, String> parameters = new HashMap<>();
private Map<String, String> cookies = new HashMap<>();
private String body;
// 请求的构造逻辑, 也使用工厂模式来构造.
// 此处的参数, 就是从 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));
String firstLine = bufferedReader.readLine();
if (firstLine != null) {
// 此处的 build 的过程就是解析请求的过程.
// 1. 解析首行
//解析得方法 url 和 版本
String[] firstLineTokens = firstLine.split(" ");
request.method = firstLineTokens[0];
request.url = firstLineTokens[1];
request.version = firstLineTokens[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);
}
//解析headers
String line = "";
while ((line = bufferedReader.readLine()) != null && line.length() != 0) {
String[] result = line.split(": ");
request.headers.put(result[0], result[1]);
}
//解析cookie
String cookie = request.headers.get("Cookie");
if (cookie != null) {
parseCookie(cookie, request.cookies);
}
//解析body
if ("POST".equalsIgnoreCase(request.method)
|| "PUT".equalsIgnoreCase(request.method)) {
//暂时只考虑这俩个方法的body
int length = Integer.parseInt(request.headers.get("Content-Length"));
char[] buffer = new char[length];
int len = bufferedReader.read(buffer);
request.body = new String(buffer, 0, len);
parseKV(request.body, request.parameters);
}
}
return request;
}
private static void parseCookie(String cookie, Map<String, String> cookies) {
//在这里解析键值对
//先按; 分割
//再按=分割
String[] kv = cookie.split("; ");
for (String s : kv
) {
String[] result = s.split("=");
cookies.put(result[0], result[1]);
}
}
private static void parseKV(String parameters, Map<String, String> parameters1) {
//在这里解析键值对
//先按&分割
//再按=分割
String[] kv = parameters.split("&");
for (String s : kv
) {
String[] result = s.split("=");
parameters1.put(result[0], result[1]);
}
}
// 给这个类构造一些 getter 方法. (不要搞 setter).
// 请求对象的内容应该是从网络上解析来的. 用户不应该修改.
public String getMethod() {
return method;
}
public String getUrl() {
return url;
}
public String getVersion() {
return version;
}
public String getBody() {
return body;
}
public String getHeaders(String key) {
return headers.get(key);
}
// 此处的 getter 手动写, 自动生成的版本是直接得到整个 hash 表.
// 而我们需要的是根据 key 来获取值.
public String getCookie(String key) {
return cookies.get(key);
}
public String getPararmeters(String key) {
return parameters.get(key);
}
@Override
public String toString() {
return "HttpRequest{" +
"method '" + method + '\'' +
", url '" + url + '\'' +
", version '" + version + '\'' +
", headers " + headers +
", parameters " + parameters +
", cookies " + cookies +
", body '" + body + '\'' +
'}';
}
}
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 HttpRespond {
private String version = "HTTP/1.1";
private int statue; // 状态码
private String message; // 状态码的描述信息
private Map<String, String> headers = new HashMap<>();
private StringBuilder body = new StringBuilder(); // 方便一会进行拼接.
// 当代码需要把响应写回给客户端的时候, 就往这个 OutputStream 中写就好了
private OutputStream outputStream;
// 表示一个 HTTP 响应, 负责构造
// 进行序列化操作
public static HttpRespond build(OutputStream outputStream) {
HttpRespond respond = new HttpRespond();
respond.outputStream = outputStream;
// 除了 outputStream 之外, 其他的属性的内容, 暂时都无法确定. 要根据代码的具体业务逻辑
// 来确定. (服务器的 "根据请求并计算响应" 阶段来进行设置的)
return respond;
}
public void setVersion(String version) {
this.version = version;
}
public void setStatue(int statue) {
this.statue = statue;
}
public void setMessage(String message) {
this.message = message;
}
public void setHeaders(String key, String value) {
this.headers.put(key, value);
}
public void setBody(String body) {
this.body.append(body);
}
// 以上的设置属性的操作都是在内存中倒腾.
// 还需要一个专门的方法, 把这些属性 按照 HTTP 协议 都写到 socket 中.
public void flush() throws IOException {
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
String firstLine = version + " " + statue + " " + message;
bufferedWriter.write(firstLine + "\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() + "\n");
bufferedWriter.flush();
}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class HttpSeverV3 {
//设置一个静态内部类表示user
static class User {
public String username;
public String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
private ServerSocket serverSocket;
// session 会话. 指的就是同一个用户的一组访问服务器的操作, 归类到一起, 就是一个会话.
// 记者来采访你, 记者问的问题就是一个请求, 你回答的内容, 就是一个响应. 一次采访过程中
// 涉及到很多问题和回答(请求和响应), 这一组问题和回答, 就可以称为是一个 "会话" (整个采访的过程)
// sessions 中就包含很多会话. (每个键值对就是一个会话)
private Map<String, User> sessions = new HashMap<>();
public HttpSeverV3(int port) throws IOException {
this.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);
}
});
}
}
private void process(Socket clientSocket) {
try {
HttpRequest request = HttpRequest.build(clientSocket.getInputStream());
HttpRespond respond = HttpRespond.build(clientSocket.getOutputStream());
if (request.getMethod() != null) {
//判断请求是什么方法 不同方法不同处理
if ("GET".equalsIgnoreCase(request.getMethod())) {
doGet(request, respond);
} else if ("POST".equalsIgnoreCase(request.getMethod())) {
doPost(request, respond);
} else {
respond.setHeaders("Content-type", "text/html");
respond.setStatue(404);
respond.setMessage("No Found");
respond.setBody("NULL
");
}
//写入
respond.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void doPost(HttpRequest request, HttpRespond respond) throws IOException {
if (request.getUrl().startsWith("/login")) {
System.out.println(request);
//判断登录名和密码是否正确
String userName = request.getPararmeters("username");
String password = request.getPararmeters("password");
if ("123".equals(userName) && "aaa".equals(password)) {
//登录成功
respond.setStatue(200);
respond.setMessage("OK");
respond.setHeaders("Content-type", "text/html");
//通过cookie让浏览器记住你
// 现有的对于登陆成功的处理. 给这次登陆的用户分配了一个 session
// (在 hash 中新增了一个键值对), key 是随机生成的. value 就是用户的身份信息
// 身份信息保存在服务器中, 此时也就不再有泄露的问题了
// 给浏览器返回的 Cookie 中只需要包含 sessionId 即可
String sessionId = UUID.randomUUID().toString();
sessions.put(sessionId, new User(userName, password));
respond.setHeaders("Set-Cookie", "sessionId="+sessionId);
InputStream inputStream = HttpSeverV3.class.getClassLoader().getResourceAsStream("LoginFinish.html");
assert inputStream != null;
BufferedReader bufferedReader =new BufferedReader(new InputStreamReader(inputStream));
String line = null;
while ((line = bufferedReader.readLine()) != null) {
respond.setBody(line);
}
bufferedReader.close();
} else {
//登录失败
respond.setStatue(200);
respond.setMessage("OK");
respond.setHeaders("Content-type", "text/html");
//登录失败应该让重写登录
InputStream inputStream = HttpSeverV3.class.getClassLoader().getResourceAsStream("Test.html");
assert inputStream != null;
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
//按行读取写入到body中
String line = null;
while ((line = bufferedReader.readLine()) != null) {
respond.setBody(line);
}
bufferedReader.close();
}
}
}
private void doGet(HttpRequest request, HttpRespond respond) throws IOException {
//返回一个html文件
if (request.getUrl().startsWith("/ok")) {
System.out.println(request);
//查看浏览器中是否有cookie 并且根据sessionId得到的用户的密码正确
String sessionId = request.getCookie("sessionId");
User user = sessions.get(sessionId);
if (user != null && "123".equals(user.username) && "aaa".equals(user.password)) {
//说明此时登录的用户就是主人直接返回简历
respond.setStatue(200);
respond.setMessage("OK");
respond.setHeaders("Content-type", "text/html");
InputStream inputStream = HttpSeverV3.class.getClassLoader().getResourceAsStream("LoginFinish.html");
assert inputStream != null;
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
//按行读取写入到body中
String line = null;
while ((line = bufferedReader.readLine()) != null) {
respond.setBody(line);
}
bufferedReader.close();
} else {
respond.setHeaders("Content-type", "text/html");
respond.setStatue(200);
respond.setMessage("Ok");
//在这里我们先获取类对象再获取类加载器 最后根据文件名在Resource目录中找到该文件 返回其InputStream对象
InputStream inputStream = HttpSeverV3.class.getClassLoader().getResourceAsStream("Test.html");
assert inputStream != null;
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
//按行读取写入到body中
String line = null;
while ((line = bufferedReader.readLine()) != null) {
respond.setBody(line);
}
bufferedReader.close();
}
}
}
public static void main(String[] args) throws IOException {
HttpSeverV3 v3 = new HttpSeverV3(9090);
v3.start();
}
}
<!doctype htpl>
<html>
<head>
<meta charset="utf-8" />
<title>用户登录</title>
</head>
<body>
<table border="1px" cellpadding="10px" cellspacing="0px"
style="width: 30%;margin:auto;background:rgb(195,195,195)"
bordercolor="red" >
<caption>请登录</caption>
<form action="/login" method="POST">
<tr>
<th>用户名:</th>
<td><input type="text" name="username">
</tr>
<tr>
<th>密码:</th>
<td><input type="password" name="password"></td>
</tr>
<tr>
<th colspan="2">
<input type="submit" value="提交">
<input type="reset" value="重置">
</th>
</tr>
</form>
</table>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>我的简历</title>
<style>
table{
border-collapse: collapse;
}
table,td.th{
border: 1px solid blue;
}
a:link {text-decoration:none};
a:hover {color:#FF00FF;}
</style>
<body>
<table width="700" height="500" border="1" align="center">
<caption><h3>个人简历</h3></caption>
<tr>
<td width="90">姓名</td>
<td width="100">Listen</td>
<td width="89">出生日期</td>
<td width="113">1999.04.xx</td>
<td width="91">性别</td>
<td width="48">男</td>
<td width="121" rowspan="4" background="http://img4.imgtn.bdimg.com/it/u=1600749507,887062207&fm=26&gp=0.jpg"></td>
</tr>
<tr>
<td>学历</td>
<td>本科</td>
<td>专业</td>
<td>软网络程</td>
<td>民族</td>
<td>汉</td>
</tr>
<tr>
<td>学校</td>
<td> xxx大学</td>
<td>政治面貌</td>
<td>团员</td>
<td>联系方式</td>
<td>151xxxxxxx</td>
</tr>
<tr>
<td>籍贯</td>
<td>陕西榆林</td>
<td>邮箱</td>
<td>1604053140@qq.com</td>
</tr>
<tr height="100">
<td>主修课程</td>
<td colspan="6">
C程序设计与算法语言,Java面向对象编程,离散数学 <br/>
C++课程设计,虚拟化技术,计算机网络,操作系统, <br/>
数据结构 数据库原理 Python数据分析,JavaWeb网络编程<br/>
</td>
</tr>
<tr>
<td>技能证书</td>
<td colspan="6">
<ul>
<li>CET 4</li>
<li>驾驶证</li>
</ul>
</td>
</tr>
<tr>
<td>项目经历</td>
<td colspan="6">
<ul>
<li>图书管理系统</li>
<li>综合测评管理系统</li>
</ul>
</tr>
<tr>
<td colspan="7" align="center"><b>自我评价</b></td>
</tr>
<tr>
<td colspan="7" height="200">
我在大学期间任职学习委员,工作认真负责,深受同学
喜爱 ,学习中,踏实学习本专业知识,和小组合作时,负责踏实
具有强烈的团队合作精神和工作能力。
</td>
</tr>
</table>
</body>
</html>
Cookie是啥? 就是一个字符串 只是里面的内容和意义是程序员自己定的
Cookie从哪来? 重服务器来 服务器会在返回的响应中引入一个Set-Cookie字段 对应的值就会保存在浏览器中
Cookie咋保存? 按域名保存/地址保存 每个域名/地址有自己的Cookie
Cookie如何用 浏览器后序访问同一个域名或者地址的时候 就会自动带上保存的Cookie 服务器可以感受到这个Cookie 服务器就可以做出不同的处理逻辑
- Cookie以文本文件格式存储在浏览器中,而session存储在服务端
- 因为每次发起 Http 请求,都要携带有效Cookie信息,所以Cookie一般都有大小限制,以防止增加网络压力,一般不超过4k
- 可以轻松访问cookie值但是我们无法轻松访问会话(session)值,因此session方案更安全