文章目录

  • 模拟实现HTTP服务器
  • HTTP服务器
  • HTTP服务器版本一
  • 创建HttpServer类
  • HTTP服务器版本二
  • 1.创建HttpRequest类
  • 2.创建HttpResponse类
  • 3.创建HttpServer类
  • HTTP服务器版本三
  • 1.创建HttpRequest类
  • 2.创建HttpResponse类
  • 3.创建HttpServer类
  • 实现process()方法
  • 实现doGet()方法
  • 实现doPost()方法


模拟实现HTTP服务器

HTTP服务器

概述

HTTP服务器本质上也是一种应用程序,通常运行在服务器之上,绑定了服务器的ip地址和某些客户端,这些客户端一般是谷歌,edge,火狐等浏览器。当浏览器发送HTTP请求就可以通过该请求向服务器获得网络资源,而服务器上的HTTP服务器就是解析来自客户端的HTTP请求以及处理HTTP请求。下图就描述的就是这一过程。

《Java-SE-第三十二章》之模拟实现HTTP服务器_服务器

HTTP底层是基于TCP实现的,所以接下来模拟实现简单的HTTP服务器使用Java中的TCP编程。

HTTP服务器版本一

在这个版本中,我们只是简单的解析GET请求,并根据请求路径来构造出不同的响应。

创建HttpServer类

  1. 先初始化 ServerSocket 和 线程池
  2. 在主循环中循环调用 accept 获取连接. 一旦获取到连接就立刻构造一个任务加入到线程池中. 这个任务负责解析请求并构造响应.
  3. 在线程池任务中, 先读取请求数据, 按行读取出首行和 header 部分. body 暂时不处理.
  4. 根据请求的 URL 的路径, 分别构造 “欢迎页面”, “没有找到页面”, 和重定向响应.

示例代码

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class HttpServer {
    //HTTPs是基于TCP实现的,所以该HTTP依旧按照TCP的基本格式开发
    private ServerSocket serverSocket;

    /**
     *
     * @param port 端口
     * @throws IOException
     */
    public HttpServer(int port) throws IOException {
        this.serverSocket = new ServerSocket(port);
    }

    public  void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService exec = Executors.newCachedThreadPool();
        while (true) {
            //1.获取连接
            Socket clinetSocket = serverSocket.accept();
            //处理请求,以短连接方式
            exec.execute(new Runnable() {
                @Override
                public void run() {
                    process(clinetSocket);
                }
            });

        }
    }

    /**
     * 解析并处理请求
     * @param clinetSocket
     */
    private void process(Socket clinetSocket) {
        //由于HTTP是文本协议,所以用字符流处理
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(clinetSocket.getInputStream()));
             BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(clinetSocket.getOutputStream()))
        ){
            //下面操作按照HTTP协议来解析
            //1.读取请求并解析
            String firstLine = reader.readLine();
            //HTTP首行是的部分是用空格分隔的
            String[] firstLineTokens = firstLine.split(" ");
            //得到HTTP请求的类型
            String method = firstLineTokens[0];
            //得到url
            String url = firstLineTokens[1];
            //得到HTTP版本
            String version = firstLineTokens[2];
            //解析header,按行读取,然后按冒号分隔键值对
            Map<String,String> headers = new HashMap<String,String>();
            String line = "";
            //注意:readLine读取的一行内容,是会自动去掉换行,对于空行来说,去掉换行,就变成了空字符串
            while ((line = reader.readLine())!=null&&!line.equals("")) {
                String [] headerTokens = line.split(": ");
                headers.put(headerTokens[0], headerTokens[1]);
            }
            //解析body,暂时不考虑
            //打印内容,看是否正确
            System.out.printf("%s %s %s\n",method,url,version);
            for (Map.Entry<String,String> entry : headers.entrySet()) {
                System.out.println(entry.getKey() + ": " + entry.getValue());
            }
            //处理请求
            String body = "";
            if (url.equals("/200")) {
                writer.write(version+" 200 ok\n");
                body = "<h1>Hello World</h1>";
            }else if (url.equals("/404")) {
                writer.write(version+" 404 ok\n");
                body = "<h1>NOT FOUND</h1>";
            }else if (url.equals("/302")) {
                writer.write(version+" 302 Found\n");
                //Location首部指定的是需要将页面重新定向至的地址
                writer.write("Location: http://www.bilibili.com\n");
                body = "";
            }else {
                writer.write(version+" 200 ok\n");
                body = "<h1>default</h1>";
            }
            //把响应写会给客户端
            writer.write("Content-type: text/html\n");
            writer.write("Content-Length: "+body.getBytes().length+"\n");
            writer.write("\n");
            writer.write(body);
            writer.flush();
            clinetSocket.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    public static void main(String[] args) throws IOException {
        HttpServer server = new HttpServer(9090);
        server.start();
    }
}

运行程序, 通过浏览器访问一下 URL进行对程序的测试

分别访问服务器. 观察效果.

《Java-SE-第三十二章》之模拟实现HTTP服务器_服务器_02


《Java-SE-第三十二章》之模拟实现HTTP服务器_HTTP_03


《Java-SE-第三十二章》之模拟实现HTTP服务器_java_04

HTTP服务器版本二

在版本1 的基础上, 我们做出一下改进:

  1. 把解析请求和构造响应的代码提取成单独的类
  2. 能够把 URL 中的 query string 解析成键值对.
  3. 能够给浏览器返回 Cookie

1.创建HttpRequest类

  1. 对照着 HTTP 请求的格式, 创建属性: method, url, version, headers.
  2. 创建 patameters, 用于存放 query string 的解析结果.
  3. 创建一个静态方法 build, 用来完成解析 HTTP 请求的过程.
  4. 从 socket 中读取数据的时候注意设置字符编码方式
  5. 创建一系列 getter 方法获取到请求中的属性.
  6. 单独写一个方法 parseKV 用来解析 query string

示例代码

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 version;
    private String url;
    private Map<String,String> headers = new HashMap<String,String>();
    private Map<String,String> parameters = new HashMap<String,String>();

    public static HttpRequest build(InputStream inputStream) throws IOException {
        HttpRequest request = new HttpRequest();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
        //1.解析首行
        String firstLine = reader.readLine();
        String  [] firstLineTokens = firstLine.split(" ");
        request.method = firstLineTokens[0];
        request.url = firstLineTokens[1];
        request.version = firstLineTokens[2];
        //2.解析url中的参数
        int pos = request.url.indexOf("?");
        if (pos != -1) {
            //获得url中?之后的字符,有字符则解析,没有就不处理
            String parameters = request.url.substring(pos + 1);
            //切分的结果,key a,value 10
            parseKV(parameters,request.parameters);
        }
        //3.解析header
        String line = "";
        while ((line = reader.readLine()) != null&&line.length()!=0) {
            String [] headerTokens = line.split(": ");
            request.headers.put(headerTokens[0], headerTokens[1]);
        }
        //4.解析body,暂时不考虑
        return  request;

    }

    /**
     * 将url中的键值对存储到output
     * @param parameters
     * @param output
     * @throws IOException
     */
    private static void parseKV(String parameters, Map<String, String> output) throws IOException{
        //按&切分成若干组键值对
        String [] kvTokens = parameters.split("&");
        //针对上述切分结果在按照=进行切分
        for (String kvToken : kvTokens) {
            String [] result = kvToken.split("=");
            output.put(result[0],result[1]);
        }
    }

    public String getMethod() {
        return method;
    }

    public String getVersion() {
        return version;
    }

    public String getUrl() {
        return url;
    }

    public String getHeaders(String key) {
        return headers.get(key);
    }

    public String getParameters(String key) {
        return parameters.get(key);
    }

    @Override
    public String toString() {
        return "HttpRequest{" +
                "method='" + method + '\'' +
                ", version='" + version + '\'' +
                ", url='" + url + '\'' +
                ", headers=" + headers +
                ", parameters=" + parameters +
                '}';
    }
}

2.创建HttpResponse类

  1. 根据 HTTP 响应, 创建属性: version, status, message, headers, body
  2. 另外创建一个 OutputStream, 用来关联到 Socket 的 OutputStream.
  3. 往 socket 中写入数据的时候注意指定字符编码方式.
  4. 创建一个静态方法 build, 用来构造 HttpResponse 对象.
  5. 创建一系列 setter 方法, 用来设置 HttpResponse 的属性.
  6. 创建一个 flush 方法, 用于最终把数据写入 OutputStream.

示例代码

import javax.xml.transform.OutputKeys;
import java.io.*;
import java.util.HashMap;
import java.util.Map;

/**
 * 负责响应
 */
public class HttpResponse {
    /**
     * HTTP版本
     */
    private String version = "HTTP/1.1";
    /**
     * 状态码
     */
    private int status;
    /**
     * 状态码的描述信息
     */
    private String message;
    /**
     * header
     */
    private Map<String, String> header = new HashMap<String, String>();
    /**
     * 响应体
     */
    private StringBuilder body = new StringBuilder();
    /**
     * 用于给客户端写数据
     */
    private OutputStream outputStream;

    public static HttpResponse build(OutputStream outputStream) {
        HttpResponse response = new HttpResponse();
        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(Map<String, String> header) {
        this.header = header;
    }

    public void writeBody(String content) {
        body.append(content);
    }

    public void flush() throws IOException {
        BufferedWriter write = new BufferedWriter(new OutputStreamWriter(outputStream,"UTF-8"));
        write.write(version+" "+status+" "+message+"\n");
        header.put("Content-Length",body.toString().getBytes().length+"");
        for(Map.Entry<String,String> entry:header.entrySet()) {
            write.write(entry.getKey()+":"+entry.getValue()+"\n");
        }
        write.write("\n");
        write.write(body.toString());
        write.flush();
    }

}

3.创建HttpServer类

示例代码

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class HttpServer {
    private ServerSocket serverSocket;

    public HttpServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService exec = Executors.newCachedThreadPool();
        while (true) {
            Socket clientSocket = serverSocket.accept();
            exec.execute(() -> process(clientSocket));
        }
    }

    /**
     * 处理请求
     *
     * @param clientSocket
     */
    private void process(Socket clientSocket) {
        try {
            //1.读取并解析请求
            HttpRequest request = HttpRequest.build(clientSocket.getInputStream());
            System.out.println("request:" + request);
            HttpResponse response = HttpResponse.build(clientSocket.getOutputStream());
            //2.根据请求计算响应
            if (request.getUrl().startsWith("/200")) {
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1>Hello World</h1>");
            } else if (request.getUrl().startsWith("/add")) {
                String atr = request.getParameters("a");
                String btr = request.getParameters("a");
                int a = Integer.parseInt(atr);
                int b = Integer.parseInt(btr);
                int result = a + b;
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1>result = " + result + "</h1>");
            } else if (request.getUrl().startsWith("/cookieUser")) {
                response.setStatus(200);
                response.setMessage("OK");
                // HTTP 的 header 中允许有多个 Set-Cookie 字段. 但是
                // 此处 response 中使用 HashMap 来表示 header 的. 此时相同的 key 就覆盖
                response.setHeader("Set-Cookie", "user=zhangsan");
                response.writeBody("<h1>set cookieUser</h1>");
            } else if (request.getUrl().startsWith("/cookieTime")) {
                response.setStatus(200);
                response.setMessage("OK");
                // HTTP 的 header 中允许有多个 Set-Cookie 字段. 但是
                // 此处 response 中使用 HashMap 来表示 header 的. 此时相同的 key 就覆盖
                response.setHeader("Set-Cookie", "time=" + (System.currentTimeMillis() / 1000));
                response.writeBody("<h1>set cookieTime</h1>");
            } else {
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1>default</h1>");
            }
            // 3. 把响应写回到客户端
            response.flush();
            // 4. 关闭 socket
            clientSocket.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
}

运行程序, 通过浏览器访问一下 URL进行对程序的测试

  1. http://127.0.0.1:9090/200,验证是否能显示欢迎页面。
  2. http://127.0.0.1:9090/add?a=10&b=20,验证能否计算出结果。
  3. http://127.0.0.1:9090/cookieUser,验证浏览器能否获取到 user=zhangsan 这个 Cookie。
  4. http://127.0.0.1:9090/cookieTime,验证浏览器能否获取到 user=[时间戳] 这个 Cookie。

分别访问服务器. 观察效果.

《Java-SE-第三十二章》之模拟实现HTTP服务器_http_05

《Java-SE-第三十二章》之模拟实现HTTP服务器_HTTP_06


《Java-SE-第三十二章》之模拟实现HTTP服务器_http_07


《Java-SE-第三十二章》之模拟实现HTTP服务器_java_08

HTTP服务器版本三

在版本 2 的基础上, 再做出进一步的改进.

  1. 解析请求中的 Cookie, 解析成键值对.
  2. body, 按照 x-www-form-urlencoded 的方式解析.
  3. 根据请求方法, 分别调用 doGet / doPost
  4. 能够返回指定的静态页面.
  5. 实现简单的会话机制.

1.创建HttpRequest类

代码整体和 版本2 类似, 做出了以下改变

  1. 属性中新增了 cookies 和 body
  2. 新增一个方法 parseCookie, 在解析 header 完成后解析 cookie
  3. 新增了解析 body 的流程.

实现代码

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 version;
    private String url;
    private String body;
    private Map<String, String> cookies = new HashMap<String, String>();
    private Map<String, String> headers = new HashMap<String, String>();
    private Map<String, String> parameters = new HashMap<String, String>();

    public static HttpRequest build(InputStream inputStream) throws IOException {
        HttpRequest request = new HttpRequest();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
        //1.解析首行
        String firstLine = reader.readLine();
        String[] firstLineTokens = firstLine.split(" ");
        request.method = firstLineTokens[0];
        request.url = firstLineTokens[1];
        request.version = firstLineTokens[2];
        //2.解析url中的参数
        int pos = request.url.indexOf("?");
        if (pos != -1) {
            //获得url中?之后的字符,有字符则解析,没有就不处理
            String parameters = request.url.substring(pos + 1);
            //切分的结果,key a,value 10
            parseKV(parameters, request.parameters);
        }
        //3.解析header
        String line = "";
        while ((line = reader.readLine()) != null && line.length() != 0) {
            String[] headerTokens = line.split(": ");
            request.headers.put(headerTokens[0], headerTokens[1]);
        }
        //4.解析cooike
        String cooike = request.headers.get("cooike");
        if (cooike != null) {
            paseCookie(cooike, request.cookies);
        }
        //5.解析body
        if ("POST".equalsIgnoreCase(request.method) || "PUT".equalsIgnoreCase(request.method)) {
            // 需要把 body 读取出来.
            // 需要先知道 body 的长度. Content-Length 就是干这个的.
            // 此处的长度单位是 "字节"
            int contentLength = Integer.parseInt(request.headers.get("Content-Length"));
            // 注意体会此处的含义~~
            // 例如 contentLength 为 100 , body 中有 100 个字节.
            // 下面创建的缓冲区长度是 100 个 char (相当于是 200 个字节)
            // 缓冲区不怕长. 就怕不够用. 这样创建的缓冲区才能保证长度管够~~
            char[] buffer = new char[contentLength];
            int len = reader.read(buffer);
            request.body = new String(buffer, 0, len);
            // body 中的格式形如: username=tanglaoshi&password=123
            parseKV(request.body, request.parameters);
        }
        return request;

    }

    private static void paseCookie(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]);
        }
    }

    /**
     * 将url中的键值对存储到output
     *
     * @param parameters
     * @param output
     * @throws IOException
     */
    private static void parseKV(String parameters, Map<String, String> output) throws IOException {
        //按&切分成若干组键值对
        String[] kvTokens = parameters.split("&");
        //针对上述切分结果在按照=进行切分
        for (String kvToken : kvTokens) {
            String[] result = kvToken.split("=");
            output.put(result[0], result[1]);
        }
    }

    public String getMethod() {
        return method;
    }

    public String getVersion() {
        return version;
    }

    public String getUrl() {
        return url;
    }

    public String getHeaders(String key) {
        return headers.get(key);
    }

    public String getBody() {
        return body;
    }

    public String getParameters(String key) {
        return parameters.get(key);
    }

    public String getCookie(String key) {
        return cookies.get(key);
    }

    @Override
    public String toString() {
        return "HttpRequest{" +
                "method='" + method + '\'' +
                ", version='" + version + '\'' +
                ", url='" + url + '\'' +
                ", headers=" + headers +
                ", parameters=" + parameters +
                '}';
    }
}

2.创建HttpResponse类

代码和 版本2 完全一致.

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 HttpResponse {
    /**
     * HTTP版本
     */
    private String version = "HTTP/1.1";
    /**
     * 状态码
     */
    private int status;
    /**
     * 状态码的描述信息
     */
    private String message;
    /**
     * header
     */
    private Map<String, String> header = new HashMap<String, String>();
    /**
     * 响应体
     */
    private StringBuilder body = new StringBuilder();
    /**
     * 用于给客户端写数据
     */
    private OutputStream outputStream;

    public static HttpResponse build(OutputStream outputStream) {
        HttpResponse response = new HttpResponse();
        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) {
        header.put(key,value);
    }

    public void writeBody(String content) {
        body.append(content);
    }

    public void flush() throws IOException {
        BufferedWriter write = new BufferedWriter(new OutputStreamWriter(outputStream,"UTF-8"));
        write.write(version+" "+status+" "+message+"\n");
        header.put("Content-Length",body.toString().getBytes().length+"");
        for(Map.Entry<String,String> entry:header.entrySet()) {
            write.write(entry.getKey()+":"+entry.getValue()+"\n");
        }
        write.write("\n");
        write.write(body.toString());
        write.flush();
    }

}

3.创建HttpServer类

新增一个 sessions 成员, 是一个键值对结构, 用来管理会话. key 是一个字符串. value 是一个 User 对象,User 是用于保存用户信息。

User类

示例代码

/**
 * 保存用户的相关信息
 */
public class User {
   public  String userName;
  public  int age;
  public String school;
}

HttpServer

示例代码

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.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class HttpServer {
    private ServerSocket serverSocket;
    private HashMap<String, User> sessions = new HashMap<>();

    public HttpServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService exec = Executors.newCachedThreadPool();
        while (true) {
            Socket clientSocket = serverSocket.accept();
            exec.execute(() -> process(clientSocket));
        }
    }

   
    public static void main(String[] args) throws IOException {
        HttpServer server = new HttpServer(9090);
        server.start();
    }
}
实现process()方法

根据请求方法的不同, 分别调用 doGet 和 doPost

/**
     * 处理请求
     *
     * @param clientSocket
     */
    private void process(Socket clientSocket) {
        // 处理核心逻辑
        try {
            // 1. 读取请求并解析
            HttpRequest request = HttpRequest.build(clientSocket.getInputStream());
            HttpResponse response = HttpResponse.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();
            // 4. 关闭 socket
            clientSocket.close();
        } catch (IOException | NullPointerException e) {
            e.printStackTrace();
        }

    }
实现doGet()方法

实现逻辑

  1. 首先根据请求路径判断用户是否已经登录
  2. 判断时候登录,先看Cooike是否存在SessionId,再看sessionId是否在sessions中存在
  3. 如果未登录,则返回一个静态页面index.html,这个页面存放在resourses下
  4. 通过 HttpServer.class.getClassLoader().getResourceAsStream(“index.html”) 能够打开该文件, 并读取文件内容.

实现代码

private void doGet(HttpRequest request, HttpResponse response) throws IOException {
        // 1. 能够支持返回一个 html 文件.
        if (request.getUrl().startsWith("/index.html")) {
            String sessionId = request.getCookie("sessionId");
            User user = sessions.get(sessionId);
            if (sessionId == null || user == null) {
                response.setStatus(200);
                response.setMessage("OK");
                response.setHeader("Content-Type", "text/html; charset=utf-8");
                InputStream inputStream = HttpServer.class.getClassLoader().getResourceAsStream("index.html");
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                // 按行读取内容, 把数据写入到 response 中
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    response.writeBody(line + "\n");
                }
                bufferedReader.close();
            } else {
                // 用户已经登陆, 无需再登陆了.
            }
        }
    }

实现index.html

通过 form 表单, 通过 POST 提交 username 和 password

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="login" method="POST">
    用户名:<input type="text" name="username">
    密码:<input type="text" name="password">
    <input type="submit" value="提交">
</form>
</body>
</html>
实现doPost()方法

实现逻辑

  1. 判断路径是否为login
  2. 获取表单中的用户名和密码对其进行校验
  3. 如果用户名和密码正确,则返回一个登录成功的页面
  4. 登录成功的同时,构造出一个SessionId和一个User对象,把这个键值放在sessions中,并把sessionId通过cookie返回给浏览器
  5. 登录失败,返回一个登录失败的页面

实现代码

private void doPost(HttpRequest request, HttpResponse response) {
        // 2. 实现 /login 的处理
        if (request.getUrl().startsWith("/login")) {
            // 读取用户提交的用户名和密码
            String userName = request.getParameters("username");
            String password = request.getParameters("password");
            System.out.println("userName: " + userName);
            System.out.println("password: " + password);
            if ("zhangsan".equals(userName) && "123".equals(password)) {
                // 登陆成功
                response.setStatus(200);
                response.setMessage("OK");
                response.setHeader("Content-Type", "text/html; charset=utf-8");
                String sessionId = UUID.randomUUID().toString();
                User user = new User();
                user.userName = "zhangsan";
                user.age = 20;
                user.school = "B站大学";
                sessions.put(sessionId, user);
                response.setHeader("Set-Cookie", "sessionId=" + sessionId);
                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>");
            }
        }
    }

运行程序. 通过以下 URL 验证: http://127.0.0.1:9090/index.html

(1)首次访问, 当前未登录, 会看到 index.html 这个登陆页面

《Java-SE-第三十二章》之模拟实现HTTP服务器_http_09

(2)输入用户名密码之后, 如果登陆成功, 预期看到

《Java-SE-第三十二章》之模拟实现HTTP服务器_服务器_10

(3)后续再访问 http://127.0.0.1:9090/index.html 时, 由于已经登陆过, 不必重新登陆

《Java-SE-第三十二章》之模拟实现HTTP服务器_http_11

总结

由此就完成了简单的HTTP服务器,虽然没有Tomcat那么强大,但是还是可以通过上述简陋的程序更好的理解处理HTTP请求的过程。