BinCat V4-实现PHP文件解析

Quercus-QuercusServlet

Quercus是一个Resin实现的解析并运行php文件的jar库,其本质是使用QuercusServlet处理所有访问.php的文件请求,Quercus会将php文件翻译成java class文件并在JVM中执行。

添加Quercus依赖:

  1. <dependency>
  2. <groupId>com.caucho</groupId>
  3. <artifactId>quercus</artifactId>
  4. <version>4.0.63</version>
  5. </dependency>

然后创建一个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代码片段:

  1. /**
  2. * 初始化Servlet
  3. *
  4. * @param servletContext Servlet上下文
  5. * @throws ServletException Servlet处理异常
  6. */
  7. public static void initServlet(BinCatServletContext servletContext) throws ServletException {
  8. Set<BinCatServletRegistrationDynamic> dynamics = servletContext.getRegistrationDynamics();
  9. for (BinCatServletRegistrationDynamic dynamic : dynamics) {
  10. Servlet servlet = dynamic.getServlet();
  11. String servletName = dynamic.getServletName();
  12. Map<String, String> initParameterMap = dynamic.getInitParameters();
  13. servlet.init(new BinCatServletConfig(servletContext, servletName, initParameterMap));
  14. }
  15. }

BinCatServletContext实现

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

BinCatServletContext代码片段:

  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. return servletMap.get(name);
  29. }
  30. @Override
  31. public Enumeration<Servlet> getServlets() {
  32. Set<Servlet> servlets = new HashSet<Servlet>();
  33. servlets.addAll(servletMap.values());
  34. return Collections.enumeration(servlets);
  35. }
  36. @Override
  37. public Enumeration<String> getServletNames() {
  38. Set<String> servlets = new HashSet<String>();
  39. servlets.addAll(servletMap.keySet());
  40. return Collections.enumeration(servlets);
  41. }
  42. }

BinCatServletConfig实现

在创建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实现代码:

  1. package com.anbai.sec.server.handler;
  2. import com.anbai.sec.server.servlet.BinCatRequest;
  3. import com.anbai.sec.server.servlet.BinCatResponse;
  4. import com.anbai.sec.server.servlet.BinCatServletContext;
  5. import com.anbai.sec.server.servlet.BinCatServletRegistrationDynamic;
  6. import org.javaweb.utils.FileUtils;
  7. import org.javaweb.utils.StringUtils;
  8. import java.io.ByteArrayOutputStream;
  9. import java.io.File;
  10. import java.io.IOException;
  11. import java.nio.file.Files;
  12. import java.util.Collection;
  13. import java.util.Set;
  14. import java.util.regex.Pattern;
  15. public class BinCatDispatcherServlet {
  16. public void doDispatch(BinCatRequest req, BinCatResponse resp, ByteArrayOutputStream out) throws IOException {
  17. // 请求URI地址
  18. String uri = req.getRequestURI();
  19. // 获取ServletContext
  20. BinCatServletContext servletContext = (BinCatServletContext) req.getServletContext();
  21. // 获取Http请求的文件
  22. File requestFile = new File(req.getRealPath(uri));
  23. // 处理Http请求的静态文件,如果文件存在(.php后缀除外)就直接返回文件内容,不需要调用Servlet
  24. if (requestFile.exists() && requestFile.isFile() && !uri.endsWith(".php")) {
  25. // 修改状态码
  26. resp.setStatus(200, "OK");
  27. // 解析文件的MimeType
  28. String mimeType = Files.probeContentType(requestFile.toPath());
  29. if (mimeType == null) {
  30. String fileSuffix = FileUtils.getFileSuffix(requestFile.getName());
  31. resp.setContentType("text/" + fileSuffix);
  32. } else {
  33. resp.setContentType(mimeType);
  34. }
  35. out.write(Files.readAllBytes(requestFile.toPath()));
  36. } else {
  37. // 遍历所有已注册得Servlet,处理Http请求
  38. Set<BinCatServletRegistrationDynamic> dynamics = servletContext.getRegistrationDynamics();
  39. for (BinCatServletRegistrationDynamic dynamic : dynamics) {
  40. Collection<String> urlPatterns = dynamic.getMappings();
  41. for (String urlPattern : urlPatterns) {
  42. try {
  43. // 检测请求的URL地址和Servlet的地址是否匹配
  44. if (Pattern.compile(urlPattern).matcher(uri).find()) {
  45. // 修改状态码
  46. resp.setStatus(200, "OK");
  47. // 调用Servlet请求处理方法
  48. dynamic.getServlet().service(req, resp);
  49. return;
  50. }
  51. } catch (Exception e) {
  52. // 修改状态码,输出服务器异常信息到浏览器
  53. resp.setStatus(500, "Internal Server Error");
  54. e.printStackTrace();
  55. out.write(("<pre>" + StringUtils.exceptionToString(e) + "</pre>").getBytes());
  56. }
  57. }
  58. }
  59. }
  60. }
  61. }

BinCatResponseHandler实现

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. // 写入换行
  23. out.write("\n".getBytes());
  24. // 将读取到的数据写入到客户端Socket
  25. out.write(baos.toByteArray());
  26. }
  27. }

BinCat V4实现

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

BinCatServerV4实现代码:

  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. }
  70. }

BinCat PHP解析测试

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

  1. <?php phpinfo();?>

image-20200911163413630

启动BinCat V4后访问http://localhost:8080/info.php:

image-20200911150900145

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

image-20200911150712040

Discuz环境检测正常:

image-20200911150754241

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