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

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 :用户登录
在这里插入图片描述
(2)login:用户进入
在这里插入图片描述
(3)302:重定向
在这里插入图片描述

(4)getCookie:用户是否有权限进入
在这里插入图片描述
在这里插入图片描述
(5)setCookie:给用户添加一个新的 sessionId
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值