/** * 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.PrintWriter; import java.io.StringWriter; import java.io.ByteArrayInputStream; import java.util.Map; import java.util.WeakHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.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 * * @since solr 1.2 */ public class SolrDispatchFilter implements Filter { final Logger log = LoggerFactory.getLogger(SolrDispatchFilter.class); protected CoreContainer cores; protected String pathPrefix = null; // strip this from the beginning of a path protected String abortErrorMessage = null; protected String solrConfigFilename = null; protected final Map<SolrConfig, SolrRequestParsers> parsers = new WeakHashMap<SolrConfig, SolrRequestParsers>(); protected final SolrRequestParsers adminRequestParser; public SolrDispatchFilter() { try { adminRequestParser = new SolrRequestParsers(new Config(null,"solr",new ByteArrayInputStream("<root/>".getBytes()),"") ); } catch (Exception e) { //unlikely throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,e); } } public void init(FilterConfig config) throws ServletException { log.info("SolrDispatchFilter.init()"); boolean abortOnConfigurationError = true; CoreContainer.Initializer init = createInitializer(); try { // web.xml configuration this.pathPrefix = config.getInitParameter( "path-prefix" ); init.setSolrConfigFilename(config.getInitParameter("solrconfig-filename")); this.cores = init.initialize(); abortOnConfigurationError = init.isAbortOnConfigurationError(); log.info("user.dir=" + System.getProperty("user.dir")); } catch( Throwable t ) { // catch this so our filter still works log.error( "Could not start Solr. Check solr/home property", t); SolrConfig.severeErrors.add( t ); SolrCore.log( t ); } // Optionally abort if we found a sever error 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" ); 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; } if( request instanceof HttpServletRequest) { HttpServletRequest req = (HttpServletRequest)request; HttpServletResponse resp = (HttpServletResponse)response; SolrRequestHandler handler = null; SolrQueryRequest solrReq = null; SolrCore core = null; String corename = ""; try { // put the core container in request attribute req.setAttribute("org.apache.solr.CoreContainer", cores); String path = req.getServletPath(); if( req.getPathInfo() != null ) { // this lets you handle /update/commit when /update is a servlet path += req.getPathInfo(); } if( pathPrefix != null && path.startsWith( pathPrefix ) ) { path = path.substring( pathPrefix.length() ); } // check for management path 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 ); } // Check for the core admin page if( path.equals( cores.getAdminPath() ) ) { handler = cores.getMultiCoreHandler(); solrReq = adminRequestParser.parse(null,path, req); handleAdminRequest(req, response, handler, solrReq); return; } else { //otherwise, we should find a core from the path idx = path.indexOf( "/", 1 ); if( idx > 1 ) { // try to get the corename as a request parameter first corename = path.substring( 1, idx ); core = cores.getCore(corename); if (core != null) { path = path.substring( idx ); } } if (core == null) { corename = ""; core = cores.getCore(""); } } // With a valid core... if( core != null ) { final SolrConfig config = core.getSolrConfig(); // get or create/cache the parser for the core SolrRequestParsers parser = null; parser = parsers.get(config); if( parser == null ) { parser = new SolrRequestParsers(config); parsers.put(config, parser ); } // Determine the handler from the url path if not set // (we might already have selected the cores handler) if( handler == null && path.length() > 1 ) { // don't match "" or "/" as valid path handler = core.getRequestHandler( path ); // no handler yet but allowed to handle select; let's check 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... 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()); 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(); solrReq.setHttpServletRequest(req); solrRsp.setHttpServletResponse(resp); /* 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) */ 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 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(); } } } // Otherwise let the webapp handle the request chain.doFilter(request, response); } 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())); } 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 response.setContentType(responseWriter.getContentType(solrReq, solrRsp)); if (Method.HEAD != reqMethod) { if (responseWriter instanceof BinaryQueryResponseWriter) { BinaryQueryResponseWriter binWriter = (BinaryQueryResponseWriter) responseWriter; binWriter.write(response.getOutputStream(), solrReq, solrRsp); } else { PrintWriter out = response.getWriter(); responseWriter.write(out, solrReq, solrRsp); } } //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; } }