乞丐版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,也是正常的。
現在添加返回靜態文件功能。功能大致如下:
- 服務器使用user.dir作為根目錄。
- 控制臺輸入文件路徑,如果文件是目錄,則打印目錄中的文件列表;如果文件不是目錄,且可讀,則返回文件內容;如果不滿足前面兩種場景,返回文件找不到
新增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篇