/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.apache.solr.servlet;import java.io.IOException;import java.io.Writer;import java.io.PrintWriter;import java.io.StringWriter;import java.io.OutputStreamWriter;import java.io.ByteArrayInputStream;import java.nio.charset.Charset;import java.util.Map;import java.util.WeakHashMap;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.xml.sax.InputSource;import javax.servlet.Filter;import javax.servlet.FilterChain;import javax.servlet.FilterConfig;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.apache.solr.common.SolrException;import org.apache.solr.common.util.NamedList;import org.apache.solr.common.util.SimpleOrderedMap;import org.apache.solr.common.params.CommonParams;import org.apache.solr.common.util.FastWriter;import org.apache.solr.common.util.ContentStreamBase;import org.apache.solr.core.*;import org.apache.solr.request.*;import org.apache.solr.response.BinaryQueryResponseWriter;import org.apache.solr.response.QueryResponseWriter;import org.apache.solr.response.SolrQueryResponse;import org.apache.solr.servlet.cache.HttpCacheHeaderUtil;import org.apache.solr.servlet.cache.Method;/** * This filter looks at the incoming URL maps them to handlers defined in solrconfig.xml * 检查所有的请求路径将他们映射到在solrconfig.xml中定义的处理器上 * @since solr 1.2 */public class SolrDispatchFilter implements Filter{ final Logger log = LoggerFactory.getLogger(SolrDispatchFilter.class); protected CoreContainer cores;/** 这是最关键的一个属性他是但前web应用中为一个用于存放不同solrcore实例的容器 solrcore是用于存放索引以及如何进行检索服务的一个core类*/ protected String pathPrefix = null; /** strip this from the beginning of a path 忽略路径中的这段字符窜*/ protected String abortErrorMessage = null; protected String solrConfigFilename = null;/** 对应的某个solrcore实例的配置文件名字*/ protected final Map<SolrConfig, SolrRequestParsers> parsers = new WeakHashMap<SolrConfig, SolrRequestParsers>();/** 某个solrcore实例的请求分析器的一个集合*/ protected final SolrRequestParsers adminRequestParser;/** 对应到管理CoreContainer的那个处理器 */ private static final Charset UTF8 = Charset.forName("UTF-8"); /** 在实例话这个对象的时候其实做了很多事情他找到了我们的solrhome主目录并且开始加载一些重要的配置文件 */ public SolrDispatchFilter() { try { adminRequestParser = new SolrRequestParsers(new Config(null,"solr",new InputSource(new ByteArrayInputStream("<root/>".getBytes("UTF-8"))),"") ); } catch (Exception e) { //unlikely throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,e); } } /** 实例化CoreContainer容器将所有的solrCore实例装到容器中 */ public void init(FilterConfig config) throws ServletException { log.info("SolrDispatchFilter.init()"); boolean abortOnConfigurationError = true; CoreContainer.Initializer init = createInitializer();/** 初始化CoreContianer的内部类 */ try { /** 配置当前web应用上下文参数*/ this.pathPrefix = config.getInitParameter( "path-prefix" );/** 可以在过滤器配置文件中指定好只针对该特殊的请求进行一个过滤*/ init.setSolrConfigFilename(config.getInitParameter("solrconfig-filename"));/** 指定好solr配置文件的名字是什么 */ this.cores = init.initialize();/** 正式将CoreContainer进行一个初始化并创建实例对象然后将所有的solrCore实例放到容器中 */ abortOnConfigurationError = init.isAbortOnConfigurationError(); log.info("user.dir=" + System.getProperty("user.dir")); } catch( Throwable t ) { /** 捕获这个异常我们的过滤器将会继续工作 */ log.error( "Could not start Solr. Check solr/home property", t); SolrConfig.severeErrors.add( t ); SolrCore.log( t ); }/** 如果服务器报错可以选择性的忽略该错误 */ if( abortOnConfigurationError && SolrConfig.severeErrors.size() > 0 ) { StringWriter sw = new StringWriter(); PrintWriter out = new PrintWriter( sw ); out.println( "Severe errors in solr configuration.\n" ); out.println( "Check your log files for more detailed information on what may be wrong.\n" ); out.println( "If you want solr to continue after configuration errors, change: \n"); out.println( " <abortOnConfigurationError>false</abortOnConfigurationError>\n" ); out.println( "in "+init.getSolrConfigFilename()+"\n" ); for( Throwable t : SolrConfig.severeErrors ) { out.println( "-------------------------------------------------------------" ); t.printStackTrace( out ); } out.flush(); // Servlet containers behave slightly differently if you throw an exception during // initialization. Resin will display that error for every page, jetty prints it in // the logs, but continues normally. (We will see a 404 rather then the real error) // rather then leave the behavior undefined, lets cache the error and spit it out // for every request. abortErrorMessage = sw.toString(); //throw new ServletException( abortErrorMessage ); } log.info("SolrDispatchFilter.init() done"); } /** Method to override to change how CoreContainer initialization is performed. */ protected CoreContainer.Initializer createInitializer() { return new CoreContainer.Initializer(); } /** 在销毁这个过滤器的时候同时将释放所有的索引实例的文件流 */ public void destroy() { if (cores != null) { cores.shutdown(); cores = null; } } /** 将对特定的需要全文检索的请求进行一个过滤然后提供全文检索服务 */ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if( abortErrorMessage != null ) { ((HttpServletResponse)response).sendError( 500, abortErrorMessage ); return; } /** 下面这几个变量是solr提供全文解锁服务的上下文参数*/ if( request instanceof HttpServletRequest) { HttpServletRequest req = (HttpServletRequest)request; HttpServletResponse resp = (HttpServletResponse)response; SolrRequestHandler handler = null;/** 当前请求所对应的请求处理器*/ SolrQueryRequest solrReq = null; /** 当前的查询请求*/ SolrCore core = null; /** 当前请求资源所对应的solrCore实例*/ String corename = ""; /** 当前请求所对应的solrCore实例的名字*/ try { /** 将我们的CoreContainer容器以键值对的形式保存到当前这个请求的map集合中 */ req.setAttribute("org.apache.solr.CoreContainer", cores); String path = req.getServletPath();/**http://host_name:port/webapp_name/solrcore_name/admin/这样一段URL那么path为solrcore_name/admin/ */ if( req.getPathInfo() != null ) { // this lets you handle /update/commit when /update is a servlet path += req.getPathInfo(); }/** 如果路径是http://host_name:port/webapp_name/test/select/... 为了能够让solr知道这个请求URL有检索需要首先要把这个test/这段字符窜给截取*/ if( pathPrefix != null && path.startsWith( pathPrefix ) ) { path = path.substring( pathPrefix.length() ); } /** 根据请求参数得到管理CoreContainer这个容器的路径的名字*/ String alternate = cores.getManagementPath(); if (alternate != null && path.startsWith(alternate)) { path = path.substring(0, alternate.length()); } // unused feature ? int idx = path.indexOf( ':' ); if( idx > 0 ) { // save the portion after the ':' for a 'handler' path parameter path = path.substring( 0, idx ); } /** 检查是否需要去管理页面 */ if( path.equals( cores.getAdminPath() ) ) { handler = cores.getMultiCoreHandler(); solrReq = adminRequestParser.parse(null,path, req); handleAdminRequest(req, response, handler, solrReq); return; } else { /** 另外,我们应该从路径中找到solrCore的名字*/ idx = path.indexOf( "/", 1 ); if( idx > 1 ) { /** 通过请求参数得到solrCore实例的名字*/ corename = path.substring( 1, idx ); core = cores.getCore(corename); if (core != null) { path = path.substring( idx );/** 将路径中的solrCore名字截取掉 */ } } if (core == null) { corename = ""; core = cores.getCore(""); } } /** 使用一个有效的solrcore实例*/ if( core != null ) { final SolrConfig config = core.getSolrConfig(); /** 从solrCore的实例缓存中得到一个请求解析对象或者创建一个新的缓存起来*/ SolrRequestParsers parser = null; parser = parsers.get(config); if( parser == null ) { parser = new SolrRequestParsers(config); parsers.put(config, parser );/** 将加载这个solrCore实例所配置的所有请求解析器对象*/ } /** 如果没有设置请求处理器的类型从请求路径得到处理器*/ /** 我们已经选择了一个请求处理器*/ if( handler == null && path.length() > 1 ) { /**没有匹配空窜或者/是有效字符 */ handler = core.getRequestHandler( path );/** 没有处理器但是允许处理查询*/ if( handler == null && parser.isHandleSelect() ) { if( "/select".equals( path ) || "/select/".equals( path ) ) {/**是一个查询请求 */ solrReq = parser.parse( core, path, req );/** 得到请求查询对象 */ String qt = solrReq.getParams().get( CommonParams.QT );/** 得到查询类型也就是对应的查询请求处理器的名字 */ handler = core.getRequestHandler( qt );/** 根据查询类型得到请求查询处理器对象 */ if( handler == null ) { throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "unknown handler: "+qt);/**如果发生异常表示没有对应的请求查询处理器 */ } } } } // With a valid handler and a valid core... /** 使用一个有效的处理器和有效的solrCore实例*/ if( handler != null ) { // if not a /select, create the request if( solrReq == null ) { solrReq = parser.parse( core, path, req ); } final Method reqMethod = Method.getMethod(req.getMethod());/** 得到http请求提交的方式*/ HttpCacheHeaderUtil.setCacheControlHeader(config, resp, reqMethod); // unless we have been explicitly told not to, do cache validation // if we fail cache validation, execute the query if (config.getHttpCachingConfig().isNever304() || !HttpCacheHeaderUtil.doCacheHeaderValidation(solrReq, req, reqMethod, resp)) { SolrQueryResponse solrRsp = new SolrQueryResponse(); /* even for HEAD requests, we need to execute the handler to * ensure we don't get an error (and to make sure the correct * QueryResponseWriter is selected and we get the correct * Content-Type) *//** solr提供全文检索服务的入口*/ this.execute( req, handler, solrReq, solrRsp );/** 根据请求对象,根据请求参数中指定的处理器名字得到的处理器,根据响应对象, 以及发送请求的方式对索引库进行一个读写操作 */ HttpCacheHeaderUtil.checkHttpCachingVeto(solrRsp, resp, reqMethod); // add info to http headers //TODO: See SOLR-232 and SOLR-267. /*try { NamedList solrRspHeader = solrRsp.getResponseHeader(); for (int i=0; i<solrRspHeader.size(); i++) { ((javax.servlet.http.HttpServletResponse) response).addHeader(("Solr-" + solrRspHeader.getName(i)), String.valueOf(solrRspHeader.getVal(i))); } } catch (ClassCastException cce) { log.log(Level.WARNING, "exception adding response header log information", cce); }*/ QueryResponseWriter responseWriter = core.getQueryResponseWriter(solrReq);/** 将请求结果通过输出流写出到客户端*/ writeResponse(solrRsp, response, responseWriter, solrReq, reqMethod); } return; // we are done with a valid handler } // otherwise (we have a core), let's ensure the core is in the SolrCore request attribute so // a servlet/jsp can retrieve it else { req.setAttribute("org.apache.solr.SolrCore", core); // Modify the request so each core gets its own /admin/** 修改请求根据不同的solrcore的管理界面进行一个转发*/ if( path.startsWith( "/admin" ) ) { req.getRequestDispatcher( pathPrefix == null ? path : pathPrefix + path ).forward( request, response ); return; } } } log.debug("no handler or core retrieved for " + path + ", follow through..."); } catch (Throwable ex) { sendError( (HttpServletResponse)response, ex ); return; } finally { if( solrReq != null ) { solrReq.close(); } if (core != null) { core.close(); } } }/** 让web应用处理其他请求*/ chain.doFilter(request, response); } /** 处理需要管理corecontainer容器的请求*/ private void handleAdminRequest(HttpServletRequest req, ServletResponse response, SolrRequestHandler handler, SolrQueryRequest solrReq) throws IOException { SolrQueryResponse solrResp = new SolrQueryResponse(); final NamedList<Object> responseHeader = new SimpleOrderedMap<Object>(); solrResp.add("responseHeader", responseHeader); NamedList toLog = solrResp.getToLog(); toLog.add("webapp", req.getContextPath()); toLog.add("path", solrReq.getContext().get("path")); toLog.add("params", "{" + solrReq.getParamString() + "}"); handler.handleRequest(solrReq, solrResp); SolrCore.setResponseHeaderValues(handler, solrReq, solrResp); StringBuilder sb = new StringBuilder(); for (int i = 0; i < toLog.size(); i++) { String name = toLog.getName(i); Object val = toLog.getVal(i); sb.append(name).append("=").append(val).append(" "); } QueryResponseWriter respWriter = SolrCore.DEFAULT_RESPONSE_WRITERS.get(solrReq.getParams().get(CommonParams.WT)); if (respWriter == null) respWriter = SolrCore.DEFAULT_RESPONSE_WRITERS.get("standard"); writeResponse(solrResp, response, respWriter, solrReq, Method.getMethod(req.getMethod())); } /** 在该请求执行往filterChain.doFilter(...)的时候将会把我们的结果添加到响应对象中*/ private void writeResponse(SolrQueryResponse solrRsp, ServletResponse response, QueryResponseWriter responseWriter, SolrQueryRequest solrReq, Method reqMethod) throws IOException { if (solrRsp.getException() != null) { sendError((HttpServletResponse) response, solrRsp.getException()); } else { // Now write it out final String ct = responseWriter.getContentType(solrReq, solrRsp); // don't call setContentType on null if (null != ct) response.setContentType(ct); if (Method.HEAD != reqMethod) { if (responseWriter instanceof BinaryQueryResponseWriter) { BinaryQueryResponseWriter binWriter = (BinaryQueryResponseWriter) responseWriter; binWriter.write(response.getOutputStream(), solrReq, solrRsp); } else { String charset = ContentStreamBase.getCharsetFromContentType(ct); Writer out = (charset == null || charset.equalsIgnoreCase("UTF-8")) ? new OutputStreamWriter(response.getOutputStream(), UTF8) : new OutputStreamWriter(response.getOutputStream(), charset); out = new FastWriter(out); responseWriter.write(out, solrReq, solrRsp); out.flush(); } } //else http HEAD request, nothing to write out, waited this long just to get ContentType } } protected void execute( HttpServletRequest req, SolrRequestHandler handler, SolrQueryRequest sreq, SolrQueryResponse rsp) { // a custom filter could add more stuff to the request before passing it on. // for example: sreq.getContext().put( "HttpServletRequest", req ); // used for logging query stats in SolrCore.execute() sreq.getContext().put( "webapp", req.getContextPath() ); sreq.getCore().execute( handler, sreq, rsp ); } protected void sendError(HttpServletResponse res, Throwable ex) throws IOException { int code=500; String trace = ""; if( ex instanceof SolrException ) { code = ((SolrException)ex).code(); } // For any regular code, don't include the stack trace if( code == 500 || code < 100 ) { StringWriter sw = new StringWriter(); ex.printStackTrace(new PrintWriter(sw)); trace = "\n\n"+sw.toString(); SolrException.logOnce(log,null,ex ); // non standard codes have undefined results with various servers if( code < 100 ) { log.warn( "invalid return code: "+code ); code = 500; } } res.sendError( code, ex.getMessage() + trace ); } //--------------------------------------------------------------------- //--------------------------------------------------------------------- /** * Set the prefix for all paths. This is useful if you want to apply the * filter to something other then /*, perhaps because you are merging this * filter into a larger web application. * * For example, if web.xml specifies: * * <filter-mapping> * <filter-name>SolrRequestFilter</filter-name> * <url-pattern>/xxx/*</url-pattern> * </filter-mapping> * * Make sure to set the PathPrefix to "/xxx" either with this function * or in web.xml. * * <init-param> * <param-name>path-prefix</param-name> * <param-value>/xxx</param-value> * </init-param> * */ public void setPathPrefix(String pathPrefix) { this.pathPrefix = pathPrefix; } public String getPathPrefix() { return pathPrefix; }}