乞丐版servlet容器第3篇

Posted 放着,让我来!!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了乞丐版servlet容器第3篇相关的知识,希望对你有一定的参考价值。

4 EventListener接口

让我们继续看SocketConnector中的acceptConnect方法:

@Override
protected void acceptConnect() throws ConnectorException {
    new Thread(() -> {
        while (true && started) {
            Socket socket = null;
            try {
                socket = serverSocket.accept();
                LOGGER.info("新增连接:" + socket.getInetAddress() + ":" + socket.getPort());
            } catch (IOException e) {
                //单个Socket异常,不要影响整个Connector
                LOGGER.error(e.getMessage(), e);
            } finally {
                IoUtils.closeQuietly(socket);
            }
        }
    }).start();
}

注意socket = serverSocket.accept(),这里获取到socket之后只是打印日志,并没获取socket的输入输出进行操作。

操作socket的输入和输出是否应该在SocketConnector中?
这时大师又说话了,Connector责任是啥,就是管理connect的啊,connect怎么使用,关它屁事。
再看那个无限循环,像不像再等待事件来临啊,成功accept一个socket就是一个事件,对scoket的使用,其实就是事件响应嘛。

OK,让我们按照这个思路来重构一下,目的就是加入事件机制,并将对具体实现的依赖控制在那几个工厂类里面去。

新增接口EventListener接口进行事件监听

public interface EventListener<T> {
    /**
    * 事件发生时的回调方法
    * @param event 事件对象
    * @throws EventException 处理事件时异常都转换为该异常抛出
    */
    void onEvent(T event) throws EventException;
}

为Socket事件实现一下,acceptConnect中打印日志的语句可以移动到这来

public class SocketEventListener implements EventListener<Socket> {
    private static final Logger LOGGER = LoggerFactory.getLogger(SocketEventListener.class);

    @Override
    public void onEvent(Socket socket) throws EventException {
        LOGGER.info("新增连接:" + socket.getInetAddress() + ":" + socket.getPort());
    }

重构Connector,添加事件机制,注意whenAccept方法调用了eventListener

public class SocketConnector extends Connector<Socket> {
    ... ...
    private final EventListener<Socket> eventListener;

    public SocketConnector(int port, EventListener<Socket> eventListener) {
        this.port = port;
        this.eventListener = eventListener;
    }

    @Override
    protected void acceptConnect() throws ConnectorException {
        new Thread(() -> {
            while (true && started) {
                Socket socket = null;
                try {
                    socket = serverSocket.accept();
                    whenAccept(socket);
                } catch (Exception e) {
                    //单个Socket异常,不要影响整个Connector
                    LOGGER.error(e.getMessage(), e);
                } finally {
                    IoUtils.closeQuietly(socket);
                }
            }
        }).start();
    }

    @Override
    protected void whenAccept(Socket socketConnect) throws ConnectorException {
        eventListener.onEvent(socketConnect);
    }
    ... ...
}

重构ServerFactory,添加对具体实现的依赖

public class ServerFactory {

    public static Server getServer(ServerConfig serverConfig) {
        List<Connector> connectorList = new ArrayList<>();
        SocketEventListener socketEventListener = new SocketEventListener();
        ConnectorFactory connectorFactory =
                new SocketConnectorFactory(new SocketConnectorConfig(serverConfig.getPort()), socketEventListener);
        connectorList.add(connectorFactory.getConnector());
        return new SimpleServer(serverConfig, connectorList);
    }
}

再运行所有单元测试,一切都OK。

现在让我们来操作socket,实现一个echo功能的server吧。
直接添加到SocketEventListener中

public class SocketEventListener implements EventListener<Socket> {
    private static final Logger LOGGER = LoggerFactory.getLogger(SocketEventListener.class);

    @Override
    public void onEvent(Socket socket) throws EventException {
        LOGGER.info("新增连接:" + socket.getInetAddress() + ":" + socket.getPort());
        try {
            echo(socket);
        } catch (IOException e) {
            throw new EventException(e);
        }
    }

    private void echo(Socket socket) throws IOException {
        InputStream inputstream = null;
        OutputStream outputStream = null;
        try {
            inputstream = socket.getInputStream();
            outputStream = socket.getOutputStream();
            Scanner scanner = new Scanner(inputstream);
            PrintWriter printWriter = new PrintWriter(outputStream);
            printWriter.append("Server connected.Welcome to echo.\\n");
            printWriter.flush();
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                if (line.equals("stop")) {
                    printWriter.append("bye bye.\\n");
                    printWriter.flush();
                    break;
                } else {
                    printWriter.append(line);
                    printWriter.append("\\n");
                    printWriter.flush();
                }
            }
        } finally {
            IoUtils.closeQuietly(inputstream);
            IoUtils.closeQuietly(outputStream);
        }
    }
}

之前都是在单元测试里面启动Server的,这次需要启动Server后,用telnet去使用echo功能。
所以再为Server编写一个启动类,在其main方法里面启动Server

public class BootStrap {
    public static void main(String[] args) throws IOException {
        ServerConfig serverConfig = new ServerConfig();
        Server server = ServerFactory.getServer(serverConfig);
        server.start();
    }
}

服务器启动后,使用telnet进行验证,打开cmd,然后输入telnet localhost 端口,端口是ServerConfig里面的默认端口或者其他,回车就可以交互了。

到现在为止,我们的服务器终于有了实际功能,下一步终于可以去实现请求静态资源的功能了。
完整代码:https://github.com/pkpk1234/BeggarServletContainer/tree/step4

5 EventHandler接口和FileEventHandler实现

首先重构代码,让事件监听和事件处理分离开,各自责任更加独立
否则想将Echo功能替换为返回静态文件,又需要到处改代码。
将责任分开后,只需要传入不同的事件处理器,即可实现不同效果。

增加EventHandler接口专门进行事件处理,SocketEventListener类中事件处理抽取到专门的EchoEventHandler实现中。

提出AbstractEventListener类,规定了事件处理的模板

public abstract class AbstractEventListener<T> implements EventListener<T> {
    /**
    * 事件处理流程模板方法
    * @param event 事件对象
    * @throws EventException
    */
    @Override
    public void onEvent(T event) throws EventException {
        EventHandler<T> eventHandler = getEventHandler(event);
        eventHandler.handle(event);
    }
    /**
    * 返回事件处理器
    * @param event
    * @return
    */
    protected abstract EventHandler<T> getEventHandler(T event);
}

SocketEventListener重构为通过构造器传入事件处理器

public class SocketEventListener extends AbstractEventListener<Socket> {
    private final EventHandler<Socket> eventHandler;
    public SocketEventListener(EventHandler<Socket> eventHandler) {
        this.eventHandler = eventHandler;
    }
    @Override
    protected EventHandler<Socket> getEventHandler(Socket event) {
        return eventHandler;
    }
}

EchoEventHandler实现Echo

public class EchoEventHandler extends AbstractEventHandler<Socket> {
    @Override
    protected void doHandle(Socket socket) {
        InputStream inputstream = null;
        OutputStream outputStream = null;
        try {
            inputstream = socket.getInputStream();
            outputStream = socket.getOutputStream();
            Scanner scanner = new Scanner(inputstream);
            PrintWriter printWriter = new PrintWriter(outputStream);
            printWriter.append("Server connected.Welcome to echo.\\n");
            printWriter.flush();
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                if (line.equals("stop")) {
                    printWriter.append("bye bye.\\n");
                    printWriter.flush();
                    break;
                } else {
                    printWriter.append(line);
                    printWriter.append("\\n");
                    printWriter.flush();
                }
            }
        } catch (IOException e) {
            throw new HandlerException(e);
        } finally {
            IoUtils.closeQuietly(inputstream);
            IoUtils.closeQuietly(outputStream);
        }
    }
}

再次将对具体实现的依赖限制到Factory中

public class ServerFactory {
    /**
    * 返回Server实例
    *
    * @return
    */
    public static Server getServer(ServerConfig serverConfig) {
        List<Connector> connectorList = new ArrayList<>();
        //传入Echo事件处理器
        SocketEventListener socketEventListener = new SocketEventListener(new EchoEventHandler());
        ConnectorFactory connectorFactory =
                new SocketConnectorFactory(new SocketConnectorConfig(serverConfig.getPort()), socketEventListener);
        connectorList.add(connectorFactory.getConnector());
        return new SimpleServer(serverConfig, connectorList);
    }
}

执行单元测试,一切正常。运行Server,用telnet进行echo,也是正常的。

现在添加返回静态文件功能。功能大致如下:

  1. 服务器使用user.dir作为根目录。
  2. 控制台输入文件路径,如果文件是目录,则打印目录中的文件列表;如果文件不是目录,且可读,则返回文件内容;如果不满足前面两种场景,返回文件找不到

新增FileEventHandler

public class FileEventHandler extends  AbstractEventHandler<Socket>{

    private final String docBase;

    public FileEventHandler(String docBase) {
        this.docBase = docBase;
    }

    @Override
    protected void doHandler(Socket socket) {
        getFile(socket);
    }

    private void getFile(Socket socket) {
        InputStream inputStream = null;
        OutputStream outputStream = null;

        try{
            inputStream = socket.getInputStream();
            outputStream = socket.getOutputStream();
            Scanner scanner = new Scanner(inputStream, "UTF-8");
            PrintWriter printWriter = new PrintWriter(outputStream);
            printWriter.append("Server connected.Welcome to File Server.\\n");
            printWriter.flush();
            while (scanner.hasNextLine()){
                String line = scanner.nextLine();
                if(line.equals("stop")){
                    printWriter.append("bye bye.\\n");
                    printWriter.flush();
                    break;
                }else {
                    Path filePath = Paths.get(this.docBase, line);
                    if(Files.isDirectory(filePath)){
                        printWriter.append("目录 ").append(filePath.toString()).append(" 下有文件: ").append("\\n");
                        try{
                            DirectoryStream<Path> stream = Files.newDirectoryStream(filePath);
                            for (Path path: stream){
                                printWriter.append(path.getFileName().toString()).append("\\n").flush();
                            }
                        }catch(IOException e){
                            e.printStackTrace();
                        }
                    //如果文件可读,就打印文件内容
                    } else if(Files.isReadable(filePath)){
                        printWriter.append("File: ").append(filePath.toString()).append(" 的内容是: ").append("\\n").flush();
                        Files.copy(filePath, outputStream);
                        printWriter.append("\\n");
                        //其他情况返回文件找不到
                    } else {
                        printWriter.append("File ").append(filePath.toString())
                                .append(" is not found.").append("\\n").flush();
                    }
                }
            }

        }catch (IOException e) {
            throw new HandlerException(e);
        } finally {
            IoUtils.closeQuietly(inputStream);
            IoUtils.closeQuietly(outputStream);
        }

    }
}

修改ServerFactory,使用FileEventHandler

public class ServerFactory {

    /**
    * 返回Server实例
    * @return
    */
    public static Server getServer(ServerConfig serverConfig) {
        List<Connector> connectorList = new ArrayList<>();

        //EventHandler eventHandler =new EchoEventHandler();
        EventHandler eventHandler = new FileEventHandler(System.getProperty("user.dir"));

        SocketEventListener socketEventListener = new SocketEventListener(eventHandler);

        ConnectorFactory connectorFactory = new SocketConnectorFactory(new SocketConnectorConfig(serverConfig.getPort()), socketEventListener);
        connectorList.add(connectorFactory.getConnector());
        return new SimpleServer(serverConfig, connectorList);
    }
}

运行BootStrap启动Server进行验证:

绿色框:输入回车,返回目录下文件列表。
黄色框:输入README.MD,返回文件内容
蓝色框:输入不存在的文件,返回文件找不到。

完整代码:https://github.com/pkpk1234/BeggarServletContainer/tree/step5

以上是关于乞丐版servlet容器第3篇的主要内容,如果未能解决你的问题,请参考以下文章

乞丐版servlet容器第2篇

C#实现乞丐版IOC容器

乞丐版JAVA扫雷

Word2Vec的PyTorch实现(乞丐版)

servlet

通学智能合约系列(二十二)--乞丐版众筹项目实践