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

news2024/7/4 5:42:46

前言

在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!”

博客主页:KC老衲爱尼姑的博客主页

博主的github,平常所写代码皆在于此

共勉:talk is cheap, show me the code

作者是爪哇岛的新手,水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!


文章目录

  • 模拟实现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请求。下图就描述的就是这一过程。

在这里插入图片描述

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进行对程序的测试

  • http://127.0.0.1:9090/200
  • http://127.0.0.1:9090/404
  • http://127.0.0.1:9090/302
  • http://127.0.0.1:9090/500

分别访问服务器. 观察效果.
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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。

分别访问服务器. 观察效果.
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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 这个登陆页面

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

总结

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


各位看官如果觉得文章写得不错,点赞评论关注走一波!谢谢啦!。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/852304.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Flask实现接口mock,安装及使用教程(一)

1、什么是接口mock 主要是针对单元测试的应用&#xff0c;它可以很方便的解除单元测试中各种依赖&#xff0c;大大的降低了编写单元测试的难度 2、什么是mock server 正常情况下&#xff1a;测试客户端——测试——> 被测系统 ——依赖——>外部服务依赖 在被测系统和…

CVE漏洞复现-CVE-2021-3493 Linux 提权内核漏洞

CVE-2021-3493 Linux 提权内核漏洞 漏洞描述 CVE-2021-3493 用户漏洞是 Linux 内核中没有文件系统中的 layfs 中的 Ubuntu over 特定问题&#xff0c;在 Ubuntu 中正确验证有关名称空间文件系统的应用程序。buntu 内核代码允许低权限用户在使用 unshare() 函数创建的用户命名…

象棋中“马”的题目(深搜)

题面 题目描述 中国象棋半张棋盘如图&#xff08;a&#xff09;所示。马自左下角往右上角跳。 今规定只许往右跳&#xff0c;不许往左跳&#xff0c;且要求马跳的方式按照&#xff08;b&#xff09;图顺时针深度优先递归。比如图&#xff08;a&#xff09;中所示为一种跳行路线…

使用动态规划实现错排问题-2023年全国青少年信息素养大赛Python复赛真题精选

[导读]&#xff1a;超平老师计划推出《全国青少年信息素养大赛Python编程真题解析》50讲&#xff0c;这是超平老师解读Python编程挑战赛真题系列的第15讲。 全国青少年信息素养大赛&#xff08;原全国青少年电子信息智能创新大赛&#xff09;是“世界机器人大会青少年机器人设…

Netty:服务端通过ServerBootstrap的childHandler函数设置处理客户端的ChannelHandler

说明 服务端通过io.netty.bootstrap.ServerBootstrap启动&#xff0c;ServerBootstrap的 childHandler(ChannelHandler childHandler)函数用于增加处理客户端的ChannelHandler。这个childHandler一般是ChannelInitializer的子类&#xff0c;用于配置ChannelPipeline&#xff0…

GD32F103VE串口与DMA传输

GD32F103VE串口与DMA传输&#xff0c;本测试采用的的串口1和DMA0之间的数据传输&#xff0c;然后通过RS485和其它设备进行数据交换&#xff0c;没有采用任何中断参与。 GD32F103VE的DMA0请求映射到串口&#xff1a; 1&#xff0c;USART0_RX映射到DMA0的通道4&#xff0c;USART…

springboot 多模块 每个模块进行单独打包

springboot项目目录结构 打包模块需要进行的配置 配置文件引入打包插件 <build><finalName>api</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifac…

APT80DQ40BG-ASEMI快恢复二极管APT80DQ40BG

编辑&#xff1a;ll APT80DQ40BG-ASEMI快恢复二极管APT80DQ40BG 型号&#xff1a;APT60DQ20BG 品牌&#xff1a;ASEMI 封装&#xff1a;TO-3P 恢复时间&#xff1a;≤50ns 正向电流&#xff1a;80A 反向耐压&#xff1a;400V 芯片个数&#xff1a;双芯片 引脚数量&…

MySQL|查看事务加锁情况

文章目录 使用information_schema数据库中的表获取锁信息INNODB_TRXINNODB_LOCKSINNODB_LOCK_WAITS 使用SHOW ENGINE INNODB STATUS获取信息补充 使用information_schema数据库中的表获取锁信息 在information_schema数据库中&#xff0c;有几个与事务和锁紧密相关的表 INNOD…

回归预测 | MATLAB实现K折交叉验证GRNN广义回归神经网络多输入单输出回归预测

回归预测 | MATLAB实现K折交叉验证GRNN广义回归神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现K折交叉验证GRNN广义回归神经网络多输入单输出回归预测效果一览基本介绍研究内容程序设计参考资料效果一览 基本介绍 回归预测 | MATLAB实现K折交叉验证GRNN广义回归神经…

MySQL高阶知识点(一) 一条 SQL查询语句是如何被执行的

一条 SQL查询语句是如何被执行的 MySQL 的基本架构示意图如下所示&#xff1a;

MySQL分析查询语句Explain

1概述 ​ 定位了查询慢的SQL之后&#xff0c;就可以使用EXPLAIN或者DESCRIBE工具做针对性的分析查询。两者使用方法相同&#xff0c;并且分析结果也是相同的。 ​ MySQL中有专门负责SQL语句优化的优化器模块&#xff0c;主要功能是计算分析系统中收集到的统计信息&#xff0c…

生信豆芽菜-limma差异分析使用说明

网站&#xff1a;http://www.sxdyc.com/diffLimmaAnalyse 一、limma简介 什么是limma&#xff1f; 首先要明白&#xff0c;不管哪种差异分析&#xff0c;其本质都是广义线性模型。 limma也是广义线性模型的一种&#xff0c;其对每个gene的表达量拟合一个线性方程。 limma的分析…

模拟实现消息队列项目(系列8) -- 实现MqClient

目录 前言 1. 创建ConnectionFactory 2. Connection 和 Channel 的定义 2.1 Connection 2.2 Channel 3. 封装请求响应读写操作 3.1 写入请求 3.2 读取响应 3.3 Connection中创建Channel 4. 封装发送请求的操作 4.1 创建Channel 4.2 阻塞等待服务器响应 4.3 添加响应信息到 basi…

【构造】CF1798 D

Problem - D - Codeforces 题意&#xff1a; 思路&#xff1a; 首先如果 a 全是 00&#xff0c;那么显然无解。 否则考虑从左到右构造新数列&#xff0c;维护新数列的前缀和 s。 如果 s≥0&#xff0c;则在剩余未加入的数中随便选择一个非正数添加到新数列末尾。如果 s<…

Semantic Kernel 入门系列:Memory内存

了解的运作原理之后&#xff0c;就可以开始使用Semantic Kernel来制作应用了。 Semantic Kernel将embedding的功能封装到了Memory中&#xff0c;用来存储上下文信息&#xff0c;就好像电脑的内存一样&#xff0c;而LLM就像是CPU一样&#xff0c;我们所需要做的就是从内存中取出…

视频声音怎么转换成文字?这四种转换方法很简单

将视频声音转换成文字的好处不仅仅限于方便记录、保存和查阅视频内容。它还可以大大提高视频内容的可访问性和可搜索性&#xff0c;使得非母语人士、听力障碍者等人群更容易理解视频内容&#xff0c;并且可以更快速地找到相关信息。此外&#xff0c;将视频声音转换成文字还可以…

ArcGISPro随机森林自动化调参分类预测模型展示

更改ArcGISPro的python环境变量请参考文章 ArcGISPro中如何使用机器学习脚本_Z_W_H_的博客-CSDN博客 脚本文件如下 点击运行 结果展示 负类预测概率 正类预测概率 二值化概率 文件夹&#xff08;模型验证结果&#xff09; 数据集数据库 ROC曲线 由于个人数据量太少所以…

工业4.0:欢迎来到智能制造

制造业正在经历一场被称为“工业4.0”的全新技术革命&#xff0c;这场革命将数字化、网络化、智能化和自动化技术融合在一起&#xff0c;旨在打造高质、高效、高产且可持续的智能工厂。工业4.0将彻底改变产品制造的方式&#xff0c;颠覆我们对制造业的传统认知。 什么是工业4.…

APP电话管家在各行业的应用

目前语音呼叫在各行业广泛应用&#xff0c;不管是电话销售也好&#xff0c;还是客户呼入咨询也好&#xff0c;部署呼叫中心对于业务提升&#xff0c;还是很有效率的。但是随着使用的行业越来越多&#xff0c;有些行业属性所在&#xff0c;需要有便于携带&#xff0c;企业管理可…