BinCat V4-实现PHP文件解析

    添加Quercus依赖:

    然后创建一个QuercusServlet映射,因为BinCat只支持注解,所以无法在QuercusServlet类上添加@WebServlet注解,但是我们可以写一个类去继承QuercusServlet从而间接的完成Servlet声明。

    QuercusPHPServlet示例:

    1. package com.anbai.sec.server.test.servlet;
    2. import com.caucho.quercus.servlet.QuercusServlet;
    3. import javax.servlet.annotation.WebServlet;
    4. @WebServlet(name = "QuercusPHPServlet", urlPatterns = ".*\\.php$")
    5. public class QuercusPHPServlet extends QuercusServlet {
    6. }

    BinCatConfig示例代码(方便统一的Servlet注册):

    1. /**
    2. * 手动注册Servlet并创建BinCatServletContext对象
    3. *
    4. * @param appClassLoader 应用的类加载器
    5. * @return ServletContext Servlet上下文对象
    6. */
    7. public static BinCatServletContext createServletContext(BinCatWebAppClassLoader appClassLoader) throws Exception {
    8. BinCatServletContext servletContext = new BinCatServletContext(appClassLoader);
    9. // 手动注册Servlet类
    10. Class<Servlet>[] servletClass = new Class[]{
    11. TestServlet.class,
    12. CMDServlet.class,
    13. QuercusPHPServlet.class
    14. };
    15. for (Class<Servlet> clazz : servletClass) {
    16. Servlet servlet = clazz.newInstance();
    17. WebServlet webServlet = clazz.getAnnotation(WebServlet.class);
    18. if (webServlet != null) {
    19. // 获取WebInitParam配置
    20. WebInitParam[] webInitParam = webServlet.initParams();
    21. // 动态创建Servlet对象
    22. ServletRegistration.Dynamic dynamic = servletContext.addServlet(webServlet.name(), servlet);
    23. // 动态设置Servlet映射地址
    24. dynamic.addMapping(webServlet.urlPatterns());
    25. // 设置Servlet启动参数
    26. for (WebInitParam initParam : webInitParam) {
    27. dynamic.setInitParameter(initParam.name(), initParam.value());
    28. }
    29. }
    30. }
    31. // 创建ServletContext
    32. return servletContext;
    33. }

    因为QuercusServlet创建时需要必须有ServletContext对象,所以我们必须实现ServletContext接口。除此之外,Servlet创建时还需要调用Servlet的初始化方法(public void init(ServletConfig config) throws ServletException)。调用init的时候还需要实现ServletConfig接口。

    初始化Servlet代码片段:

    BinCatServletContext实现

    Servlet容器启动的时候必须创建一个ServletContext(Servlet上下文),用于管理容器中的所有Servlet对象。在创建BinCatServletContext的时候需要创建并初始化所有的Servlet并存储到servletMap中。

    1. package com.anbai.sec.server.servlet;
    2. import javax.servlet.*;
    3. import javax.servlet.annotation.WebServlet;
    4. import javax.servlet.descriptor.JspConfigDescriptor;
    5. import java.io.File;
    6. import java.io.InputStream;
    7. import java.net.MalformedURLException;
    8. import java.net.URL;
    9. import java.util.*;
    10. import java.util.concurrent.ConcurrentHashMap;
    11. public class BinCatServletContext implements ServletContext {
    12. // 创建一个装动态注册的Servlet的Map
    13. private final Map<String, Servlet> servletMap = new HashMap<>();
    14. // 创建一个装ServletContext初始化参数的Map
    15. private final Map<String, String> initParameterMap = new HashMap<>();
    16. // 创建一个装ServletContext属性对象的Map
    17. private final Map<String, Object> attributeMap = new HashMap<>();
    18. // 创建一个装Servlet动态注册的Set
    19. private final Set<BinCatServletRegistrationDynamic> registrationDynamics = new LinkedHashSet<>();
    20. // BinCatWebAppClassLoader,Web应用的类加载器
    21. private final BinCatWebAppClassLoader appClassLoader;
    22. public BinCatServletContext(BinCatWebAppClassLoader appClassLoader) throws Exception {
    23. this.appClassLoader = appClassLoader;
    24. }
    25. // 此处省略ServletContext接口中的大部分方法,仅保留几个示例方法...
    26. @Override
    27. public Servlet getServlet(String name) throws ServletException {
    28. }
    29. @Override
    30. public Enumeration<Servlet> getServlets() {
    31. servlets.addAll(servletMap.values());
    32. return Collections.enumeration(servlets);
    33. }
    34. @Override
    35. public Enumeration<String> getServletNames() {
    36. Set<String> servlets = new HashSet<String>();
    37. servlets.addAll(servletMap.keySet());
    38. return Collections.enumeration(servlets);
    39. }
    40. }

    在创建BinCatServletContext时我们指定了一个ServletConfig实现:BinCatServletConfigServletConfig用于指定Servlet启动时的配置信息。

    BinCatServletConfig实现:

    1. package com.anbai.sec.server.servlet;
    2. import javax.servlet.ServletConfig;
    3. import javax.servlet.ServletContext;
    4. import javax.servlet.annotation.WebInitParam;
    5. import javax.servlet.annotation.WebServlet;
    6. import java.util.Collections;
    7. import java.util.Enumeration;
    8. import java.util.HashSet;
    9. import java.util.Set;
    10. public class BinCatServletConfig implements ServletConfig {
    11. private final BinCatServletContext servletContext;
    12. private final WebServlet webServlet;
    13. private final WebInitParam[] webInitParam;
    14. public BinCatServletConfig(BinCatServletContext servletContext, WebServlet webServlet) {
    15. this.servletContext = servletContext;
    16. this.webServlet = webServlet;
    17. this.webInitParam = webServlet.initParams();
    18. }
    19. @Override
    20. public String getServletName() {
    21. return webServlet.name();
    22. }
    23. @Override
    24. public ServletContext getServletContext() {
    25. return this.servletContext;
    26. }
    27. @Override
    28. public String getInitParameter(String name) {
    29. for (WebInitParam initParam : webInitParam) {
    30. String paramName = initParam.name();
    31. if (paramName.equals(name)) {
    32. return initParam.value();
    33. }
    34. }
    35. return null;
    36. }
    37. @Override
    38. public Enumeration<String> getInitParameterNames() {
    39. Set<String> initParamSet = new HashSet<String>();
    40. for (WebInitParam initParam : webInitParam) {
    41. initParamSet.add(initParam.name());
    42. }
    43. return Collections.enumeration(initParamSet);
    44. }
    45. }

    BinCatDispatcherServlet实现

    为了方便后续的BinCat版本处理Http请求和响应处理结果,我们简单的封装了BinCatDispatcherServletBinCatResponseHandler对象。BinCatDispatcherServlet会根据浏览器请求的不同URL地址去调用对应的Servlet服务,除此之外还提供了一个简单的静态资源文件处理逻辑和PHP解析功能。

    BinCatDispatcherServlet实现代码:

    BinCatResponseHandler只是一个简单封装的用于向浏览器输出Http处理请求结果的对象。

    BinCatResponseHandler实现代码:

    1. package com.anbai.sec.server.handler;
    2. import com.anbai.sec.server.servlet.BinCatResponse;
    3. import java.io.ByteArrayOutputStream;
    4. import java.io.IOException;
    5. import java.io.OutputStream;
    6. import java.util.Map;
    7. public class BinCatResponseHandler {
    8. public void processResult(BinCatResponse response, Map<String, String> responseHeader, String serverName,
    9. OutputStream out, ByteArrayOutputStream baos) throws IOException {
    10. // 处理Http响应内容
    11. out.write(("HTTP/1.1 " + response.getStatus() + " " + response.getMessage() + "\n").getBytes());
    12. // 输出Web服务器信息
    13. out.write(("Server: " + serverName + "\n").getBytes());
    14. // 输出返回的消息类型
    15. out.write(("Content-Type: " + response.getContentType() + "\n").getBytes());
    16. // 输出返回字节数
    17. out.write(("Content-Length: " + baos.size() + "\n").getBytes());
    18. // 输出用户自定义的Header
    19. for (String key : responseHeader.keySet()) {
    20. out.write((key + ": " + responseHeader.get(key) + "\n").getBytes());
    21. // 写入换行
    22. out.write("\n".getBytes());
    23. // 将读取到的数据写入到客户端Socket
    24. out.write(baos.toByteArray());
    25. }
    26. }

    BinCat V4实现

    V4V3的基础上实现了ServletConfigServletContext接口,从而实现了Servlet实例化初始化BinCatDispatcherServlet实现的Servlet服务调用。

    1. package com.anbai.sec.server;
    2. import com.anbai.sec.server.config.BinCatConfig;
    3. import com.anbai.sec.server.handler.BinCatDispatcherServlet;
    4. import com.anbai.sec.server.handler.BinCatResponseHandler;
    5. import com.anbai.sec.server.servlet.BinCatRequest;
    6. import com.anbai.sec.server.servlet.BinCatResponse;
    7. import com.anbai.sec.server.servlet.BinCatServletContext;
    8. import java.io.ByteArrayOutputStream;
    9. import java.io.InputStream;
    10. import java.io.OutputStream;
    11. import java.net.ServerSocket;
    12. import java.net.Socket;
    13. import java.util.Map;
    14. import java.util.concurrent.ConcurrentHashMap;
    15. import java.util.logging.Logger;
    16. /**
    17. * ServerSocket Http 服务器示例
    18. */
    19. public class BinCatServerV4 {
    20. // 设置服务监听端口
    21. private static final int PORT = 8080;
    22. // 设置服务名称
    23. private static final String SERVER_NAME = "BinCat-0.0.4";
    24. private static final Logger LOG = Logger.getLogger("info");
    25. public static void main(String[] args) {
    26. try {
    27. // 创建ServerSocket,监听本地端口
    28. ServerSocket ss = new ServerSocket(PORT);
    29. // 创建BinCatServletContext对象
    30. BinCatServletContext servletContext = BinCatConfig.createServletContext();
    31. // 初始化Servlet
    32. BinCatConfig.initServlet(servletContext);
    33. LOG.info(SERVER_NAME + " 启动成功,监听端口: " + PORT);
    34. while (true) {
    35. // 等待客户端连接
    36. Socket socket = ss.accept();
    37. try {
    38. // 获取Socket输入流对象
    39. InputStream in = socket.getInputStream();
    40. // 获取Socket输出流对象
    41. OutputStream out = socket.getOutputStream();
    42. // 创建BinCat请求处理对象
    43. BinCatRequest request = new BinCatRequest(socket, servletContext);
    44. // 创建BinCat请求处理结果输出流
    45. ByteArrayOutputStream baos = new ByteArrayOutputStream();
    46. // 创建BinCat请求处理结果Header对象
    47. Map<String, String> responseHeader = new ConcurrentHashMap<String, String>();
    48. // 创建BinCat响应处理对象
    49. BinCatResponse response = new BinCatResponse(socket, responseHeader, baos);
    50. // 创建BinCatDispatcherServlet对象,用于分发Http请求
    51. BinCatDispatcherServlet dispatcherServlet = new BinCatDispatcherServlet();
    52. // 创建BinCatResponseHandler对象,用于处理Http请求结果
    53. BinCatResponseHandler responseHandler = new BinCatResponseHandler();
    54. // 使用BinCatDispatcherServlet处理Servlet请求
    55. dispatcherServlet.doDispatch(request, response, baos);
    56. // 响应服务器处理结果
    57. responseHandler.processResult(response, responseHeader, SERVER_NAME, out, baos);
    58. in.close();
    59. out.close();
    60. } catch (Exception e) {
    61. LOG.info("处理客户端请求异常:" + e);
    62. } finally {
    63. socket.close();
    64. }
    65. }
    66. } catch (Exception e) {
    67. e.printStackTrace();
    68. }
    69. }

    我们需要在javaweb-sec项目根目录创建一个测试文件,如info.php:

    启动BinCat V4后访问:

    image-20200911150900145

    复制一个最新版本的Discuzjavaweb-sec目录,尝试安装Discuz,访问:http://localhost:8080/discuz/install/index.php

    Discuz环境检测正常:

    测试BinCatPHP解析功能正常,只是开始安装Discuz时无法下一步,无异常和错误卡了,无法完成安装。