1. 程式人生 > >Solr 6.0 學習(八) SolrDispatchFilter原始碼解析及solr擴充套件

Solr 6.0 學習(八) SolrDispatchFilter原始碼解析及solr擴充套件

SolrDispatchFilter做了什麼

我們釋出好我們的solr6.X之後我們可以看到專案下web.xml中一段配置

<!-- Any path (name) registered in solrconfig.xml will be sent to that filter -->
  <filter>
    <filter-name>SolrRequestFilter</filter-name>
    <filter-class>org.apache.solr.servlet.SolrDispatchFilter</filter-class
>
<!-- Exclude patterns is a list of directories that would be short circuited by the SolrDispatchFilter. It includes all Admin UI related static content. NOTE: It is NOT a pattern but only matches the start of the HTTP ServletPath. --> <init-param> <param-name
>
excludePatterns</param-name> <param-value>/css/.+,/js/.+,/img/.+,/tpl/.+</param-value> </init-param> </filter> <filter-mapping> <!-- NOTE: When using multicore, /admin JSP URLs with a core specified such as /solr/coreName/admin/stats.jsp get forwarded by a RequestDispatcher to /solr/admin/stats.jsp with the specified core put into request scope keyed as "org.apache.solr.SolrCore". It is unnecessary, and potentially problematic, to have the SolrDispatchFilter configured to also filter on forwards. Do not configure this dispatcher as <dispatcher>FORWARD</dispatcher>. -->
<filter-name>SolrRequestFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

我們注意其中org.apache.solr.servlet.SolrDispatchFilter
這個是個全域性的過濾器Filter。下面我們來一步步的研究一下其原始碼主要做了些什麼

BaseSolrFilter

package org.apache.solr.servlet;

import javax.servlet.Filter;

abstract class BaseSolrFilter
  implements Filter
{
  static
  {
    CheckLoggingConfiguration.check();
  }
}

CheckLoggingConfiguration

package org.apache.solr.servlet;

import org.slf4j.LoggerFactory;

final class CheckLoggingConfiguration
{
  static void check()
  {
    try
    {
      LoggerFactory.getLogger(CheckLoggingConfiguration.class);
    } catch (NoClassDefFoundError e) {
      throw new NoClassDefFoundError("Failed to initialize Apache Solr: Could not find necessary SLF4j logging jars. If using Jetty, the SLF4j logging jars need to go in the jetty lib/ext directory. For other containers, the corresponding directory should be used. For more information, see: http://wiki.apache.org/solr/SolrLogging");
    }
  }
}

SolrDispatchFilter

package org.apache.solr.servlet;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.HttpClient;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.NodeConfig;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.core.SolrXmlConfig;
import org.apache.solr.request.SolrRequestInfo;
import org.apache.solr.security.AuthenticationPlugin;
import org.apache.solr.update.UpdateShardHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SolrDispatchFilter extends BaseSolrFilter
{
  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
  protected volatile CoreContainer cores;
  protected String abortErrorMessage = null;
  protected HttpClient httpClient;
  private ArrayList<Pattern> excludePatterns;
  public static final String PROPERTIES_ATTRIBUTE = "solr.properties";
  public static final String SOLRHOME_ATTRIBUTE = "solr.solr.home";

  public void init(FilterConfig config)
    throws ServletException
  {
    log.info("SolrDispatchFilter.init(): {}", getClass().getClassLoader());
    //獲取filter配置中的excludePatterns,實際上就是配置不經過filter的路徑
    String exclude = config.getInitParameter("excludePatterns");
    if (exclude != null) {
      String[] excludeArray = exclude.split(",");
      this.excludePatterns = new ArrayList();
      for (String element : excludeArray)
        this.excludePatterns.add(Pattern.compile(element));
    }
    try
    {
      Properties extraProperties = (Properties)config.getServletContext().getAttribute("solr.properties");
      if (extraProperties == null) {
        extraProperties = new Properties();
      }
      String solrHome = (String)config.getServletContext().getAttribute("solr.solr.home");
      ExecutorUtil.addThreadLocalProvider(SolrRequestInfo.getInheritableThreadLocalProvider());
      this.cores = createCoreContainer(solrHome == null ? SolrResourceLoader.locateSolrHome() : Paths.get(solrHome, new String[0]), extraProperties);

      this.httpClient = this.cores.getUpdateShardHandler().getHttpClient();
      log.info("user.dir=" + System.getProperty("user.dir"));
    }
    catch (Throwable t)
    {
      log.error("Could not start Solr. Check solr/home property and the logs");
      SolrCore.log(t);
      if ((t instanceof Error)) {
        throw ((Error)t);
      }
    }

    log.info("SolrDispatchFilter.init() done");
  }

  protected CoreContainer createCoreContainer(Path solrHome, Properties extraProperties)
  {
    NodeConfig nodeConfig = loadNodeConfig(solrHome, extraProperties);
    this.cores = new CoreContainer(nodeConfig, extraProperties, true);
    this.cores.load();
    return this.cores;
  }

  public static NodeConfig loadNodeConfig(Path solrHome, Properties nodeProperties)
  {
    SolrResourceLoader loader = new SolrResourceLoader(solrHome, null, nodeProperties);
    if (!StringUtils.isEmpty(System.getProperty("solr.solrxml.location"))) {
      log.warn("Solr property solr.solrxml.location is no longer supported. Will automatically load solr.xml from ZooKeeper if it exists");
    }

    String zkHost = System.getProperty("zkHost");
    if (!StringUtils.isEmpty(zkHost)) {
      try { SolrZkClient zkClient = new SolrZkClient(zkHost, 30000); Throwable localThrowable4 = null;
        try { if (zkClient.exists("/solr.xml", true).booleanValue()) {
            log.info("solr.xml found in ZooKeeper. Loading...");
            byte[] data = zkClient.getData("/solr.xml", null, null, true);
            return SolrXmlConfig.fromInputStream(loader, new ByteArrayInputStream(data));
          }
        }
        catch (Throwable localThrowable2)
        {
          localThrowable4 = localThrowable2; throw localThrowable2;
        }
        finally
        {
          if (zkClient != null) if (localThrowable4 != null) try { zkClient.close(); } catch (Throwable localThrowable3) { localThrowable4.addSuppressed(localThrowable3); } else zkClient.close();  
        } } catch (Exception e) { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error occurred while loading solr.xml from zookeeper", e); }

      log.info("Loading solr.xml from SolrHome (not found in ZooKeeper)");
    }
    return SolrXmlConfig.fromSolrHome(loader, loader.getInstancePath());
  }

  public CoreContainer getCores() {
    return this.cores;
  }

  public void destroy()
  {
    if (this.cores != null)
      try {
        this.cores.shutdown();

        this.cores = null; } finally { this.cores = null; }

  }

  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException
  {
    doFilter(request, response, chain, false);
  }

  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain, boolean retry) throws IOException, ServletException {
    if (!(request instanceof HttpServletRequest)) return;
    try
    {
      if ((this.cores == null) || (this.cores.isShutDown())) {
        log.error("Error processing the request. CoreContainer is either not initialized or shutting down.");
        throw new SolrException(SolrException.ErrorCode.SERVICE_UNAVAILABLE, "Error processing the request. CoreContainer is either not initialized or shutting down.");
      }

      AtomicReference wrappedRequest = new AtomicReference();
      if (!authenticateRequest(request, response, wrappedRequest))
      {
        return;
      }
      if (wrappedRequest.get() != null) {
        request = (ServletRequest)wrappedRequest.get();
      }
      if (this.cores.getAuthenticationPlugin() != null)
        log.debug("User principal: {}", ((HttpServletRequest)request).getUserPrincipal());
      String requestPath;
      if (this.excludePatterns != null) {
        requestPath = ((HttpServletRequest)request).getServletPath();
        String extraPath = ((HttpServletRequest)request).getPathInfo();
        if (extraPath != null)
        {
          requestPath = requestPath + extraPath;
        }
        for (Pattern p : this.excludePatterns) {
          Matcher matcher = p.matcher(requestPath);
          if (matcher.lookingAt()) {
            chain.doFilter(request, response);
            return;
          }
        }
      }

      HttpSolrCall call = getHttpSolrCall((HttpServletRequest)request, (HttpServletResponse)response, retry);
      ExecutorUtil.setServerThreadFlag(Boolean.TRUE);
      try {
        Action result = call.call();
        switch (2.$SwitchMap$org$apache$solr$servlet$SolrDispatchFilter$Action[result.ordinal()]) {
        case 1:
          chain.doFilter(request, response);
          break;
        case 2:
          doFilter(request, response, chain, true);
          break;
        case 3:
          request.getRequestDispatcher(call.getPath()).forward(request, response);
        }
      }
      finally {
        call.destroy();
        ExecutorUtil.setServerThreadFlag(null);
      }
    } finally {
      consumeInputFully((HttpServletRequest)request);
    }
  }

  private void consumeInputFully(HttpServletRequest req)
  {
    try {
      ServletInputStream is = req.getInputStream();
      while ((!is.isFinished()) && (is.read() != -1));
    }
    catch (IOException e) {
      log.info("Could not consume full client request", e);
    }
  }

  protected HttpSolrCall getHttpSolrCall(HttpServletRequest request, HttpServletResponse response, boolean retry)
  {
    return new HttpSolrCall(this, this.cores, request, response, retry);
  }

  private boolean authenticateRequest(ServletRequest request, ServletResponse response, final AtomicReference<ServletRequest> wrappedRequest) throws IOException {
    final AtomicBoolean isAuthenticated = new AtomicBoolean(false);
    AuthenticationPlugin authenticationPlugin = this.cores.getAuthenticationPlugin();
    if (authenticationPlugin == null) {
      return true;
    }

    String header = ((HttpServletRequest)request).getHeader("SolrAuth");
    if ((header != null) && (this.cores.getPkiAuthenticationPlugin() != null))
      authenticationPlugin = this.cores.getPkiAuthenticationPlugin();
    try {
      log.debug("Request to authenticate: {}, domain: {}, port: {}", new Object[] { request, request.getLocalName(), Integer.valueOf(request.getLocalPort()) });

      authenticationPlugin.doAuthenticate(request, response, new FilterChain() {
        public void doFilter(ServletRequest req, ServletResponse rsp) throws IOException, ServletException {
          isAuthenticated.set(true);
          wrappedRequest.set(req);
        } } );
    }
    catch (Exception e) {
      e.printStackTrace();
      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error during request authentication, ", e);
    }

    if (!isAuthenticated.get()) {
      response.flushBuffer();
      return false;
    }
    return true;
  }

  static enum Action
  {
    PASSTHROUGH, FORWARD, RETURN, RETRY, ADMIN, REMOTEQUERY, PROCESS;
  }
}

原始碼很多,我們拆分來看。

  1. 初始化solr core容器
this.cores = createCoreContainer(solrHome == null ? SolrResourceLoader.locateSolrHome() : Paths.get(solrHome, new String[0]), extraProperties);

我們看到SolrResourceLoader.locateSolrHome()
擷取部分程式碼

 /*
 *可以找到原始碼中讀取配置檔案程式碼
*   Context c = new InitialContext();
*   home = (String)c.lookup("java:comp/env/solr/home");
*   補充說明:
*   env -entry元素宣告Web應用的環境項。它由一個可選的description元素、一個
*   env-entry-name元素(一個相對於java: comp/env環境JNDI名)、一個env-entry-value元素(項值)以及一個env-entry-type元素
*  (java.lang程式包中一個型別的完全限定類名,java.lang.Boolean、java.lang.String等)組成。
*   那麼實際上就是去找web.xml下
*   <!-- 配置solr home位置  -->
    <env-entry>
       <env-entry-name>solr/home</env-entry-name>
       <env-entry-value>D:\solr\apache-tomcat-8.0.33\webapps\solr\solrhome</env-entry-value>
       <env-entry-type>java.lang.String</env-entry-type>
    </env-entry>
*/
 public static Path locateSolrHome()
  {
    String home = null;
    try
    {
      Context c = new InitialContext();
      home = (String)c.lookup("java:comp/env/solr/home");
      log.info(new StringBuilder().append("Using JNDI solr.home: ").append(home).toString());
    } catch (NoInitialContextException e) {
      log.info("JNDI not configured for solr (NoInitialContextEx)");
    } catch (NamingException e) {
      log.info("No /solr/home in JNDI");
    } catch (RuntimeException ex) {
      log.warn(new StringBuilder().append("Odd RuntimeException while testing for JNDI: ").append(ex.getMessage()).toString());
    }

    if (home == null) {
      String prop = "solr.solr.home";
      home = System.getProperty(prop);
      if (home != null) {
        log.info(new StringBuilder().append("using system property ").append(prop).append(": ").append(home).toString());
      }

    }

    if (home == null) {
      home = "solr/";
      log.info(new StringBuilder().append("solr home defaulted to '").append(home).append("' (could not find system property or JNDI)").toString());
    }
    return Paths.get(home, new String[0]);
  }
/**
 * 建立solr core容器
 * @param solrHome
 * @param extraProperties
 * @return
 */
  protected CoreContainer createCoreContainer(Path solrHome, Properties extraProperties)
  {
    NodeConfig nodeConfig = loadNodeConfig(solrHome, extraProperties);
    this.cores = new CoreContainer(nodeConfig, extraProperties, true);
    this.cores.load();
    return this.cores;
  }
/**
 * 獲取配置檔案  包含zookeeper的配置
 * @param solrHome
 * @param nodeProperties
 * @return
 */
  public static NodeConfig loadNodeConfig(Path solrHome, Properties nodeProperties)
  {
    SolrResourceLoader loader = new SolrResourceLoader(solrHome, null, nodeProperties);
    if (!StringUtils.isEmpty(System.getProperty("solr.solrxml.location"))) {
      log.warn("Solr property solr.solrxml.location is no longer supported. Will automatically load solr.xml from ZooKeeper if it exists");
    }

    String zkHost = System.getProperty("zkHost");
    if (!StringUtils.isEmpty(zkHost)) {
      try { SolrZkClient zkClient = new SolrZkClient(zkHost, 30000); Throwable localThrowable4 = null;
        try { if (zkClient.exists("/solr.xml", true).booleanValue()) {
            log.info("solr.xml found in ZooKeeper. Loading...");
            byte[] data = zkClient.getData("/solr.xml", null, null, true);
            return SolrXmlConfig.fromInputStream(loader, new ByteArrayInputStream(data));
          }
        }
        catch (Throwable localThrowable2)
        {
          localThrowable4 = localThrowable2; throw localThrowable2;
        }
        finally
        {
          if (zkClient != null) if (localThrowable4 != null) try { zkClient.close(); } catch (Throwable localThrowable3) { localThrowable4.addSuppressed(localThrowable3); } else zkClient.close();  
        } } catch (Exception e) { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error occurred while loading solr.xml from zookeeper", e); }

      log.info("Loading solr.xml from SolrHome (not found in ZooKeeper)");
    }
    return SolrXmlConfig.fromSolrHome(loader, loader.getInstancePath());
  }

2、doFilter:全域性性的過濾,將對特定的需要全文檢索的請求進行一個過濾然後提供全文檢索服務

HttpSolrCall call = getHttpSolrCall((HttpServletRequest)request, (HttpServletResponse)response, retry);

獲取httpSolrCall例項

protected HttpSolrCall getHttpSolrCall(HttpServletRequest request, HttpServletResponse response, boolean retry)
  {
    return new HttpSolrCall(this, this.cores, request, response, retry);
  }

呼叫call

 Action result = call.call();

下面我們重點看下HttpSolrCall 做了些什麼,Solr 6.0 學習(九)會對HttpSolrCall做更詳細的解答。