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中存儲的是
    在這裏插入圖片描述
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章