Java实现能获取静态资源的简易版服务器(类Tomcat)

Java实现能获取静态资源的简易版服务器(类Tomcat)

我们平时使用Tomcat做为服务器进行Web开发,通常会输入http://localhost:8888/index.html这种URL,这种请求的Request Headers 如下:

GET /index.html HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

Request Headers 可以得到请求想要访问的静态资源是index.html(我们自己实现服务器,浏览器发起请求,服务器后台得到就是Request Headers这种数据 )。

现在我们知道如何获取请求想要的资源了(从Request Headers 获取),这就好办了。

当用户在浏览器中输入URL发起请求,其实就是与服务器先建立网络连接,再获取服务器上的资源。
这就和Java网络编程有关系了,现在说一说服务器主要的任务:

  • 服务器首先要打开ServerSocket,当有客户端(浏览器)进行连接,并且发起请求后,获得该请求的Request Headers (你可以把浏览器当作我们之前实现的多人聊天室里面的客户端,因为浏览器向服务器发起请求,数据也是通过流Stream或者类似的组件实现的)。
  • 服务器获得用户请求的Request Headers 后,然后从Request Headers 中分析出资源名称。
  • 知道用户想要的资源后,服务器将资源与一些必要信息打包发送给客户端(协议、状态码、资源等),当资源不存在时,服务器返回404.html资源。

Bootstrap类,启动服务器的模块。

import connector.Connector;

public final class Bootstrap {
    public static void main(String[] args) {
        Connector connector = new Connector();
        connector.start();
    }
}

Connector类,创建ServerSocket(用于客户端建立连接)、创建Request(从Request Headers 中获取客户端请求的资源)、创建Response(将协议、状态码、资源等数据打包写入流中)、创建StaticProcessor(借助Response将资源与一些必要信息发送给服务器),代码注释应该非常清楚。

package connector;

import processor.StaticProcessor;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Connector implements Runnable {

    private static final int DEFAULT_PORT = 8888;

    private ServerSocket server;
    private int port;

    public Connector(){
        this(DEFAULT_PORT);
    }

    public Connector(int port) {
        this.port = port;
    }

    public void start(){
        new Thread(this).start();
    }

    @Override
    public void run() {
        try {
            // 创建ServerSocket,绑定、监听端口
            server = new ServerSocket(port);
            System.out.println("启动服务器,监听端口:" + port);
            while(true){
                // 等待客户端连接
                Socket socket = server.accept();
                // 获取输入流
                InputStream input = socket.getInputStream();
                // 获取输出流
                OutputStream output = socket.getOutputStream();

                // 创建请求request,并且传入输入流(有客户端请求的信息)
                Request request = new Request(input);
                // request通过输入流的信息,分析出客户端想要的资源
                request.parse();

                // 创建响应response,并且传入输出流(方便将获取的资源发送给客户端)
                Response response = new Response(output);
                // response需要request的uri(客户端请求的资源)
                response.setRequest(request);
                
                // 创建处理者processor
                StaticProcessor processor = new StaticProcessor();
                // processor通过response把数据发送给客户端
                processor.process(response);

                //关闭socket
                close(socket);
            }
        } catch (IOException e) {
            // 浏览器可以识别状态码,当状态码表示请求不成功时(如404),似乎会断开socket,所以这里不进行处理
        } finally{
            close(server);
        }
    }

    private void close(Closeable closeable){
        if(closeable != null){
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Request类,从客户端与服务器建立连接的socket的输入流InputStream中读取信息,并且分析出客户端想要的资源。

package connector;

import java.io.IOException;
import java.io.InputStream;

public class Request {

    private static final int BUFFER_SIZE = 1024;

    private InputStream input;

    private String uri;

    public Request(InputStream input) {
        this.input = input;
    }

    public String getUri() {
        return uri;
    }

    public void parse(){
        int length = 0;
        byte[] buffer = new byte[BUFFER_SIZE];
        try {
            // 读取流里面的数据,并且记录长度
            length = input.read(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 将数据转化成StringBuilder
        StringBuilder request = new StringBuilder();
        for (int i = 0; i < length; i++) {
            request.append((char) buffer[i]);
        }

        // 分析出客户端想要的资源
        uri = parseUri(request.toString());
    }

    /**
     *从 “GET /index.html HTTP/1.1 
     *      ......
     *       ”中获取index.html
     * 通过空格来分离出来
     * */
    public String parseUri(String request){
        int index1 , index2;
        // 第一个空格的位置
        index1 = request.indexOf(' ');
        if(index1 != -1){
            // 第二个空格的位置
            index2 = request.indexOf(' ', index1+1);
            if(index2 != -1){
                // 分离出资源名称
                return request.substring(index1 + 2 , index2);
            }
        }
        // 没有办法解析出uri
        return "";
    }
}

Response类,将协议、状态码、资源等数据打包写入流中。

package connector;

import java.io.*;

public class Response {

    private static final int BUFFER_SIZE = 1024;
    Request request;
    OutputStream output;

    public Response(OutputStream output) {
        this.output = output;
    }

    public void setRequest(Request request) {
        this.request = request;
    }

    public void sendStaticResource() throws IOException {
        try{
            // 通过request的uri,获取资源的路径
            String filePath = getClass()
                    .getClassLoader()
                    .getResource(request.getUri()).getFile();
            // 创建资源文件
            File file = new File(filePath.substring(1 , filePath.length()));
            // 将资源写入流里面,HttpStatus.SC_OK是状态码
            write(file , HttpStatus.SC_OK);
        } catch (Exception e) {
            // 当出现错误时,简单处理 ,发送404.html给客户端
            String errorFilePath = getClass().getClassLoader().getResource("404.html").getFile();
            // 将资源写入流里面,HttpStatus.SC_NOT_FOUND是状态码
            write(new File(errorFilePath.substring(1 , errorFilePath.length())) ,
                    HttpStatus.SC_NOT_FOUND);

        }
    }

    private void write(File resource , HttpStatus status) throws IOException {
        
        try(FileInputStream fis = new FileInputStream(resource)){
            // 先将协议、状态码等必要信息写入流中,ConnectorUtils是工具类
            output.write(ConnectorUtils.renderStatus(status).getBytes());
            byte[] buffer = new byte[BUFFER_SIZE];
            int length = 0;
            // 把资源文件写入流中
            while((length = fis.read(buffer , 0 , BUFFER_SIZE)) != -1){
                output.write(buffer , 0 ,length);
            }
        }
    }
}

HttpStatus类,状态码枚举类。

package connector;

public enum HttpStatus {
    SC_OK(200 , "OK"),
    SC_NOT_FOUND(404 , "File Not Found");

    private int statusCode;
    private String reason;

    HttpStatus(int statusCode, String reason) {
        this.statusCode = statusCode;
        this.reason = reason;
    }

    public int getStatusCode() {
        return statusCode;
    }

    public String getReason() {
        return reason;
    }
}

ConnectorUtils类(工具类),将协议、状态码等信息组装成浏览器可以识别的信息。

package connector;

public class ConnectorUtils {

    public static final String PROTOCOL = "HTTP/1.1";

    public static final String CARRIAGE = "\r";

    public static final String NEWLINE = "\n";

    public static final String SPACE = " ";

    public static String renderStatus(HttpStatus status){
        StringBuilder sb = new StringBuilder(PROTOCOL)
                .append(SPACE)
                .append(status.getStatusCode())
                .append(SPACE)
                .append(status.getReason())
                .append(CARRIAGE).append(NEWLINE)
                .append(CARRIAGE).append(NEWLINE);
        return sb.toString();
    }
}

StaticProcessor类,做为静态资源处理者的身份,借助Response进行处理。

扫描二维码关注公众号,回复: 9053150 查看本文章
package processor;

import connector.Response;

import java.io.IOException;

public class StaticProcessor {

    public void process(Response response){
        try {
            response.sendStaticResource();
        } catch (IOException e) {
            // 不处理浏览器断开连接等错误
        }
    }
}

这里我们便完成了一个Java实现的能获取静态资源的简易版服务器。

项目结构
在这里插入图片描述
项目源码

Java实现获取静态资源的服务器

测试

请求成功,服务器有资源。
在这里插入图片描述
在这里插入图片描述

请求失败,服务器没有资源。

在这里插入图片描述

在这里插入图片描述

大家可以动手试一试,不用ServerSocket来实现,用NIO模型或者AIO模型的组件来实现。

如果有说错的地方,请大家不吝赐教(记得留言哦~~~~)。

发布了288 篇原创文章 · 获赞 325 · 访问量 65万+

猜你喜欢

转载自blog.csdn.net/qq_37960603/article/details/104216019