Http Server 版本3 - 实现完整的登录过程(Cookie & Session)
Posted 一朵花花
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Http Server 版本3 - 实现完整的登录过程(Cookie & Session)相关的知识,希望对你有一定的参考价值。
前言:
之前,我们写了两个版本的 Http 服务器,本篇,再继续实现第三个版本
V3 将满足:
1.支持返回一个静态的 html 文件
2.解析处理 cookie (把 cookie 处理成键值对结构)
3.解析处理 body (把 body 中的数据处理成键值对结构)
4.实现一个完整的登录功能 (session 的简单实现)
request 类:
public class Request
private String method;
private String url;
private String version;
private Map<String,String> headers = new HashMap<>();
// url 中的参数和 body中的参数都放在 parameters 哈希表里
private Map<String,String> parameters = new HashMap<>();
private Map<String,String> cookies = new HashMap<>();
private String body;
public static Request build(InputStream inputStream) throws IOException
Request request = new Request();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
//1.处理首行
String fistLine = bufferedReader.readLine();
String[] firstLineTokens = fistLine.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 读取出来
// 此处的长度单位是 "字节"
int contentLength = Integer.parseInt(request.headers.get("Content-Length"));
// contentLength 长度单位是字节
// 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=huahua&password=666
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);
response 类:
public class Response
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 Response build(OutputStream outputStream)
Response response = new Response();
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();
主类:
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.读取请求并解析
Request request = Request.build(clientSocket.getInputStream());
Response response = Response.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 | NullPointerException e)
e.printStackTrace();
finally
try
clientSocket.close();
catch (IOException e)
e.printStackTrace();
private void doGet(Request request, Response response) throws IOException
//1.能够支持返回一个 html 文件
if(request.getUrl().startsWith("/index.html"))
// 让代码读取一个 /index.html 这样的文件
// 要想读文件,需要先知道文件路径 (只知道文件名)
// 此时 html 文件所属的路径,可以自己约定
// 把文件内容写到响应的 body 中
response.setStatus(200);
response.setMessage("OK");
response.setHeader("Content-Type","text/html; charset=utf-8");
// HttpServerV3.class获取一个类对象
// getClassLoader() 获取当前类的类加载器
InputStream inputStream = HttpServerV3.class.getClassLoader().getResourceAsStream("index.html");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
//按行读取内容,把数据写入到 response 中
String lint = " ";
while ((lint = bufferedReader.readLine()) != null)
response.writeBody(lint + "\\n");
bufferedReader.close();
private void doPost(Request request, Response response)
public static void main(String[] args) throws IOException
HttpServerV3 httpServerV3 = new HttpServerV3(6060);
httpServerV3.start();
要想能够支持返回一个 html 文件,就需要让代码读取一个 类似 /index.html 这样的文件,要想读文件,需要先知道文件路径 (但我们只知道文件名)
解决方法:
new 一个 和 src 同级的 Directory
右击新创建的 xxx (Directory):Mark Directory as — xxx Root
然后 new 一个 file:index.html
<html>
<head>
<title>登录界面</title>
<meta charset="UTF-8">
</head>
<body>
<!-- /login操作服务器端还没有实现 -->
<form method="post" action="/login">
<div style="margin-bottom: 8px">
<input type="text" name="username" placeholder="请输入用户名">
</div>
<div style="margin-bottom: 8px">
<input type="password" name="password" placeholder="请输入密码">
</div>
<div>
<input type="submit" value="登录">
</div>
</form>
</body>
</html>
写到这里,服务器已经可以返回一个指定的静态页面了
这个页面中包含了一个 form 表单,借助表单来实现登录操作
此时,启动服务器
由于表单是把数据提交到 /login 这个 path 中
服务器紧接着就要实现 POST 请求下的 /login 的处理
doPost 方法:
private void doPost(Request request, Response 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);
再次重启服务器,使用 fiddler 抓包:
流程:
接着,请求到达服务器上,解析成 Request 对象
用户名和密码这种键值对,就保存到 body 中,同时会把 body 中的键值对解析保存到 parameters 表中
doPost 方法获取参数:
在这个基础上,在继续实现验证用户名密码是否正确
private void doPost(Request request, Response response)
//2.实现 /login 的处理
if(request.getUrl().startsWith("/login"))
//读取用户提交的用户名和密码
String userName = request.getParameter("username");
String password = request.getParameter("password");
//登录逻辑需要验证用户名密码是否正确
// 此处为了简单,把用户名和密码在代码中写死
// 更科学的处理方式: 从数据库中读取用户名对应密码,检验密码是否一致
if("hh".equals(userName) && "666".equals(password))
// 登陆成功
response.setStatus(200);
response.setMessage("OK");
response.setHeader("Content-Type","text/html;charset=utf-8");
response.writeBody("<html>");
response.writeBody("<div>欢迎您!" + userName + "</div>");
response.writeBody("</html>");
else
// 登陆失败
response.setStatus(403);
response.setMessage("Forbidden");
response.setHeader("Content-Type","text/html;charset=utf-8");
response.writeBody("<html>");
response.writeBody("<div>登陆失败</div>");
response.writeBody("</html>");
重启服务器,进入页面:
使用 fiddler 抓包验证:
对于页面来说,若登录成功之后,刷新页面,自己仍然处于登陆状态
访问该网站的其他页面,此时仍然处在登录状态
如何实现上述功能? 就需要使用 Cookie
在 do
以上是关于Http Server 版本3 - 实现完整的登录过程(Cookie & Session)的主要内容,如果未能解决你的问题,请参考以下文章