实现一个最简易的静态Web资源服务器

1.基本元素与变量:

  • 服务器,监听着某一端口号;
  • 监听客户端的请求---------HTTP请求
  • 识别出客户端的请求资源信息
  • 根据资源存在与否做出对应的响应(如果请求的资源存在,那么应当将文件的内容返回给客户端,如果文件不存在,那么应当返回一个404)------HTTP响应
  • 将客户端发送过来的网络路径,解析转换成本地的硬盘路径
  • 在硬盘上面去找某个文件,应该使用哪一套API?
    File、FileInputStream、FileOutputStream
    在这里插入图片描述

2.代码与实现:

Request.java(发送请求)

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

public  class Request{
    
    
    private Socket client;
    private String requestString;
    private String method;
    private String requestURI;
    private String protocol;
    //成员变量里面用Map
    private Map<String,String> requestHeaders;

    public Request(Socket client) {
    
    
        this.client = client;
        this.requestHeaders = new HashMap<>();
        parseRequest();
        if (!StringUtils.isEmpty(requestString)){
    
    
            //解析HTTP请求报文
            parseRequestLine();
            parseRequestHeaders();
        }
    }

private void parseRequestHeaders(){
    
    
        //找到请求行,即请求头开始
        int begin=requestString.indexOf("\r\n");
        // 找到请求头结束位置
    int end=requestString.indexOf("\r\n\r\n");
    //begin + 2,排除\r与\n
    String substring = requestString.substring(begin + 2, end);
    //拿到请求头的每一行
    String[] parts = substring.split("\r\n");

    for (String part:parts){
    
    
        int i=part.indexOf(":");
        String key=part.substring(0,i);
        String value=part.substring(i+1);
        //将请求头每行以键值对的形式存储。trim()去掉两端空格
        this.requestHeaders.put(key.trim(),value.trim());
    }
}

    //先解析HTTP请求报文的请求行 每一行结束都有一个\r\n
    //空行这块本身也是一个\r\n
    //利用\r\n可以把请求行分割出来
    //利用\r\n和\r\n(最后一个请求行)\r\n(空行)可以把请求头部分割出来
    private void parseRequestLine(){
    
    
        //找到\r\n第一次出现的位置
        int i=requestString.indexOf("\r\n");
        //分割出请求行requestLine
        String requestLine=requestString.substring(0,i);
       //利用空格来分割出请求行的三元素method、requestURI、protocol
        String[] parts = requestLine.split(" ");
        this.method=parts[0];
        this.requestURI=parts[1];
        this.protocol=parts[2];
        //还需要考虑到一点,如果在地址栏里面附带了请求参数呢?
        int i1 = requestURI.indexOf("?");
        if (i1!=-1){
    
    
            this.requestURI=requestURI.substring(0,i1);
        }
    }


    //处理请求信息
    private void parseRequest(){
    
    
        InputStream inputStream=null;
        try {
    
    
            //建立IO流中的输入流通道inputStream(目的是:从服务器中读取数据)
            inputStream=client.getInputStream();
            byte[] bytes = new byte[1024];

            int read = inputStream.read(bytes);
            if(read != -1){
    
    
                this.requestString = new String(bytes, 0, read);
                System.out.println(requestString);


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



}
    public String getMethod() {
    
    
        return method;
    }

    public String getRequestURI() {
    
    
        return requestURI;
    }

    public String getProtocol() {
    
    
        return protocol;
    }

    public String getHeader(String headerName){
    
    
        return requestHeaders.get(headerName);
    }
}

Response.java(响应)

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class Response {
    
    
    private Socket client;
    private String requestURI;

    public Response(Socket client, String requestURI){
    
    
        this.requestURI=requestURI;
        this.client=client;
        handleResponse();
    }

    private void handleResponse() {
    
    
        try {
    
    

            OutputStream outputStream = client.getOutputStream();
            //这一步的目的是为了将请求资源前面的/去掉,如果由/,那么file找不到的
            File file = new File(requestURI.substring(1));
            StringBuffer buffer = new StringBuffer();
            if (file.exists() && file.isFile()) {
    
    
                //如何拿到文件的输入流
                FileInputStream fileInputStream = new FileInputStream(file);

                byte[] bytes = new byte[1024];
                int length = 0;
                buffer.append("HTTP/1.1 200 OK\r\n");
                buffer.append("Content-Type:text/html;charset=utf-8\r\n");
                buffer.append("\r\n");
                outputStream.write(buffer.toString().getBytes("utf-8"));
                while ((length = fileInputStream.read(bytes)) != -1) {
    
    
                    outputStream.write(bytes, 0, length);
                }

            } else {
    
    
                buffer.append("HTTP/1.1 404 Not Found\r\n");
                buffer.append("Content-Type:text/html;charset=utf-8\r\n");
                buffer.append("\r\n");
                buffer.append("<div style='color:red'>File Not Found</div>");
                outputStream.write(buffer.toString().getBytes("utf-8"));
            }
            outputStream.flush();
            outputStream.close();

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

    }

}

WebServer.java(服务器端)

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class WebServer {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            //创建端口号为8090的服务器对象serverSocket
            ServerSocket serverSocket = new ServerSocket(8090);
            //继续监听8090端口号
            while (true) {
    
    
                //里面的client其实就是连接过来的每一个客户端
                //这一步是阻塞的,若没有客户端连接过来,会一直阻塞无法执行
                Socket client = serverSocket.accept();
                //为防止客户端连接上服务器但没传输数据阻塞,影响其它客户端连接,故通过线程来解决。
                //可实现多客户端同时连接

                new Thread(new Runnable() {
    
    
                    @Override
                    public void run() {
    
    
                        //就是对请求信息的封装,客户端发送过来的信息都在这里面
                        try {
    
    
                            //对于响应信息的封装,只需要往这里面写入数据,最终就可以把数据返回给客户端
                            //OutputStream outputStream = client.getOutputStream();
                            
                            //如何把inputStream里面的内容解析成为一个字符串呢?
                            //根据面向对象的思想,将请求响应设置为两个对象 request、response
                            Request request = new Request(client);
                            
                            //请求的资源,请求意图  /1.html  google----oracle  android
                            //看这个文件是否存在,如果存在,则返回给客户端;如果不存在,则返回404 HTTP响应状态码
                            String requestURI = request.getRequestURI();
                            
                            Response response = new Response(client, requestURI);

                        } catch (Exception e) {
    
    
                            e.printStackTrace();
                        }
                    }
                }).start();

            }

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

}

StringUtils.java(工具类)

//工具类:这些代码在很多地方可能需要频繁使用到
public class StringUtils {
    
    

    public static boolean isEmpty(String content){
    
    
        if(content == null || content.isEmpty()){
    
    
            return true;
        }
        return false;
    }
}

test.html(服务器内文件,被访问)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <form action="http://localhost:8090/test2.html" method="post">
        <input type="text" name="username">账号<br>
        <input type="password" name="pasword">密码
        <input type="submit">
    </form>
</div>
</body>
</html>

test2.html(服务器内文件,被访问)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
   test2
</div>
</body>
</html>

3.结果输出:

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

4.请求报文:

在这里插入图片描述

5.响应报文:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/gy99csdn/article/details/114022613