欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

自己实现简易版本的Http服务器

程序员文章站 2022-05-04 18:21:08
...

1.模块设计:

Http服务器需要解析来自浏览器端的数据,因此我们需要设计两个类。
(1)Request类,负责保存浏览器端的信息;
(2) HttpServer 类,负责处理浏览器端的信息

2.详细设计

主要解析三部分的请求数据:
(1)请求行
(2)请求报头
(3)请求正文
其中请求行包括三部分:请求方法,url,版本号。
请求报头可以解析到哈希表中
请求正文也需要解析到哈希表中

这里我们设置端口号是9999;
服务器端的 url 设置了五种,分别为:
(1)login.html:是一个登录页面。用户需要输入账号和密码。
(2)login:用户登录成功后跳转的页面。登录成功后,服务器端会保存用户信息,并且生成一个 sessionId,并把该 sessionId 返回给浏览器端,此后浏览器再访问服务器都会携带该 sessionId
(3)302:此处我们重定向到 www.baidu.com
(4)getCookie:浏览器端向服务器端请求时会携带之前的sessionId,此时服务器会检验此 sessionId 有没有与之对应的用户名,如果存在,那么此用户就可以进入此网站;否则不可以进入
(5)setCookie:可以根据原来的 sessionId 找到对应的用户,并且为该用户添加一个新的 sessionId

参考代码如下:
** HttpServer 类:解析客户端信息并响应相关操作**

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class HttpServer {

    // 端口号
    private static final int PORT = 9999;

    // 获取处理器核数
    private static final int COUNT = Runtime.getRuntime().availableProcessors();

    // 处理的任务量和线程数量、CPU、内存等资源都相关
    // 一般推荐使用跟处理器核数数量相等的线程数
    private static final ExecutorService EXE =
            Executors.newFixedThreadPool(COUNT);

    public static Map<String, String> SESSION = new HashMap<>();

    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(PORT);
        // 一个 socket 对象代表一个客户端
        while (true) {
            // 获取客户端请求 socket 对象:阻塞方法
            Socket socket = server.accept();
            EXE.submit(new HttpTask(socket));
        }
    }
}

// Http请求任务处理类

class HttpTask implements Runnable {

    private Socket socket;

    // 构造方法
    public HttpTask(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        // 获取客户端请求数据:输入流
        InputStream is = null;
        InputStreamReader isr = null;
        BufferedReader br = null;

        // 获取客户端输出流,返回响应数据
        OutputStream os = null;
        PrintWriter pw = null;

        try {
            try {
                is = socket.getInputStream();
                // 响应输出数据
                // 将原生的字节流转换成字符流,方便处理
                isr = new InputStreamReader(is, StandardCharsets.UTF_8);
                // 转换流放在缓冲流中读取效率比较高
                br = new BufferedReader(isr);
                Request request = new Request();
                // 请求数据的解析:http协议报的解包
                // 1.解析请求行(第一行): Method URL Version
                String requestLine = br.readLine();
                // 根据 " " 把请求行分成三部分
                String[] requestLines = requestLine.split(" ");
                // 添加 method
                request.setMethod(requestLines[0]);
                // 可能为 http://localhost:9999/xxx?uername=stu&passord=123
                String url = requestLines[1];
                // 当 method 为 get 时可能需要解析请求正文
                if (url.contains("?")) {
                    String parameters = url.substring(url.indexOf("?") + 1);
                    request.parseParamaters(parameters);
                    url = url.substring(0, url.indexOf("?"));
                }
                // 添加 url
                request.setUrl(url);
                // 添加 version
                request.setVersion(requestLines[2]);
                // 2.解析请求头
                // key : value 每个换行,以空行作为结尾
                String header;
                while ((header = br.readLine()) != null && header.length() != 0) {
                    // 第一个条件保证请求头还没有结束,
                    // 第二个条件保证读取到的元素不是空行
                   String key = header.substring(0, header.indexOf(":"));
                   String value = header.substring(header.indexOf(":") + 1);
                   request.addHeader(key, value.trim());
                }
                // POST请求,需要根据请求头 Content-Length 获取请求体的长度
                if ("POST".equals(request.getMethod())) {
                    String len = request.getHeader("Content-Length");

                    if (len != null) {
                        // 将 String 类型转换为 int 类型
                        int l = Integer.parseInt(len);
                        char[] chars = new char[l];
                        br.read(chars, 0, l);
                        // 请求参数格式:username=stu&password=123
                        String s = new String(chars);
                        //String requestBody = Arrays.toString(chars);
                        //System.out.println(requestBody + "hehe");
                        //request.parseParamaters(requestBody);
                        request.parseParamaters(s);
                        //String[] ps = s.split("&");
//                        for (String p : ps) {
//                            String[] array = p.split("=");
//                            request.addParameter(array[0], array[1]);
//                            System.out.println(array[0] + "### " + array[1]);
//                        }
                        //System.out.println("req body==="+requestBody);
                    }
                }
                System.out.println(request);


                os = socket.getOutputStream();
                pw = new PrintWriter(os, true);
                // http://localhost:9999/302/111
                if ("/302".equals(request.getUrl())) {
                    pw.println("HTTP/1.1 302 重定向");
                    pw.println("Content-Type: text/html;charset=utf-8");
                    pw.println("Location: https://www.baidu.com");
                } else if ("/login".equals(request.getUrl())) {
                    pw.println("HTTP/1.1 200 OK");
                    String username = request.getParameter("username");


                    String sessionId = UUID.randomUUID().toString();
                    // 在服务器保存生成的 sessionId 和 username 对应的哈希表
                    HttpServer.SESSION.put(sessionId, username);
                    pw.println("Set-Cookie: SESSIONID=" + sessionId);
                    pw.println("Content-Type: text/html; charset=utf-8");
                    String content= "<h2>欢迎用户[" + request.getParameter("username") + "]登录</h2>";
                    pw.println("Content-Length: "+content.getBytes().length);
                    pw.println();
                    pw.println(content);

                } else if ("/setCookie".equals(request.getUrl())) {
                    pw.println("HTTP/1.1 200 OK");
                    String sessionId = UUID.randomUUID().toString();
                    // 根据原来的 Cookie 值找到对应的 value 即 username
                    String oldCookie = request.getHeader("Cookie");
                    String[] arr = oldCookie.split("=");
                    String old = arr[1];
                    String name = HttpServer.SESSION.get(old);
                    HttpServer.SESSION.put(sessionId, name);
                    pw.println("Set-Cookie: SESSIONID=" + sessionId);
                    pw.println("Content-Type: text/html; charset=utf-8");
                    String content = "<h2>sseion重置成功!</h2>";
                    pw.println("Content-Length: "+content.getBytes().length);
                    pw.println();
                    pw.println(content);
                } else if ("/getCookie".equals(request.getUrl())) {
                    String cookie = request.getHeader("Cookie");
                    String[] cookies = cookie.split(";");
                    for (String s : cookies) {
                        String[] s2 = s.split("=");
                        String key = s2[0].trim();
                        String value = s2[1].trim();
                        if (key.equals("SESSIONID") && HttpServer.SESSION.containsKey(value)) {
                            pw.println("HTTP/1.1 200 OK");
                            pw.println("Content-Type: text/html; charset=utf-8");
                            pw.println();
                            pw.println("<h2>用户" + HttpServer.SESSION.get(value) + "能够访问</h2>");
                            return;
                        }
                    }
                    pw.println("HTTP/1.1 403 Forbidden");
                    pw.println("Content-Type: text/html; charset=utf-8");
                    pw.println();
                    pw.println("没有访问权限");
                } else {
                    // 访问 /login.html,转化为访问./login.html
                    // 其中 ./ 表示当前目录下的文件
                    InputStream htmlIs = HttpServer.class.getClassLoader()
                            .getResourceAsStream("." + request.getUrl());
                    if (htmlIs != null) {
                        pw.println("HTTP/1.1 200 OK");
                        pw.println("Content-Type: text/html; charset=utf-8");
                        pw.println();

                        // 返回 webapp 下的静态资源文件内容
                        InputStreamReader htmlIsr = new InputStreamReader(htmlIs);
                        BufferedReader htmlBr = new BufferedReader(htmlIsr);
                        String content;
                        while ((content = htmlBr.readLine()) != null) {
                            pw.println(content);
                        }
                    } else {
                        // 返回 404
                        pw.println("HTTP/1.1 404 Not Found");
                        pw.println("Content-Type: text/html; charset=utf-8");
                        pw.println();
                        pw.println("<h2>找不到资源</h2>");
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            finally {
                // 关闭流:反向关闭
                if (br != null) {
                    br.close();
                }
                if (isr != null) {
                    isr.close();
                }
                if (is != null) {
                    is.close();
                }
                if (pw != null) {
                    pw.close();
                }
                if (os != null) {
                    os.close();
                }
                // 关闭客户端连接(短连接)
                socket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

用户登录界面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户登录</title>
</head>
<body>
    <h3>登录页面</h3>
    <form method="post" action="login">
        用户名:<input type="text" name="username" />
        <br> <!-- br代表换行符 -->
        密码:<input type="password" name="password"/>
        <input type="submit" value="登录" />
    </form>
</body>
</html>

Request 类:保存,获取用户相关信息

import java.util.HashMap;
import java.util.Map;

public class Request {
    // 请求方法
    private String method;
    // 请求路径
    private String url;
    // http版本号
    private String version;
    // 请求头
    private Map<String, String> heads = new HashMap<>();
    // 请求参数
    private Map<String, String> parameters = new HashMap<>();

    // 添加请求头
    public void addHeader(String key, String value) {
        heads.put(key, value);
    }
    // 获取某个请求头
    public String getHeader(String key) {
        return heads.get(key);
    }

    // 解析请求参数:key1=value1&key2=value2
    public void parseParamaters(String parameters) {
        String[] ps = parameters.split("&");
        for (String p : ps) {
            String[] array = p.split("=");
            addParameter(array[0], array[1]);
        }
//        addParameter(ps[0],ps[1]);
    }

    // 添加请求参数
    public void addParameter(String key, String value) {
        parameters.put(key, value);
    }
    // 获取请求参数
    public String getParameter(String key) {
        return parameters.get(key);
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public Map<String, String> getHeads() {
        return heads;
    }

    public void setHeads(Map<String, String> heads) {
        this.heads = heads;
    }

    public Map<String, String> getParameters() {
        return parameters;
    }

    public void setParameters(Map<String, String> parameters) {
        this.parameters = parameters;
    }

    @Override
    public String toString() {
        return "Request{" +
                "\n method='" + method + '\'' +
                ", \n url='" + url + '\'' +
                ", \n version='" + version + '\'' +
                ", \n heads=" + heads +
                ", \n parameters=" + parameters +
                '}';
    }
}

结果展示

(1)login.html :用户登录
自己实现简易版本的Http服务器
(2)login:用户进入
自己实现简易版本的Http服务器
(3)302:重定向
自己实现简易版本的Http服务器

(4)getCookie:用户是否有权限进入
自己实现简易版本的Http服务器
自己实现简易版本的Http服务器
(5)setCookie:给用户添加一个新的 sessionId
自己实现简易版本的Http服务器