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)的主要内容,如果未能解决你的问题,请参考以下文章

求SQL SERVER 2008R2 版本10.50.1600.1 完整安装包,下载地址也可以

php实现在不同国家显示网站的不同语言版本

简单实现 http Server (版本12)

简单实现 http Server (版本12)

用cas来实现php的单点登陆

JAVA CAS单点登录(SSO) 教程