Java阶段二Day03

news2024/12/28 19:06:14

Java阶段二Day03

文章目录

  • Java阶段二Day03
    • V5
      • BirdBootApplication
      • ClientHandler
      • HttpServletRequest
    • V6
      • BirdBootApplication
      • ClientHandler
      • HttpServletRequest
    • V7
      • BirdBootApplication
      • ClientHandler
      • HttpServletRequest
      • HttpServletResponse
    • V8
      • BirdBootApplication
      • ClientHandler
      • HttpServletRequest
      • HttpServletResponse
      • DispatcherServlet
    • SpringBoot核心逻辑
    • WebServer流程

V5

此版本完成响应客户端的工作
这里先将ClientHandler中处理一次交互的第三步:响应客户端 实现出来。
目标:将一个固定的html页面通过发送一个标准的HTTP响应回复给浏览器使其呈现出来。
需要的知识点:
HTTP的响应格式。

实现:
一:先创建第一个页面index.html
1:在src/main/resource下新建目录static
这个目录用于存放当前服务端下所有的静态资源。

2:在static目录下新建目录新建第一个页面:index.html

二:实现将index.html页面响应给浏览器
在ClientHandler第三步发送响应处,按照HTTP协议规定的响应格式,将该页面包含在正文部分将其发送给浏览器即可。

三:第二步测试成功后,我们就可以根据请求中浏览器传递过来的抽象路径去static目录下定位浏览器实际
请求的页面,然后用上述方式将该页面响应给浏览器,达到浏览器自主请求其需要的资源。

四:可以在BirdBootApplication中的start方法里将接受客户端链接的动作放在死循环里重复进行了。
无论浏览器请求多少次,我们都可以遵从一问一答的原则响应用户请求的所有页面了。

BirdBootApplication

package com.birdboot.core;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * 主启动类
 */
public class BirdBootApplication {
    private ServerSocket serverSocket;

    public BirdBootApplication(){
        try {
            System.out.println("正在启动服务端...");
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void start(){
        try {
            while(true) {
                System.out.println("等待客户端链接...");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端链接了!");
                //启动一个线程处理该客户端交互
                ClientHandler handler = new ClientHandler(socket);
                Thread t = new Thread(handler);
                t.start();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        BirdBootApplication application = new BirdBootApplication();
        application.start();
    }
}

ClientHandler

package com.birdboot.core;

import com.birdboot.http.HttpServletRequest;

import java.io.*;
import java.net.Socket;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * 该线程任务负责与指定的客户端进行HTTP交互
 * HTTP协议要求浏览器与服务端采取"一问一答"的模式。对此,这里的处理流程分为三步:
 * 1:解析请求
 * 2:处理请求
 * 3:发送响应
 */
public class ClientHandler implements Runnable {
    private Socket socket;

    public ClientHandler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        try {
            //1 解析请求
            HttpServletRequest request = new HttpServletRequest(socket);
            //获取请求的抽象路径
            String path = request.getUri();
            System.out.println(path);

            //2 处理请求


            //3 发送响应
            /*
                V5的第一个测试目标:将static目录下的index.html页面发送给浏览器

                相对路径中非常常用的一个:类加载路径
                是我们项目中任何一个类(该类的class文件)所在包的顶级包的上一层目录
                就是类加载路径。
                如果使用File去定位该目录,格式是固定的
                File dir = new File(
                    当前类名.class.getClassLoader().getResource(".").toURI()
                );

                以在当前类ClientHandler中使用为例:
                ClientHandler所在的包:com.birdboot.core。该包的顶层包是com,
                此时类加载路径就是指com所在的目录。
                在当前项目中代码编译后,src/main/java中所有的java类都会编译后
                放到该项目的target/classes目录中。而src/main/resources下的静态
                资源也会放到target/classes目录中。
                而com目录就是放到了target/classes中,因此该目录就是类加载路径。
                从该目录能看出,只要定位到他,就可以顺着该目录找到所有java类以及
                所有的静态资源
             */
            //定位当前项目的类加载路径
            File baseDir = new File(
                    ClientHandler.class.getClassLoader().getResource(".").toURI()
            );
            //定位类加载路径下的static目录
            File staticDir = new File(baseDir, "static");
            //定位static下的index.html
//            File file = new File(staticDir, "index.html");
            /*
               V5的第二个测试目标:根据浏览器输入的抽象路径去定位static下的资源
               并将其发送给浏览器
               此测试需要将上面66行代码注释

               改造后测试:
               浏览器输入路径:
               http://localhost:8088/index.html
               http://localhost:8088/classtable.html
             */
            File file = new File(staticDir, path);

            /*
                一个响应的大致格式:
                HTTP/1.1 200 OK(CRLF)
                Content-Type: text/html(CRLF)
                Content-Length: 2546(CRLF)(CRLF)
                1011101010101010101......(index.html页面内容)
             */
            //3.1发送状态行
            println("HTTP/1.1 200 OK");

            //3.2发送响应头
            println("Content-Type: text/html");
            println("Content-Length: "+file.length());
            //单独发送回车+换行,表示响应头发送完毕
            println("");

            //3.3发送响应正文(index.html页面内容)
            FileInputStream fis = new FileInputStream(file);
            OutputStream out = socket.getOutputStream();
            byte[] buf = new byte[1024*10];//10kb
            int d;//记录每次实际读取的数据量
            while( (d = fis.read(buf)) !=-1){
                out.write(buf,0,d);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

    }

    /**
     * 向客户端发送一行字符串
     * @param line
     */
    private void println(String line) throws IOException {
        OutputStream out = socket.getOutputStream();
        byte[] data = line.getBytes(StandardCharsets.ISO_8859_1);
        out.write(data);
        out.write(13);//发送回车符
        out.write(10);//发送换行符
    }


    public static void main(String[] args) throws URISyntaxException {
        File baseDir = new File(
                ClientHandler.class.getClassLoader()
                        .getResource(".").toURI()
        );
        //定位类加载路径下的static目录
        File staticDir = new File(baseDir, "static");
        //定位static下的index.html
        File file = new File(staticDir, "index.html");
        System.out.println("页面是否存在:" + file.exists());
    }

}

HttpServletRequest

package com.birdboot.http;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

/**
 * V4:新增内容
 * 请求对象
 * 该类的每一个实例用于表示浏览器发送过来的一个HTTP请求
 * HTTP协议要求请求的格式由三部分构成:请求行,消息头,消息正文
 */
public class HttpServletRequest {
    private Socket socket;
    //请求行相关信息
    private String method;//请求方式
    private String uri;//抽象路径
    private String protocol;//协议版本

    //消息头相关信息 key:消息头名字  value:消息头对应的值
    private Map<String,String> headers = new HashMap<>();


    public HttpServletRequest(Socket socket) throws IOException {
        this.socket = socket;

        //1.1解析请求行
        parseRequestLine();
        //        String line = readLine();
        //        System.out.println("请求行:"+line);
        //
        //        //将请求行按照空格("\s"在正则表达式中表示一个空白字符,包含空格)拆分为三部分
        //        String[] data = line.split("\\s");
        //        method = data[0];
        //        uri = data[1];
        //        protocol = data[2];
        //
        //        System.out.println("method:"+method);
        //        System.out.println("uri:"+uri);
        //        System.out.println("protocol:"+protocol);

        //1.2解析消息头
        parseHeaders();
        //        while(true) {
        //            line = readLine();
        //            if(line.isEmpty()){//如果读取到了空行
        //                break;
        //            }
        //            System.out.println("消息头:" + line);
        //            data = line.split(":\\s");
        //            headers.put(data[0],data[1]);
        //        }
        //        System.out.println("headers:"+headers);

        //1.3解析消息正文
        parseContent();
    }
    //解析请求行
    private void parseRequestLine() throws IOException {
        String line = readLine();
        System.out.println("请求行:"+line);

        //将请求行按照空格("\s"在正则表达式中表示一个空白字符,包含空格)拆分为三部分
        String[] data = line.split("\\s");
        method = data[0];
        uri = data[1];
        protocol = data[2];

        System.out.println("method:"+method);
        System.out.println("uri:"+uri);
        System.out.println("protocol:"+protocol);
    }
    //解析消息头
    private void parseHeaders() throws IOException {
        while(true) {
            String line = readLine();
            if(line.isEmpty()){//如果读取到了空行
                break;
            }
            System.out.println("消息头:" + line);
            String[] data = line.split(":\\s");
            headers.put(data[0],data[1]);
        }
        System.out.println("headers:"+headers);
    }
    //解析消息正文
    private void parseContent(){}



    /**
     * 通过socket获取的输入流读取客户端发送过来的一行字符串
     * @return
     */
    private String readLine() throws IOException {//通常被重用的代码不自己处理异常
        //对一个socket实例调用多次getInputStream()返回的始终是同一条输入流。而输出流也是如此
        InputStream in = socket.getInputStream();
        int d;
        char pre='a',cur='a';//pre表示上次读取的字符,cur表示本次读取的字符
        StringBuilder builder = new StringBuilder();//保存读取后的所有字符
        while((d = in.read())!=-1){
            cur = (char)d;//本次读取的字符
            if(pre==13 && cur==10){//是否连续读取到了回车+换行
                break;
            }
            builder.append(cur);//将本次读取的字符拼接
            pre=cur;//在进行下次读取前,将本次读取的字符保存到"上次读取的字符"中
        }
        return builder.toString().trim();
    }

    public String getMethod() {
        return method;
    }

    public String getUri() {
        return uri;
    }

    public String getProtocol() {
        return protocol;
    }

    /**
     * 根据给定的消息头的名字获取对应消息头的值
     * @param name
     * @return
     */
    public String getHeader(String name) {
        return headers.get(name);
    }
}

V6

完成404的响应
上一个版本中我们已经实现了根据浏览器中用户在地址栏上输入的URL中的抽象路径去
static目录下寻找对应资源进行响应的工作。

但是会存在路径输入有误,导致定位不对(要么定位的是一个目录,要么该文件不存在)
此时再发送响应的响应正文时使用文件输入流读取就会出现异常提示该资源不存在。

这是一个典型的404情况,因此我们在ClientHandler处理请求的环节,在实例化File
对象根据抽象路径定位static下的资源后,要添加一个分支,若该资源存在则将其响应
回去,如果不存在则要响应404状态代码和404页面提示用户。

实现:
1:在static下新建页面:404.html
该页面居中显示一行字即可:404,资源不存在!
2:在ClientHandler处理请求的环节,当实例化File对象后添加一个分支,如果该File
对象存在且表示的是一个文件则将其响应给浏览器
否则发送的响应做如下变化:

  1. 状态行中的状态代码改为404,状态描述改为NotFound
  2. 响应头Content-Length发送的是404页面的长度
  3. 响应正文为404页面内容

完成后,在浏览器地址栏输入一个不存在的资源地址,检查服务端是否正确响应404页面

404响应
HTTP/1.1 404 NotFound(CRLF)
Content-Type: text/html(CRLF)
Content-Length: 2546(CRLF)(CRLF) <—- 404页面长度
1011101010101010101… <—- 404页面内容

http://localhost:8088/
http://localhost:8088/123.html

BirdBootApplication

package com.birdboot.core;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 主启动类
 */
public class BirdBootApplication {
    private ServerSocket serverSocket;

    public BirdBootApplication(){
        try {
            System.out.println("正在启动服务端...");
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start(){
        try {
            while(true) {
                System.out.println("等待客户端链接...");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端链接了!");
                //启动一个线程处理该客户端交互
                ClientHandler handler = new ClientHandler(socket);
                Thread t = new Thread(handler);
                t.start();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        BirdBootApplication application = new BirdBootApplication();
        application.start();
    }
}

ClientHandler

package com.birdboot.core;

import com.birdboot.http.HttpServletRequest;

import java.io.*;
import java.net.Socket;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * 该线程任务负责与指定的客户端进行HTTP交互
 * HTTP协议要求浏览器与服务端采取"一问一答"的模式。对此,这里的处理流程分为三步:
 * 1:解析请求
 * 2:处理请求
 * 3:发送响应
 */
public class ClientHandler implements Runnable {
    private Socket socket;

    public ClientHandler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        try {
            //1 解析请求
            HttpServletRequest request = new HttpServletRequest(socket);


            //2 处理请求
            //获取请求的抽象路径
            String path = request.getUri();
            System.out.println(path);
            //定位当前项目的类加载路径
            File baseDir = new File(
                    ClientHandler.class.getClassLoader().getResource(".").toURI()
            );
            //定位类加载路径下的static目录
            File staticDir = new File(baseDir, "static");
            /*
                File定位static下用户请求的资源可能不存在或者定位的是一个目录
                这都属于失效的情况
                例如:
                http://localhost:8088/123.html   static下没有123.html

                http://localhost:8088/           定位的就是static目录本身

                上述两种情况都应当响应404

             */
            File file = new File(staticDir, path);

            int statusCode;//状态代码
            String statusReason;//状态描述
            if(file.isFile()){
                statusCode=200;
                statusReason="OK";
            }else{
                statusCode=404;
                statusReason="NotFound";
                file = new File(staticDir,"404.html");
            }



            //3 发送响应
            //3.1发送状态行
            println("HTTP/1.1"+" "+statusCode+" "+statusReason);
            //3.2发送响应头
            println("Content-Type: text/html");
            println("Content-Length: "+file.length());
            //单独发送回车+换行,表示响应头发送完毕
            println("");
            //3.3发送响应正文(index.html页面内容)
            FileInputStream fis = new FileInputStream(file);
            OutputStream out = socket.getOutputStream();
            byte[] buf = new byte[1024*10];//10kb
            int d;//记录每次实际读取的数据量
            while( (d = fis.read(buf)) !=-1){
                out.write(buf,0,d);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

    }

    /**
     * 向客户端发送一行字符串
     * @param line
     */
    private void println(String line) throws IOException {
        OutputStream out = socket.getOutputStream();
        byte[] data = line.getBytes(StandardCharsets.ISO_8859_1);
        out.write(data);
        out.write(13);//发送回车符
        out.write(10);//发送换行符
    }


    public static void main(String[] args) throws URISyntaxException {
        File baseDir = new File(
                ClientHandler.class.getClassLoader()
                        .getResource(".").toURI()
        );
        //定位类加载路径下的static目录
        File staticDir = new File(baseDir, "static");
        //定位static下的index.html
        File file = new File(staticDir, "index.html");
        System.out.println("页面是否存在:" + file.exists());
    }

}

HttpServletRequest

package com.birdboot.http;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

/**
 * V4:新增内容
 * 请求对象
 * 该类的每一个实例用于表示浏览器发送过来的一个HTTP请求
 * HTTP协议要求请求的格式由三部分构成:请求行,消息头,消息正文
 */
public class HttpServletRequest {
    private Socket socket;
    //请求行相关信息
    private String method;//请求方式
    private String uri;//抽象路径
    private String protocol;//协议版本

    //消息头相关信息 key:消息头名字  value:消息头对应的值
    private Map<String,String> headers = new HashMap<>();


    public HttpServletRequest(Socket socket) throws IOException {
        this.socket = socket;

        //1.1解析请求行
        parseRequestLine();

        //1.2解析消息头
        parseHeaders();
        //1.3解析消息正文
        parseContent();
    }
    //解析请求行
    private void parseRequestLine() throws IOException {
        String line = readLine();
        System.out.println("请求行:"+line);

        //将请求行按照空格("\s"在正则表达式中表示一个空白字符,包含空格)拆分为三部分
        String[] data = line.split("\\s");
        method = data[0];
        uri = data[1];
        protocol = data[2];

        System.out.println("method:"+method);
        System.out.println("uri:"+uri);
        System.out.println("protocol:"+protocol);
    }
    //解析消息头
    private void parseHeaders() throws IOException {
        while(true) {
            String line = readLine();
            if(line.isEmpty()){//如果读取到了空行
                break;
            }
            System.out.println("消息头:" + line);
            String[] data = line.split(":\\s");
            headers.put(data[0],data[1]);
        }
        System.out.println("headers:"+headers);
    }
    //解析消息正文
    private void parseContent(){}



    /**
     * 通过socket获取的输入流读取客户端发送过来的一行字符串
     * @return
     */
    private String readLine() throws IOException {//通常被重用的代码不自己处理异常
        //对一个socket实例调用多次getInputStream()返回的始终是同一条输入流。而输出流也是如此
        InputStream in = socket.getInputStream();
        int d;
        char pre='a',cur='a';//pre表示上次读取的字符,cur表示本次读取的字符
        StringBuilder builder = new StringBuilder();//保存读取后的所有字符
        while((d = in.read())!=-1){
            cur = (char)d;//本次读取的字符
            if(pre==13 && cur==10){//是否连续读取到了回车+换行
                break;
            }
            builder.append(cur);//将本次读取的字符拼接
            pre=cur;//在进行下次读取前,将本次读取的字符保存到"上次读取的字符"中
        }
        return builder.toString().trim();
    }

    public String getMethod() {
        return method;
    }

    public String getUri() {
        return uri;
    }

    public String getProtocol() {
        return protocol;
    }

    /**
     * 根据给定的消息头的名字获取对应消息头的值
     * @param name
     * @return
     */
    public String getHeader(String name) {
        return headers.get(name);
    }
}

V7

重构代码
将ClientHandler中发送响应的工作拆分出去

实现:

  1. 在com.webserver.http包中新建类:HttpServletResponse 响应对象
  2. 在响应对象中定义对应的属性来保存响应内容并定义response方法来发送响应.
  3. 修改ClientHandler,使用响应对象完整响应的发送

BirdBootApplication

package com.birdboot.core;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 主启动类
 */
public class BirdBootApplication {
    private ServerSocket serverSocket;

    public BirdBootApplication(){
        try {
            System.out.println("正在启动服务端...");
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start(){
        try {
            while(true) {
                System.out.println("等待客户端链接...");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端链接了!");
                //启动一个线程处理该客户端交互
                ClientHandler handler = new ClientHandler(socket);
                Thread t = new Thread(handler);
                t.start();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        BirdBootApplication application = new BirdBootApplication();
        application.start();
    }
}

ClientHandler

package com.birdboot.core;

import com.birdboot.http.HttpServletRequest;
import com.birdboot.http.HttpServletResponse;

import java.io.*;
import java.net.Socket;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * 该线程任务负责与指定的客户端进行HTTP交互
 * HTTP协议要求浏览器与服务端采取"一问一答"的模式。对此,这里的处理流程分为三步:
 * 1:解析请求
 * 2:处理请求
 * 3:发送响应
 */
public class ClientHandler implements Runnable {
    private Socket socket;

    public ClientHandler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        try {
            //1 解析请求
            HttpServletRequest request = new HttpServletRequest(socket);
            HttpServletResponse response = new HttpServletResponse(socket);

            //2 处理请求
            //获取请求的抽象路径
            String path = request.getUri();
            System.out.println(path);
            //定位当前项目的类加载路径
            File baseDir = new File(
                    ClientHandler.class.getClassLoader().getResource(".").toURI()
            );
            //定位类加载路径下的static目录
            File staticDir = new File(baseDir, "static");
            File file = new File(staticDir, path);

            /*
                V7改造
                1:将原来在这里定义的两个局部变量statusCode和statusReason删除
                2:将处理结果设置到response对应的属性上
                3:将发送响应的操作移动到HttpServletResponse的response方法中
                  并在第三步发送响应时改为调用response方法发送响应
             */
            if(file.isFile()){
                //由于响应对象中状态代码和描述默认值为200,OK因此正确情况下不用再设置
                response.setContentFile(file);
            }else{
                response.setStatusCode(404);
                response.setStatusReason("NotFound");
                file = new File(staticDir,"404.html");
                response.setContentFile(file);
            }

            //3 发送响应
            response.response();

        } catch (IOException e) {
            e.printStackTrace();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

    }
}

HttpServletRequest

package com.birdboot.http;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

/**
 * V4:新增内容
 * 请求对象
 * 该类的每一个实例用于表示浏览器发送过来的一个HTTP请求
 * HTTP协议要求请求的格式由三部分构成:请求行,消息头,消息正文
 */
public class HttpServletRequest {
    private Socket socket;
    //请求行相关信息
    private String method;//请求方式
    private String uri;//抽象路径
    private String protocol;//协议版本

    //消息头相关信息 key:消息头名字  value:消息头对应的值
    private Map<String,String> headers = new HashMap<>();


    public HttpServletRequest(Socket socket) throws IOException {
        this.socket = socket;

        //1.1解析请求行
        parseRequestLine();
  
        //1.2解析消息头
        parseHeaders();

        //1.3解析消息正文
        parseContent();
    }
    //解析请求行
    private void parseRequestLine() throws IOException {
        String line = readLine();
        System.out.println("请求行:"+line);

        //将请求行按照空格("\s"在正则表达式中表示一个空白字符,包含空格)拆分为三部分
        String[] data = line.split("\\s");
        method = data[0];
        uri = data[1];
        protocol = data[2];

        System.out.println("method:"+method);
        System.out.println("uri:"+uri);
        System.out.println("protocol:"+protocol);
    }
    //解析消息头
    private void parseHeaders() throws IOException {
        while(true) {
            String line = readLine();
            if(line.isEmpty()){//如果读取到了空行
                break;
            }
            System.out.println("消息头:" + line);
            String[] data = line.split(":\\s");
            headers.put(data[0],data[1]);
        }
        System.out.println("headers:"+headers);
    }
    //解析消息正文
    private void parseContent(){}



    /**
     * 通过socket获取的输入流读取客户端发送过来的一行字符串
     * @return
     */
    private String readLine() throws IOException {//通常被重用的代码不自己处理异常
        //对一个socket实例调用多次getInputStream()返回的始终是同一条输入流。而输出流也是如此
        InputStream in = socket.getInputStream();
        int d;
        char pre='a',cur='a';//pre表示上次读取的字符,cur表示本次读取的字符
        StringBuilder builder = new StringBuilder();//保存读取后的所有字符
        while((d = in.read())!=-1){
            cur = (char)d;//本次读取的字符
            if(pre==13 && cur==10){//是否连续读取到了回车+换行
                break;
            }
            builder.append(cur);//将本次读取的字符拼接
            pre=cur;//在进行下次读取前,将本次读取的字符保存到"上次读取的字符"中
        }
        return builder.toString().trim();
    }

    public String getMethod() {
        return method;
    }

    public String getUri() {
        return uri;
    }

    public String getProtocol() {
        return protocol;
    }

    /**
     * 根据给定的消息头的名字获取对应消息头的值
     * @param name
     * @return
     */
    public String getHeader(String name) {
        return headers.get(name);
    }
}

HttpServletResponse

package com.birdboot.http;

import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * V7新增内容:
 * 响应对象
 * 该类的每一个实例用于表示服务端给客户端发送的一个HTTP的响应
 * HTTP协议要求一个响应由三部分构成:状态行,响应头,响应正文
 */
public class HttpServletResponse {
    private Socket socket;

    //状态行相关信息
    private int statusCode = 200;//状态代码
    private String statusReason = "OK";//状态描述

    //响应头相关信息

    //响应正文相关信息
    private File contentFile;//响应正文对应的实体文件


    public HttpServletResponse(Socket socket){
        this.socket = socket;
    }

    /**
     * 该方法用于将当前响应对象内容以标准的HTTP响应格式发送给客户端
     */
    public void response() throws IOException {
        //3.1发送状态行
        sendStatusLine();
        //3.2发送响应头
        sendHeaders();
        //3.3发送响应正文
        sendContent();


    //发送状态行
    private void sendStatusLine() throws IOException {
        println("HTTP/1.1"+" "+statusCode+" "+statusReason);
    }
    //发送响应头
    private void sendHeaders() throws IOException {
        println("Content-Type: text/html");
        println("Content-Length: "+contentFile.length());
        //单独发送回车+换行,表示响应头发送完毕
        println("");
    }
    //发送响应正文
    private void sendContent() throws IOException {
        FileInputStream fis = new FileInputStream(contentFile);
        OutputStream out = socket.getOutputStream();
        byte[] buf = new byte[1024*10];//10kb
        int d;//记录每次实际读取的数据量
        while( (d = fis.read(buf)) !=-1){
            out.write(buf,0,d);
        }
    }


    /**
     * V7:将ClientHandler中发送响应的工作全部移动到这里,println方法也是。
     * 向客户端发送一行字符串
     * @param line
     */
    private void println(String line) throws IOException {
        OutputStream out = socket.getOutputStream();
        byte[] data = line.getBytes(StandardCharsets.ISO_8859_1);
        out.write(data);
        out.write(13);//发送回车符
        out.write(10);//发送换行符
    }

    public int getStatusCode() {
        return statusCode;
    }

    public void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }

    public String getStatusReason() {
        return statusReason;
    }

    public void setStatusReason(String statusReason) {
        this.statusReason = statusReason;
    }

    public File getContentFile() {
        return contentFile;
    }

    public void setContentFile(File contentFile) {
        this.contentFile = contentFile;
    }
}

V8

重构代码
将ClientHandler中处理请求的操作拆分出去

实现:

  1. 在com.webserver.core包下新建类:DispatcherServlet

    并定义service方法,用来处理请求

  2. 将ClientHandler处理请求的操作移动到service方法中去

  3. ClientHandler通过调用DispatcherServlet的service完成处理请求环节.

  4. 将DispatcherServlet定义为单例的(使用单例模式)

BirdBootApplication

package com.birdboot.core;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 主启动类
 */
public class BirdBootApplication {
    private ServerSocket serverSocket;

    public BirdBootApplication(){
        try {
            System.out.println("正在启动服务端...");
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start(){
        try {
            while(true) {
                System.out.println("等待客户端链接...");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端链接了!");
                //启动一个线程处理该客户端交互
                ClientHandler handler = new ClientHandler(socket);
                Thread t = new Thread(handler);
                t.start();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        BirdBootApplication application = new BirdBootApplication();
        application.start();
    }
}

ClientHandler

package com.birdboot.core;

import com.birdboot.http.HttpServletRequest;
import com.birdboot.http.HttpServletResponse;

import java.io.*;
import java.net.Socket;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * 该线程任务负责与指定的客户端进行HTTP交互
 * HTTP协议要求浏览器与服务端采取"一问一答"的模式。对此,这里的处理流程分为三步:
 * 1:解析请求
 * 2:处理请求
 * 3:发送响应
 */
public class ClientHandler implements Runnable {
    private Socket socket;

    public ClientHandler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        try {
            //1 解析请求
            HttpServletRequest request = new HttpServletRequest(socket);
            HttpServletResponse response = new HttpServletResponse(socket);

            //2 处理请求
            //V8改造:将处理请求的操作移动到DispatcherServlet的service方法中并调用
            DispatcherServlet servlet = new DispatcherServlet();
            servlet.service(request,response);

            //3 发送响应
            response.response();


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //V8新增内容:交互后与浏览器断开连接
            //HTTP协议要求浏览器与服务端交互完毕后要断开连接
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

HttpServletRequest

package com.birdboot.http;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

/**
 * V4:新增内容
 * 请求对象
 * 该类的每一个实例用于表示浏览器发送过来的一个HTTP请求
 * HTTP协议要求请求的格式由三部分构成:请求行,消息头,消息正文
 */
public class HttpServletRequest {
    private Socket socket;
    //请求行相关信息
    private String method;//请求方式
    private String uri;//抽象路径
    private String protocol;//协议版本

    //消息头相关信息 key:消息头名字  value:消息头对应的值
    private Map<String,String> headers = new HashMap<>();

    public HttpServletRequest(Socket socket) throws IOException {
        this.socket = socket;

        //1.1解析请求行
        parseRequestLine();
        //1.2解析消息头
        parseHeaders(); 
        //1.3解析消息正文
        parseContent();
    }
    //解析请求行
    private void parseRequestLine() throws IOException {
        String line = readLine();
        System.out.println("请求行:"+line);

        //将请求行按照空格("\s"在正则表达式中表示一个空白字符,包含空格)拆分为三部分
        String[] data = line.split("\\s");
        method = data[0];
        uri = data[1];
        protocol = data[2];

        System.out.println("method:"+method);
        System.out.println("uri:"+uri);
        System.out.println("protocol:"+protocol);
    }
    //解析消息头
    private void parseHeaders() throws IOException {
        while(true) {
            String line = readLine();
            if(line.isEmpty()){//如果读取到了空行
                break;
            }
            System.out.println("消息头:" + line);
            String[] data = line.split(":\\s");
            headers.put(data[0],data[1]);
        }
        System.out.println("headers:"+headers);
    }
    //解析消息正文
    private void parseContent(){}



    /**
     * 通过socket获取的输入流读取客户端发送过来的一行字符串
     * @return
     */
    private String readLine() throws IOException {//通常被重用的代码不自己处理异常
        //对一个socket实例调用多次getInputStream()返回的始终是同一条输入流。而输出流也是如此
        InputStream in = socket.getInputStream();
        int d;
        char pre='a',cur='a';//pre表示上次读取的字符,cur表示本次读取的字符
        StringBuilder builder = new StringBuilder();//保存读取后的所有字符
        while((d = in.read())!=-1){
            cur = (char)d;//本次读取的字符
            if(pre==13 && cur==10){//是否连续读取到了回车+换行
                break;
            }
            builder.append(cur);//将本次读取的字符拼接
            pre=cur;//在进行下次读取前,将本次读取的字符保存到"上次读取的字符"中
        }
        return builder.toString().trim();
    }

    public String getMethod() {
        return method;
    }

    public String getUri() {
        return uri;
    }

    public String getProtocol() {
        return protocol;
    }

    /**
     * 根据给定的消息头的名字获取对应消息头的值
     * @param name
     * @return
     */
    public String getHeader(String name) {
        return headers.get(name);
    }
}

HttpServletResponse

package com.birdboot.http;

import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * V7新增内容:
 * 响应对象
 * 该类的每一个实例用于表示服务端给客户端发送的一个HTTP的响应
 * HTTP协议要求一个响应由三部分构成:状态行,响应头,响应正文
 */
public class HttpServletResponse {
    private Socket socket;

    //状态行相关信息
    private int statusCode = 200;//状态代码
    private String statusReason = "OK";//状态描述

    //响应头相关信息

    //响应正文相关信息
    private File contentFile;//响应正文对应的实体文件


    public HttpServletResponse(Socket socket){
        this.socket = socket;
    }

    /**
     * 该方法用于将当前响应对象内容以标准的HTTP响应格式发送给客户端
     */
    public void response() throws IOException {
        //3.1发送状态行
        sendStatusLine();
        //3.2发送响应头
        sendHeaders();
        //3.3发送响应正文
        sendContent();

    }

    //发送状态行
    private void sendStatusLine() throws IOException {
        println("HTTP/1.1"+" "+statusCode+" "+statusReason);
    }
    //发送响应头
    private void sendHeaders() throws IOException {
        println("Content-Type: text/html");
        println("Content-Length: "+contentFile.length());
        //单独发送回车+换行,表示响应头发送完毕
        println("");
    }
    //发送响应正文
    private void sendContent() throws IOException {
        FileInputStream fis = new FileInputStream(contentFile);
        OutputStream out = socket.getOutputStream();
        byte[] buf = new byte[1024*10];//10kb
        int d;//记录每次实际读取的数据量
        while( (d = fis.read(buf)) !=-1){
            out.write(buf,0,d);
        }
    }

    /**
     * V7:将ClientHandler中发送响应的工作全部移动到这里,println方法也是。
     * 向客户端发送一行字符串
     * @param line
     */
    private void println(String line) throws IOException {
        OutputStream out = socket.getOutputStream();
        byte[] data = line.getBytes(StandardCharsets.ISO_8859_1);
        out.write(data);
        out.write(13);//发送回车符
        out.write(10);//发送换行符
    }

    public int getStatusCode() {
        return statusCode;
    }

    public void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }

    public String getStatusReason() {
        return statusReason;
    }

    public void setStatusReason(String statusReason) {
        this.statusReason = statusReason;
    }

    public File getContentFile() {
        return contentFile;
    }

    public void setContentFile(File contentFile) {
        this.contentFile = contentFile;
    }
}

DispatcherServlet

package com.birdboot.core;

import com.birdboot.http.HttpServletRequest;
import com.birdboot.http.HttpServletResponse;

import java.io.File;
import java.net.URISyntaxException;

/**
 * V8新增内容:
 * 该类是SpringMVC框架与Tomcat整合时的一个关键类
 * Tomcat处理业务原生的都是调用继承了HttpServlet的类来完成,此时需要进行很多配置
 * 以及使用时要作很多重复性劳动。
 * SpringMVC框架提供的该类也是继承了HttpServlet的,使用它来接收处理请求的工作。
 */
public class DispatcherServlet {
    private static File baseDir;//类加载路径
    private static File staticDir;//类加载路径下的static目录

    static{
        try {
            //定位当前项目的类加载路径
            baseDir = new File(
                    DispatcherServlet.class.getClassLoader().getResource(".").toURI()
            );
            //定位类加载路径下的static目录
            staticDir = new File(baseDir, "static");
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }


    /**
     * service方法实际上是当我们继承了HttpServlet后必须重写的方法
     * 该方法要求接收两个参数:请求对象与响应对象。
     * Tomcat在处理请求时就是调用某个Servlet的service方法并将请求与响应对象传入
     * 来让其完成处理工作的。
     */
    public void service(HttpServletRequest request, HttpServletResponse response){
        //获取请求的抽象路径
        String path = request.getUri();
        System.out.println(path);

        File file = new File(staticDir, path);
        if(file.isFile()){
            //由于响应对象中状态代码和描述默认值为200,OK因此正确情况下不用再设置
            response.setContentFile(file);
        }else{
            response.setStatusCode(404);
            response.setStatusReason("NotFound");
            file = new File(staticDir,"404.html");
            response.setContentFile(file);
        }
    }
}

SpringBoot核心逻辑

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5JnWC8C6-1681822496271)(E:/TeduWork/bird-boot2303-teacher/SpringBoot.png)]

WebServer流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JbTaaLAH-1681822496272)(E:/TeduWork/bird-boot2303-teacher/V7/WebServer%E6%B5%81%E7%A8%8B.png)]

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

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

相关文章

【Unity入门】14.值类型和引用类型

【Unity入门】值类型和引用类型 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity入门系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;值类型 &#xff08;1&#xff09;C#的值类型 1. 布尔类型&#xff08;bool&#xff09; 2. 字符类型&#x…

【软考备战·希赛网每日一练】2023年4月18日

文章目录 一、今日成绩二、错题总结第一题第二题第三题 三、知识查缺 题目及解析来源&#xff1a;2023年04月18日软件设计师每日一练 一、今日成绩 二、错题总结 第一题 解析&#xff1a; MTTF&#xff1a;平均无故障时间 MTTR&#xff1a;平均故障修复时间 可用性/可靠性MTTF…

一文入门Typrscript和Vue3

一、TypeScript快速上手 在TypeScript专栏已经详细介绍过TypeScript,在此总结一下TypeScript和Vue3的基础知识。 1. 初识 TypeScript 1.1 TypeScript 的介绍 TypeScript是一种由微软开发的开源、跨平台的编程语言。它是JavaScript的超集&#xff0c;最终会被编译为JavaScrip…

Python轻量级Web框架Flask(9)——图书馆项目

1、项目要求&#xff1a; 创建一个项目&#xff0c;用来说明出版社&#xff0c;书籍和作者的关系。作者和书籍之间的关系&#xff1a;1对多&#xff08;一本书由一个作者完成&#xff0c;一本书可以有多个创作者&#xff09;出版社和书籍之间的关系&#xff1a;多对多&#xf…

day15 消息队列

目录 消息队列 消息队列的使用 发送消息 消息的接收 消息队列的控制 消息队列 概念&#xff1a; 消息队列是system V IPC对象的一种&#xff1b; 消息队列有消息队列ID来唯一标识&#xff1b; 消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等&a…

有介质的高斯定理详细证明(电偶极子模型)以及例题讲解

目录 静电场中的电介质 电极化强度的引入 电偶极子模型的计算 电介质极化过程 极化电荷引入 推导 各向同性和线性的电介质 例题 静电场中的电介质 电介质与导体的区别&#xff1a;所有的粒子被束缚在原子核周围&#xff08;限制空间&#xff09; 电介质分为两种 1.无…

OldWang带你了解MySQL(十)

文章目录 &#x1f525;MySQL事务&#x1f525;使用事务&#x1f525;事务的并发问题&#x1f525;MySQL的用户管理&#x1f525;MySQL分页查询 &#x1f525;MySQL事务 事务简介 事务是指作为单个逻辑工作单元执行的一系列操作&#xff0c;要么完全地执行&#xff0c;要么完…

【网络小知识】当我在浏览器url输入文本后的解析流程/http协议对比https协议

当我们在地址栏输入信息之后&#xff0c;我们会得到浏览器给我们一个返回的信息&#xff0c;那么这个信息怎么出来的&#xff1f;且看下文~~~ 目录 ⭐一、浏览器url解析流程⭐二、http协议对比https协议 ⭐一、浏览器url解析流程 在浏览器地址栏输入文本后&#xff0c;比如 “…

window.postMessage()接收不到信息(失效)

问题描述 按照正常的代码逻辑&#xff0c;应该是这个样子&#xff0c;通过iframe打开子窗口&#xff0c;能够正常通信。 // 接收端&#xff08;父窗口&#xff09; window.onmessage function (e) {// 逻辑代码 }// 发送端&#xff08;子窗口&#xff09; window.parent.po…

第一次参加CSDN周赛,这体验很难说···

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 本文章收录于专栏 【CSDN周赛】 本篇文章目录&#x1f30f;前言&#x1f30f;一、勾股数&#x1f338;题目描述&#x1f338;题解&…

Android Activity 了解

前言 : Android 系统的四大组件分别是 1 活动 &#xff08;Activity&#xff09; 2 服务&#xff08;Service&#xff09; 3 广播接收器&#xff08;Broadcast Receiver&#xff09; 4 内容提供器 &#xff08;Content Provider&#xff09; 其中活动是所有安卓应用程序的…

rust闭包(Closure)

闭包(Closure) 闭包在现代化的编程语言中普遍存在。闭包是一种匿名函数&#xff0c;它可以赋值给变量也可以作为参数传递给其它函数&#xff0c;不同于函数的是&#xff0c;它允许捕获调用者作用域中的值。Rust 闭包在形式上借鉴了 Smalltalk 和 Ruby 语言&#xff0c;与函数最…

无聊小知识.04 以下代码会输出什么?

1、前言 今天同事给我看了一段代码&#xff0c;然后这段简单的代码&#xff0c;我却陷入了沉思。 2、代码 String string ""; try {string "123";return string; } finally {string "234"; } 这段代码&#xff0c;string最终是“123”还是…

【SpringSecurity】学习笔记(一)

学习笔记一、SpringSecurity 简介二、创建测试项目2.1、引入依赖2.2、测试三、SpringSecurity基本原理3.1、过滤器链3.1.1、FilterSecurityInterceptor3.1.2、ExceptionTranslationFilter3.1.3、UsernamePasswordAuthenticationFilter3.2、过滤器加载过程3.3、两个重要的接口3.…

看完这篇,保证你学网络安全一帆风顺!

网络安全入门很难吗&#xff1f;总有人抱怨黑客入门太难了&#xff0c;然后仔细了解之后却发现&#xff0c;觉得难是因为看的视频教程都不是配套的&#xff0c;都是这里学一点、那里学一点&#xff0c;脑子里连基本的框架都没有。更过分的是&#xff0c;有的人学了好几个月&…

uniapp - 微信小程序端引入 Echarts 图表及使用详细教程,简单快速的解决方案(拒绝复杂的过程,附带详细的使用示例保姆级教程)

效果图 现在各种平台的文章都太乱了,基本上实测无效。。。 帮你在uniapp开发中,微信小程序平台端使用 echats 图表的详细教程,快速并且简单轻松搞定。 下面是最后的运行结果,随便用的一个图表进行展示,图表更多用法详见文档!

用正则去掉所有HTML标签,保留指定标签

保留单个&#xff0c;如保留img标签&#xff1a;str.replace(/]*>/ig,) 保留多个&#xff0c;如保留strong、em、p、u标签&#xff1a;str.replace(/]*>/ig,) 清除所有标签&#xff0c;就是去掉保留指定标签的部分了&#xff1a;str.replace(/]*>/g, ) replace 正则匹…

(学习日记)2023.4.18

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

java基础——迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类

迭代器&#xff0c;数据结构,List,Set ,TreeSet集合,Collections工具类 第一章 Iterator迭代器 1.1 Iterator接口 在程序开发中&#xff0c;经常需要遍历集合中的所有元素。针对这种需求&#xff0c;JDK专门提供了一个接口java.util.Iterator。 想要遍历Collection集合&…

【Leetcode】最小栈、栈的压入、弹出序列、逆波兰表达式求值

文章目录最小栈栈的压入、弹出序列逆波兰表达式求值最小栈 题目要求是在常数时间内检索到最小的元素的栈&#xff0c;思路是每当栈中存放一个更小的数据时&#xff0c;就将它入栈&#xff0c;相同的值也要入栈。 class MinStack { public:MinStack() {}//对自定义类型&#xf…