zoukankan      html  css  js  c++  java
  • 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("
    ");
                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("
    ");
                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
    ").append("Content-Type:text/html
    ").append("
    ")
                .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
    

  • 相关阅读:
    hadoop集群配置和测试
    ubuntu 12.04安装jdk
    springboot集成mybatis
    redis连接数据库
    生产随机数
    关于字符串统计次数
    闲的无聊写了一个房租的后台
    简单的死锁
    java集合类总结(转)
    mybatis框架的搭建
  • 原文地址:https://www.cnblogs.com/caoleiCoding/p/14827749.html
Copyright © 2011-2022 走看看