1. 程式人生 > >乞丐版servlet容器第3篇

乞丐版servlet容器第3篇

輸出 handle event git bootstra 移動 驗證 註意 抽取

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篇