JavaWeb~使用HttpServer和Html 实现登录界面并跳转到个人简历(体会Cookie的用途)

实现请求解析

  • 注释详解
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();
    }
}

实现Http Sever

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("<h1>NULL</h1>");
                }
                //写入
                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">
             &nbsp;   我在大学期间任职学习委员,工作认真负责,深受同学
    喜爱 ,学习中,踏实学习本专业知识,和小组合作时,负责踏实
    具有强烈的团队合作精神和工作能力。
            </td>
        </tr>
</table>
        </body>
    
</html>

体会Cookie的功能

Cookie是啥? 就是一个字符串 只是里面的内容和意义是程序员自己定的
Cookie从哪来? 重服务器来 服务器会在返回的响应中引入一个Set-Cookie字段 对应的值就会保存在浏览器中
Cookie咋保存? 按域名保存/地址保存 每个域名/地址有自己的Cookie
Cookie如何用 浏览器后序访问同一个域名或者地址的时候 就会自动带上保存的Cookie 服务器可以感受到这个Cookie 服务器就可以做出不同的处理逻辑

  • 如上所述, Http 是一个无状态的协议,但是访问有些资源的时候往往需要经过认证的账户才能访问,而且要一直保持在线状态,所以,cookie是一种在浏览器端解决的方案,将登陆认证之后的用户信息保存在本地浏览器中,后面每次发起http请求,都自动携带上该信息,就能达到认证用户,保持用户在线的作用
  1. 启动服务器浏览器访问域名进入登录界面
    在这里插入图片描述
    在这里插入图片描述
  • 此时浏览器是没有保存Cookie的
    在这里插入图片描述
  • 而且此时请求中也是没有Cookie字段的
    在这里插入图片描述
  • 输入账号和密码登录 (如果输入错误就重新返回登录界面)
    在这里插入图片描述
  • 点击提交跳转到个人简历页面
    在这里插入图片描述
  • 我们通过抓包会发现在响应报文里会有Set-Cookie字段
    在这里插入图片描述
  • 然后浏览器处理响应报文的时候就会记住这个Cookie
    在这里插入图片描述
  • 当浏览器记住这个Cookie后 我们再次访问登录界面 就会直接跳转到个人简历
    在这里插入图片描述
  • 而且浏览器给服务器发送的请求中就会自动带上Cookie字段
    在这里插入图片描述

体会Session的功能

  • 上面我们体会到Cookie会将我们的账户信息保存在浏览器中 此时我们会发现 而将用户敏感信息放到本地浏览器中,能解决一定的问题,但是又引进了新的安全问题,一旦cookie丢失,用户信息泄露,也很容易造成跨站攻击,所以有了另一种解决方法,将用户敏感信息保存至服务器,而服务器本身采用md5算法或相关算法生成唯一值(session id),将该值保存值客户端浏览器,随后,客户端的后续请求,浏览器都会自动携带该id,进而再在服务器端认证,进而达到状态保持的效果

cookie vs session两者有什么区别呢?

  • Cookie以文本文件格式存储在浏览器中,而session存储在服务端
  • 因为每次发起 Http 请求,都要携带有效Cookie信息,所以Cookie一般都有大小限制,以防止增加网络压力,一般不超过4k
  • 可以轻松访问cookie值但是我们无法轻松访问会话(session)值,因此session方案更安全
  • 所以才会有我们上面演示的浏览器Cookie中存储的是
    在这里插入图片描述
  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值