1. 1 不可撤销
  2. 2 小年兽 程嘉敏
  3. 3 手放开 李圣杰
  4. 4 迷人的危险3(翻自 dance flow) FAFA
  5. 5 山楂树之恋 程佳佳
  6. 6 summertime cinnamons / evening cinema
  7. 7 不谓侠(Cover 萧忆情Alex) CRITTY
  8. 8 神武醉相思(翻自 优我女团) 双笙
  9. 9 空山新雨后 音阙诗听 / 锦零
  10. 10 Wonderful U (Demo Version) AGA
  11. 11 广寒宫 丸子呦
  12. 12 陪我看日出 回音哥
  13. 13 春夏秋冬的你 王宇良
  14. 14 世界が终わるまでは… WANDS
  15. 15 多想在平庸的生活拥抱你 隔壁老樊
  16. 16 千禧 徐秉龙
  17. 17 我的一个道姑朋友 双笙
  18. 18 大鱼  (Cover 周深) 双笙
  19. 19 霜雪千年(Cover 洛天依 / 乐正绫) 双笙 / 封茗囧菌
  20. 20 云烟成雨(翻自 房东的猫) 周玥
  21. 21 情深深雨濛濛 杨胖雨
  22. 22 Five Hundred Miles Justin Timberlake / Carey Mulligan / Stark Sands
  23. 23 斑马斑马 房东的猫
  24. 24 See You Again Wiz Khalifa / Charlie Puth
  25. 25 Faded Alan Walker / Iselin Solheim
  26. 26 Natural J.Fla
  27. 27 New Soul Vox Angeli
  28. 28 ハレハレヤ(朗朗晴天)(翻自 v flower) 猫瑾
  29. 29 像鱼 王贰浪
  30. 30 Bye Bye Bye Lovestoned
  31. 31 Blame You 眠 / Lopu$
  32. 32 Believer J.Fla
  33. 33 书信 戴羽彤
  34. 34 柴 鱼 の c a l l i n g【已售】 幸子小姐拜托了
  35. 35 夜空中最亮的星(翻自 逃跑计划) 戴羽彤
  36. 36 慢慢喜欢你 LIve版(翻自 莫文蔚) 戴羽彤
  37. 37 病变(翻自 cubi) 戴羽彤
  38. 38 那女孩对我说 (完整版) Uu
  39. 39 绿色 陈雪凝
  40. 40 月牙湾 LIve版(翻自 F.I.R.) 戴羽彤
夜空中最亮的星(翻自 逃跑计划) - 戴羽彤
00:00 / 04:10

夜空中最亮的星 能否听清

那仰望的人 心底的孤独和叹息

夜空中最亮的星 能否记起

那曾与我同行 消失在风里的身影

我祈祷拥有一颗透明的心灵

和会流泪的眼睛

给我再去相信的勇气

越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请指引我靠近你

夜空中最亮的星 是否知道

那曾与我同行的身影 如今在哪里

夜空中最亮的星 是否在意

是等太阳先升起 还是意外先来临

我宁愿所有痛苦都留在心底

也不愿忘记你的眼睛

哦 给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行 哒~

我祈祷拥有一颗透明的心灵

和会流泪的眼睛 哦

给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行

2021.05.29 手写服务器问题搞定了……后面继续搞事情

问题搞定了

昨天的问题,已经找到问题了,是因为请求参数里有空字符串的情况,就是这些空字符串,导致inputStream被阻塞,所以最终无法响应给浏览器端。

昨天的doService方法是这样写的:

public void doService(SyskeRequest request, SyskeResponse response) throws Exception {
        try {
            byte[] bytes = new byte[1024];
            int read;
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            while ((read = request.getInputStream().read(bytes)) != -1) {
                byteArrayOutputStream.write(bytes);
            }
            byte[] toByteArray = byteArrayOutputStream.toByteArray();
            String requestStr = new String(toByteArray);
            System.out.println(String.format("请求参数:%s", requestStr));
            String[] split = requestStr.split("\r\n");
            System.out.println("end");
            response.write("hello syskeCat");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

简单调整下,就可以了

public static void doService(SyskeRequest request, SyskeResponse response) {
        try {
            String readLine;
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(request.getInputStream()));
            while ((readLine = bufferedReader.readLine()) != null) {
                if(readLine.length() == 0){
                    break;
                }
                byteArrayOutputStream.write(bytes);
            }
            byte[] toByteArray = byteArrayOutputStream.toByteArray();
            String requestStr = new String(toByteArray);
            System.out.println(String.format("请求参数:%s", requestStr));
            String[] split = requestStr.split("\r\n");
            System.out.println("end");
            response.write("hello syskeCat");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

我把不一样的地方单独提出来:

这是原来的写法:

while ((read = request.getInputStream().read(bytes)) != -1) {
                byteArrayOutputStream.write(bytes);
            }

修改之后:

 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(request.getInputStream()));
            while ((readLine = bufferedReader.readLine()) != null) {
                if(readLine.length() == 0){
                    break;
                }
            }

其实,写这段代码就是为了拿到请求头和请求参数:

{Cookie= NMTID=00O3GJZgJXpkp8vI0cpmNRliUnGvYkAAAF4sPsi2w, Accept= text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9, Connection= keep-alive, User-Agent= Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36, Sec-Fetch-Site= cross-site, Sec-Fetch-Dest= document, Host= localhost:8080, Accept-Encoding= gzip, deflate, br, Sec-Fetch-Mode= navigate, sec-ch-ua= " Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90", sec-ch-ua-mobile= ?0, Cache-Control= max-age=0, Upgrade-Insecure-Requests= 1, Sec-Fetch-User= ?1, Accept-Language= zh-CN,zh;q=0.9}

直接删除掉while循环也行,但是也就拿不到完整的请求信息了。而且经过我的实验,如果把空字符判断那块拿掉,就又会出现昨天的情况了。

项目已建好

昨天立的flag,今天已经把仓库建好了,写了一些简单的代码,以后就是慢慢添砖添瓦了。我们先来看看,现在是什么进展。

项目结构

创建了一个maven项目,目前只实现了简单的RequestResponse,可以实现简单的请求,但是还不能根据不同的请求返回对应的结果,这是下一步的工作。

关于这个项目,初步的计划是,后面会逐步实现Springboot的一些简单功能,最后希望能通过这个项目,能够更深入地了解springboot,了解web服务器。目前是基于传统的socket实现的,后面想以NIO的方式再写一遍,大概率会用Netty

服务器入口

这里启用了一个线程池来处理客户端的请求

public class SyskeBootServerApplication {
    private static final Logger logger = LoggerFactory.getLogger(SyskeBootServerApplication.class);
    private static final int SERVER_PORT = 8080;
    private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

    public static void main(String[] args) {
        start();
    }

    /**
     * 启动服务器
     * @throws Exception
     */
    public static void start() {
        try (ServerSocket serverSocket = new ServerSocket(SERVER_PORT) ) {
            Socket accept = null;
            logger.info("SyskeCatServer is starting……, port: {}", SERVER_PORT);
            while ((accept = serverSocket.accept()) != null){
                threadPoolExecutor.execute(new SyskeRequestHandler(accept));
            }
        } catch (Exception e) {
            logger.error("服务器后端异常");
        }
    }
}

请求处理线程

这里的核心在run方法,也就是在这里处理客户端的请求,后期要着重实现doDispatcher()方法,实现请求的分发和响应。在SyskeRequestHandler被实例化的时候,会根据客户端socket构建requstresponse

public class SyskeRequestHandler implements Runnable {
    private final Logger logger = LoggerFactory.getLogger(SyskeRequestHandler.class);
    private Socket socket;
    private SyskeRequest syskeRequest;
    private SyskeResponse syskeResponse;

    public SyskeRequestHandler(Socket socket) throws IOException{
        this.socket = socket;
        init();
    }

    private void init() throws IOException{
        this.syskeRequest = new SyskeRequest(socket.getInputStream());
        this.syskeResponse = new SyskeResponse(socket.getOutputStream());
    }

    @Override
    public void run() {
        try {
           doDispatcher();
        } catch (Exception e) {
            logger.error("系统错误:", e);
        }
    }

    /**
     * 请求分发处理
     * @throws Exception
     */
    public void doDispatcher() throws Exception{
        logger.info("请求头信息:{}", syskeRequest.getHeader());
        logger.info("请求信息:{}", syskeRequest.getRequestAttributeMap());
        syskeResponse.write(String.format("hello syskeCat, dateTime:%d", System.currentTimeMillis()));
        socket.close();
    }
}

请求封装

除了获取inputStream,还有获取请求参数和请求头的封装。后面还要继续完善请求头这一块,实现获取请求地址和具体参数的需求。

public class SyskeRequest implements Request {
    /**
     * 输入流
     */
    private InputStream inputStream;
    /**
     * 请求参数
     */
    private Map<String, String> requestAttributeMap;

    private String header;

    public SyskeRequest(InputStream inputStream) throws IOException{
        this.inputStream = inputStream;
        initRequest();
    }

    @Override
    public InputStream getInputStream() {
        return inputStream;
    }

    @Override
    public Map<String, String> getRequestAttributeMap() throws IOException{
        if (requestAttributeMap != null) {
            return requestAttributeMap;
        }
        initRequest();
        return requestAttributeMap;
    }

    /**
     * 初始化请求
     *
     * @throws IOException
     */
    private void initRequest() throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        header = bufferedReader.readLine();
        Map<String, String> attributeMap = Maps.newHashMap();
        String readLine = null;
        while ((readLine = bufferedReader.readLine()) != null) {
            if(readLine.length()==0) {
                break;
            }
            if (readLine.contains(":")) {
                String[] split = readLine.split(":", 2);
                attributeMap.put(split[0], split[1]);
            }
        }
        requestAttributeMap = attributeMap;
    }

    public String getHeader() {
        return header;
    }
}

响应封装

现在的响应信息太单一了,不论前端发送啥请求,后端始终响应的是html,这块也有再优化。下一步就是要实现根据请求头,响应对应的数据信息。

public class SyskeResponse implements Response {
    private OutputStream outputStream;

    public SyskeResponse(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    @Override
    public OutputStream getOutputStream() {
        return outputStream;
    }

    //将文本转换为字节流
    public void write(String content) throws IOException {
        StringBuffer httpResponse = new StringBuffer();
        // 按照HTTP响应报文的格式写入
        httpResponse.append("HTTP/1.1 200 OK\n").append("Content-Type:text/html\n").append("\r\n")
            .append("<html><head><link rel=\"icon\" href=\"data:;base64,=\"></head><body>").append(content)
            .append("</body></html>");
        // 将文本转为字节流
        outputStream.write(httpResponse.toString().getBytes());
        outputStream.flush();
        outputStream.close();
    }
}

总结

flag立好了,项目也建起来,后面就是实现自己的想法了。说实话,我现在还是挺期待的,感觉这是个很有意思的事情。可能看文章的小伙伴不一定能get到任何点,但对我来说真的是颠覆性的,昨天我也说了,今天依然有这样的感觉。以前,顶多是写写前端页面,写写后端接口,但是这个请求如何从前端到后端的,就是特别谜,也从来没想明白,但是昨天一下就醍醐灌顶了,感觉这一道大门终于被打通了,这感觉有一种发自心底的愉悦和兴奋,我甚至有种想马上把其他功能需求都实现的冲动。

下面是项目的开源仓库,有兴趣的小伙伴可以去看看,如果有想法的小伙伴,我真心推荐你自己动个手,自己写一下,真的感觉不错:

https://github.com/Syske/syske-boot

posted @ 2021-05-30 13:11  云中志  阅读(57)  评论(0编辑  收藏  举报