Springboot简码V6-V10

92 阅读9分钟

V6部分

  • 2.处理请求:获取加载路径,定位目录。获取解析请求中的uri,在目录下找是否存在文件,存在则发送状态代码statusCode:200,状态描述statusReason:OK,若没有则发送404,NotFound,并设置file为404.html文件。
  • 3.1发送响应行:println("HTTP/1.1" + " " + statusCode + " " + statusReason);
  • 3.2发送响应头:先设置死,文件长度为file.length()获取文件大小。
  • 3.3发送响应正文:读取file文件,用获取的socket输出流输出。
package com.webserver.core;
import com.webserver.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.Locale;
import java.util.Map;

/**
 * 该线程任务负责与指定的客户端进行HTTP交互
 * 按照HTTP协议的交互规则,与浏览器采取一问一答。
 * 规划三部:
 * 1:解析请求(生成HttpServletRequest对象)
 * 2:处理请求(交给SpringMVC框架处理)
 * 3:发送响应(将HttpServletResponse内容发送给浏览器)
 * 断开TCP连接
 *
 */
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处理请求
            File root = new File(
                ClientHandler.class.getClassLoader().getResource(".").toURI()
            );
            File staticDir = new File(root,"static");
            /*
                http://localhost:8088/index.html
                http://localhost:8088/classtable.html

                http://localhost:8088/index123.html     //static下没有index123.html
                http://localhost:8088                   //抽象路径:"/".这时File file = new File(staticDir,path)行会定位到static目录本身
                上述两种情况都会导致发送响应正文时使用文件输入流读取时报错
                1:不存在的文件会出现系统找不到指定的文件
                2:读取的是目录会导致出现无法访问
             */
            String path = request.getUri();
            File file = new File(staticDir,path);

            int statusCode;//状态代码
            String statusReason;//状态描述
            if(file.isFile()){//判断请求的文件真实存在且确定是一个文件(不是目录)
                statusCode = 200;
                statusReason = "OK";
            }else{//404情况
                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发送响应正文(file表示的文件内容)
            OutputStream out = socket.getOutputStream();
            FileInputStream fis = new FileInputStream(file);
            int len;
            byte[] buf = new byte[1024*10];
            while((len = fis.read(buf))!=-1){
                out.write(buf,0,len);
            }


        } catch (IOException e) {
            e.printStackTrace();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        } finally {
            try {
                //一问一答后断开TCP连接
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    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);//发送换行符
    }

}

v6部分结束


v7部分

  • 将ClientHandler中的发送响应部分移动到HttpServletResponse类中,封装在方法里,三个步骤方法封装在response()中一起调用。专门负责发送响应。
  • ClientHandler类中将HttpServletResponse类实例化出来,最后调用response()。
package com.webserver.core;

import com.webserver.http.HttpServletRequest;
import com.webserver.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.Locale;
import java.util.Map;

/**
 * 该线程任务负责与指定的客户端进行HTTP交互
 * 按照HTTP协议的交互规则,与浏览器采取一问一答。
 * 规划三部:
 * 1:解析请求(生成HttpServletRequest对象)
 * 2:处理请求(交给SpringMVC框架处理)
 * 3:发送响应(将HttpServletResponse内容发送给浏览器)
 * 断开TCP连接
 *
 */
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处理请求
            File root = new File(
                ClientHandler.class.getClassLoader().getResource(".").toURI()
            );
            File staticDir = new File(root,"static");

            String path = request.getUri();
            File file = new File(staticDir,path);

            if(file.isFile()){//判断请求的文件真实存在且确定是一个文件(不是目录)
                response.setContentFile(file);

            }else{//404情况
                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();
        } finally {
            try {
                //一问一答后断开TCP连接
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
package com.webserver.http;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * 响应对象
 * 该类的每一个实例用于表示一个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 {
        OutputStream out = socket.getOutputStream();
        FileInputStream fis = new FileInputStream(contentFile);
        int len;
        byte[] buf = new byte[1024*10];
        while((len = fis.read(buf))!=-1){
            out.write(buf,0,len);
        }
    }


    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;
    }
}

v7部分


V8部分

  • 将处理请求部分,装进DispatcherServlet类中,并设置单例模式,全局只能new一个对象,把处理细节封装在service()中,将request和response两个类当作参数传进去,用于交互数据。并在ClientHandler中实例化出来调用service方法。
package com.webserver.core;

import com.webserver.http.HttpServletRequest;
import com.webserver.http.HttpServletResponse;

import java.io.*;
import java.net.Socket;

/**
 * 该线程任务负责与指定的客户端进行HTTP交互
 * 按照HTTP协议的交互规则,与浏览器采取一问一答。
 * 规划三部:
 * 1:解析请求(生成HttpServletRequest对象)
 * 2:处理请求(交给SpringMVC框架处理)
 * 3:发送响应(将HttpServletResponse内容发送给浏览器)
 * 断开TCP连接
 *
 */
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处理请求
            DispatcherServlet servlet = DispatcherServlet.getInstance();
            servlet.service(request,response);

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


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //一问一答后断开TCP连接
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
package com.webserver.core;

import com.webserver.http.HttpServletRequest;
import com.webserver.http.HttpServletResponse;

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

/**
 * DispatcherServlet实际是由SpringMVC框架提供的一个类,用于和Tomcat整合并负责
 * 接手处理请求的工作。
 *
 * Servlet是JAVA EE里的一个接口,译作:运行在服务端的小程序
 * Servlet中有一个重要的抽象方法:
 * public void service(HttpServletRequest request,HttpServletResponse response)
 * 该方法用于处理某个服务
 *
 * SpringMVC框架提供的DispatcherServlet就实现了该接口并重写了service方法,那么与Tomcat整合后,Tomcat在处理
 * 请求的环节就可以调用DispatcherServlet的service方法将请求对象与响应对象传递进去由SpringMVC框架完成处理请求
 * 的操作。
 */
public class DispatcherServlet {
    private static DispatcherServlet instance = new DispatcherServlet();
    private static File root;
    private static File staticDir;
    static {
        try {
            root = new File(
                DispatcherServlet.class.getClassLoader().getResource(".").toURI()
            );
            staticDir = new File(root,"static");
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    private DispatcherServlet(){}
    public static DispatcherServlet getInstance(){
        return instance;
    }

    public void service(HttpServletRequest request, HttpServletResponse response){
        String path = request.getUri();
        File file = new File(staticDir,path);

        if(file.isFile()){//判断请求的文件真实存在且确定是一个文件(不是目录)
            response.setContentFile(file);

        }else{//404情况
            response.setStatusCode(404);
            response.setStatusReason("NotFound");
            file = new File(staticDir,"404.html");
            response.setContentFile(file);
        }
    }
}

v8部分结束


v9部分

HTTP协议注明:为了保证服务端的健壮性,应当忽略客户端空的请求。
浏览器有时会发送空请求,即:与服务端链接后没有发送标准的HTTP请求内容,直接与服务端断开链接。 此时服务端按照一问一答的处理流程在解析请求时请求行由于没有内容,在拆分后获取信息会出现数组 下标越界。
解决:当解析请求行时发现没有内容就对外抛出空请求异常(自定义的一个异常),并最终抛出给 ClientHandler,使其忽略后续的处理请求与发送响应的工作,直接与客户端断开来忽略本次操作。

创建一个异常类EmptyRequestException

package com.webserver.http;

public class EmptyRequestException extends Exception{
    public EmptyRequestException(){

    }

    public EmptyRequestException(String message) {
        super(message);
    }

    public EmptyRequestException(String message, Throwable cause) {
        super(message, cause);
    }

    public EmptyRequestException(Throwable cause) {
        super(cause);
    }

    public EmptyRequestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

HttpservletRequest

public HttpServletRequest(Socket socket) throws IOException, EmptyRequestException {
    this.socket = socket;
    //1.1:读取请求行
    parseRequestLine();
    //1.2解析消息头
    parseHeaders();
    //1.3解析消息正文
    parseContent();

}
//解析请求行
private void parseRequestLine() throws IOException, EmptyRequestException {
    String line = readLine();
    if(line.isEmpty()){//如果请求行没有读取到内容,则为空请求
        throw new EmptyRequestException();
    }
    System.out.println("请求行:" + line);
    String[] data = line.split("\s");
    method = data[0];
    uri = data[1];
    protocol = data[2];
}

ClientHandler

package com.webserver.http.core;

import com.webserver.http.EmptyRequestException;
import com.webserver.http.HttpServletRequest;
import com.webserver.http.HttpServletResponse;

import java.io.*;
import java.net.Socket;

/**
 * 该线程任务负责与指定的客户端进行HTTP交互
 * 按照HTTP协议的交互规则,与浏览器采取一问一答。
 * 规划三部:
 * 1:解析请求(生成HttpServletRequest对象)
 * 2:处理请求(交给SpringMVC框架处理)
 * 3:发送响应(将HttpServletResponse内容发送给浏览器)
 * 断开TCP连接
 *
 */
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处理请求
            DispatcherServlet servlet = DispatcherServlet.getInstance();
            servlet.service(request,response);

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


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

        } finally {
            try {
                //一问一答后断开TCP连接
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

v9结束


V10开始

  • HttpServletResponse中设置Map类型headers,设置方法addHeader将name和value传入headers中,在DispatcherServlet中调用addHeader方法,判断请求的文件添加响应头到headers中。在sendHeader方法中将header中的值循环遍历发送给客户端。
package com.webserver.http;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 响应对象
 * 该类的每一个实例用于表示一个HTTP的响应
 * 一个响应由三部分构成:
 * 状态行,响应头,响应正文
 *
 */
public class HttpServletResponse {
    private Socket socket;
    //状态行相关信息
    private int statusCode = 200;                   //状态代码
    private String statusReason = "OK";             //状态描述
    //响应头相关信息
    private Map<String,String> headers = new HashMap<>();
    //响应正文相关信息
    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 {
        Set<Map.Entry<String,String>> entrySet = headers.entrySet();
        for (Map.Entry<String,String> e:entrySet){
            String key =  e.getKey();
            String value = e.getValue();
            println(key+": "+value);
        }
        println("");
    }
    private void sendContent() throws IOException {
        OutputStream out = socket.getOutputStream();
        FileInputStream fis = new FileInputStream(contentFile);
        int len;
        byte[] buf = new byte[1024*10];
        while((len = fis.read(buf))!=-1){
            out.write(buf,0,len);
        }
    }


    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;
    }

    public void addHeader(String name,String value){
        headers.put(name,value);
    }
}
package com.webserver.core;

import com.webserver.http.HttpServletRequest;
import com.webserver.http.HttpServletResponse;

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

/**
 * DispatcherServlet实际是由SpringMVC框架提供的一个类,用于和Tomcat整合并负责
 * 接手处理请求的工作。
 *
 * Servlet是JAVA EE里的一个接口,译作:运行在服务端的小程序
 * Servlet中有一个重要的抽象方法:
 * public void service(HttpServletRequest request,HttpServletResponse response)
 * 该方法用于处理某个服务
 *
 * SpringMVC框架提供的DispatcherServlet就实现了该接口并重写了service方法,那么与Tomcat整合后,Tomcat在处理
 * 请求的环节就可以调用DispatcherServlet的service方法将请求对象与响应对象传递进去由SpringMVC框架完成处理请求
 * 的操作。
 */
public class DispatcherServlet {
    private static DispatcherServlet instance = new DispatcherServlet();
    private static File root;
    private static File staticDir;
    static {
        try {
            root = new File(
                DispatcherServlet.class.getClassLoader().getResource(".").toURI()
            );
            staticDir = new File(root,"static");
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    private DispatcherServlet(){}
    public static DispatcherServlet getInstance(){
        return instance;
    }

    public void service(HttpServletRequest request, HttpServletResponse response){
        String path = request.getUri();
        File file = new File(staticDir,path);

        if(file.isFile()){//判断请求的文件真实存在且确定是一个文件(不是目录)
            response.setContentFile(file);
            response.addHeader("Content-Type","text/html");
            response.addHeader("Content-Length",file.length()+"");
        }else{//404情况
            response.setStatusCode(404);
            response.setStatusReason("NotFound");
            file = new File(staticDir,"404.html");
            response.setContentFile(file);
            response.addHeader("Content-Type","text/html");
            response.addHeader("Content-Length",file.length()+"");
        }
        response.addHeader("Server","BirdServer");
    }
}

v10部分结束