2
* Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements. See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
7
* the License. You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
18
package org.apache.solr.servlet;
20
import java.io.IOException;
21
import java.io.Writer;
22
import java.io.PrintWriter;
23
import java.io.StringWriter;
24
import java.io.OutputStreamWriter;
25
import java.io.ByteArrayInputStream;
26
import java.nio.charset.Charset;
28
import java.util.WeakHashMap;
29
import org.slf4j.Logger;
30
import org.slf4j.LoggerFactory;
31
import org.xml.sax.InputSource;
33
import javax.servlet.Filter;
34
import javax.servlet.FilterChain;
35
import javax.servlet.FilterConfig;
36
import javax.servlet.ServletException;
37
import javax.servlet.ServletRequest;
38
import javax.servlet.ServletResponse;
39
import javax.servlet.http.HttpServletRequest;
40
import javax.servlet.http.HttpServletResponse;
42
import org.apache.solr.common.SolrException;
43
import org.apache.solr.common.util.NamedList;
44
import org.apache.solr.common.util.SimpleOrderedMap;
45
import org.apache.solr.common.params.CommonParams;
46
import org.apache.solr.common.util.FastWriter;
47
import org.apache.solr.common.util.ContentStreamBase;
48
import org.apache.solr.core.*;
49
import org.apache.solr.request.*;
50
import org.apache.solr.response.BinaryQueryResponseWriter;
51
import org.apache.solr.response.QueryResponseWriter;
52
import org.apache.solr.response.SolrQueryResponse;
53
import org.apache.solr.servlet.cache.HttpCacheHeaderUtil;
54
import org.apache.solr.servlet.cache.Method;
57
* This filter looks at the incoming URL maps them to handlers defined in solrconfig.xml
61
public class SolrDispatchFilter implements Filter
63
final Logger log = LoggerFactory.getLogger(SolrDispatchFilter.class);
65
protected CoreContainer cores;
66
protected String pathPrefix = null; // strip this from the beginning of a path
67
protected String abortErrorMessage = null;
68
protected String solrConfigFilename = null;
69
protected final Map<SolrConfig, SolrRequestParsers> parsers = new WeakHashMap<SolrConfig, SolrRequestParsers>();
70
protected final SolrRequestParsers adminRequestParser;
72
private static final Charset UTF8 = Charset.forName("UTF-8");
74
public SolrDispatchFilter() {
76
adminRequestParser = new SolrRequestParsers(new Config(null,"solr",new InputSource(new ByteArrayInputStream("<root/>".getBytes("UTF-8"))),"") );
77
} catch (Exception e) {
79
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,e);
83
public void init(FilterConfig config) throws ServletException
85
log.info("SolrDispatchFilter.init()");
87
boolean abortOnConfigurationError = true;
88
CoreContainer.Initializer init = createInitializer();
90
// web.xml configuration
91
this.pathPrefix = config.getInitParameter( "path-prefix" );
92
init.setSolrConfigFilename(config.getInitParameter("solrconfig-filename"));
94
this.cores = init.initialize();
95
abortOnConfigurationError = init.isAbortOnConfigurationError();
96
log.info("user.dir=" + System.getProperty("user.dir"));
98
catch( Throwable t ) {
99
// catch this so our filter still works
100
log.error( "Could not start Solr. Check solr/home property", t);
101
SolrConfig.severeErrors.add( t );
105
// Optionally abort if we found a sever error
106
if( abortOnConfigurationError && SolrConfig.severeErrors.size() > 0 ) {
107
StringWriter sw = new StringWriter();
108
PrintWriter out = new PrintWriter( sw );
109
out.println( "Severe errors in solr configuration.\n" );
110
out.println( "Check your log files for more detailed information on what may be wrong.\n" );
111
out.println( "If you want solr to continue after configuration errors, change: \n");
112
out.println( " <abortOnConfigurationError>false</abortOnConfigurationError>\n" );
113
out.println( "in "+init.getSolrConfigFilename()+"\n" );
115
for( Throwable t : SolrConfig.severeErrors ) {
116
out.println( "-------------------------------------------------------------" );
117
t.printStackTrace( out );
121
// Servlet containers behave slightly differently if you throw an exception during
122
// initialization. Resin will display that error for every page, jetty prints it in
123
// the logs, but continues normally. (We will see a 404 rather then the real error)
124
// rather then leave the behavior undefined, lets cache the error and spit it out
125
// for every request.
126
abortErrorMessage = sw.toString();
127
//throw new ServletException( abortErrorMessage );
130
log.info("SolrDispatchFilter.init() done");
133
/** Method to override to change how CoreContainer initialization is performed. */
134
protected CoreContainer.Initializer createInitializer() {
135
return new CoreContainer.Initializer();
138
public void destroy() {
145
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
146
if( abortErrorMessage != null ) {
147
((HttpServletResponse)response).sendError( 500, abortErrorMessage );
151
if( request instanceof HttpServletRequest) {
152
HttpServletRequest req = (HttpServletRequest)request;
153
HttpServletResponse resp = (HttpServletResponse)response;
154
SolrRequestHandler handler = null;
155
SolrQueryRequest solrReq = null;
156
SolrCore core = null;
157
String corename = "";
159
// put the core container in request attribute
160
req.setAttribute("org.apache.solr.CoreContainer", cores);
161
String path = req.getServletPath();
162
if( req.getPathInfo() != null ) {
163
// this lets you handle /update/commit when /update is a servlet
164
path += req.getPathInfo();
166
if( pathPrefix != null && path.startsWith( pathPrefix ) ) {
167
path = path.substring( pathPrefix.length() );
169
// check for management path
170
String alternate = cores.getManagementPath();
171
if (alternate != null && path.startsWith(alternate)) {
172
path = path.substring(0, alternate.length());
175
int idx = path.indexOf( ':' );
177
// save the portion after the ':' for a 'handler' path parameter
178
path = path.substring( 0, idx );
181
// Check for the core admin page
182
if( path.equals( cores.getAdminPath() ) ) {
183
handler = cores.getMultiCoreHandler();
184
solrReq = adminRequestParser.parse(null,path, req);
185
handleAdminRequest(req, response, handler, solrReq);
189
//otherwise, we should find a core from the path
190
idx = path.indexOf( "/", 1 );
192
// try to get the corename as a request parameter first
193
corename = path.substring( 1, idx );
194
core = cores.getCore(corename);
196
path = path.substring( idx );
201
core = cores.getCore("");
205
// With a valid core...
207
final SolrConfig config = core.getSolrConfig();
208
// get or create/cache the parser for the core
209
SolrRequestParsers parser = null;
210
parser = parsers.get(config);
211
if( parser == null ) {
212
parser = new SolrRequestParsers(config);
213
parsers.put(config, parser );
216
// Determine the handler from the url path if not set
217
// (we might already have selected the cores handler)
218
if( handler == null && path.length() > 1 ) { // don't match "" or "/" as valid path
219
handler = core.getRequestHandler( path );
220
// no handler yet but allowed to handle select; let's check
221
if( handler == null && parser.isHandleSelect() ) {
222
if( "/select".equals( path ) || "/select/".equals( path ) ) {
223
solrReq = parser.parse( core, path, req );
224
String qt = solrReq.getParams().get( CommonParams.QT );
225
handler = core.getRequestHandler( qt );
226
if( handler == null ) {
227
throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "unknown handler: "+qt);
233
// With a valid handler and a valid core...
234
if( handler != null ) {
235
// if not a /select, create the request
236
if( solrReq == null ) {
237
solrReq = parser.parse( core, path, req );
240
final Method reqMethod = Method.getMethod(req.getMethod());
241
HttpCacheHeaderUtil.setCacheControlHeader(config, resp, reqMethod);
242
// unless we have been explicitly told not to, do cache validation
243
// if we fail cache validation, execute the query
244
if (config.getHttpCachingConfig().isNever304() ||
245
!HttpCacheHeaderUtil.doCacheHeaderValidation(solrReq, req, reqMethod, resp)) {
246
SolrQueryResponse solrRsp = new SolrQueryResponse();
247
/* even for HEAD requests, we need to execute the handler to
248
* ensure we don't get an error (and to make sure the correct
249
* QueryResponseWriter is selected and we get the correct
252
this.execute( req, handler, solrReq, solrRsp );
253
HttpCacheHeaderUtil.checkHttpCachingVeto(solrRsp, resp, reqMethod);
254
// add info to http headers
255
//TODO: See SOLR-232 and SOLR-267.
257
NamedList solrRspHeader = solrRsp.getResponseHeader();
258
for (int i=0; i<solrRspHeader.size(); i++) {
259
((javax.servlet.http.HttpServletResponse) response).addHeader(("Solr-" + solrRspHeader.getName(i)), String.valueOf(solrRspHeader.getVal(i)));
261
} catch (ClassCastException cce) {
262
log.log(Level.WARNING, "exception adding response header log information", cce);
264
QueryResponseWriter responseWriter = core.getQueryResponseWriter(solrReq);
265
writeResponse(solrRsp, response, responseWriter, solrReq, reqMethod);
267
return; // we are done with a valid handler
269
// otherwise (we have a core), let's ensure the core is in the SolrCore request attribute so
270
// a servlet/jsp can retrieve it
272
req.setAttribute("org.apache.solr.SolrCore", core);
273
// Modify the request so each core gets its own /admin
274
if( path.startsWith( "/admin" ) ) {
275
req.getRequestDispatcher( pathPrefix == null ? path : pathPrefix + path ).forward( request, response );
280
log.debug("no handler or core retrieved for " + path + ", follow through...");
282
catch (Throwable ex) {
283
sendError( (HttpServletResponse)response, ex );
287
if( solrReq != null ) {
296
// Otherwise let the webapp handle the request
297
chain.doFilter(request, response);
300
private void handleAdminRequest(HttpServletRequest req, ServletResponse response, SolrRequestHandler handler,
301
SolrQueryRequest solrReq) throws IOException {
302
SolrQueryResponse solrResp = new SolrQueryResponse();
303
final NamedList<Object> responseHeader = new SimpleOrderedMap<Object>();
304
solrResp.add("responseHeader", responseHeader);
305
NamedList toLog = solrResp.getToLog();
306
toLog.add("webapp", req.getContextPath());
307
toLog.add("path", solrReq.getContext().get("path"));
308
toLog.add("params", "{" + solrReq.getParamString() + "}");
309
handler.handleRequest(solrReq, solrResp);
310
SolrCore.setResponseHeaderValues(handler, solrReq, solrResp);
311
StringBuilder sb = new StringBuilder();
312
for (int i = 0; i < toLog.size(); i++) {
313
String name = toLog.getName(i);
314
Object val = toLog.getVal(i);
315
sb.append(name).append("=").append(val).append(" ");
317
QueryResponseWriter respWriter = SolrCore.DEFAULT_RESPONSE_WRITERS.get(solrReq.getParams().get(CommonParams.WT));
318
if (respWriter == null) respWriter = SolrCore.DEFAULT_RESPONSE_WRITERS.get("standard");
319
writeResponse(solrResp, response, respWriter, solrReq, Method.getMethod(req.getMethod()));
322
private void writeResponse(SolrQueryResponse solrRsp, ServletResponse response,
323
QueryResponseWriter responseWriter, SolrQueryRequest solrReq, Method reqMethod)
325
if (solrRsp.getException() != null) {
326
sendError((HttpServletResponse) response, solrRsp.getException());
329
final String ct = responseWriter.getContentType(solrReq, solrRsp);
330
// don't call setContentType on null
331
if (null != ct) response.setContentType(ct);
333
if (Method.HEAD != reqMethod) {
334
if (responseWriter instanceof BinaryQueryResponseWriter) {
335
BinaryQueryResponseWriter binWriter = (BinaryQueryResponseWriter) responseWriter;
336
binWriter.write(response.getOutputStream(), solrReq, solrRsp);
338
String charset = ContentStreamBase.getCharsetFromContentType(ct);
339
Writer out = (charset == null || charset.equalsIgnoreCase("UTF-8"))
340
? new OutputStreamWriter(response.getOutputStream(), UTF8)
341
: new OutputStreamWriter(response.getOutputStream(), charset);
342
out = new FastWriter(out);
343
responseWriter.write(out, solrReq, solrRsp);
347
//else http HEAD request, nothing to write out, waited this long just to get ContentType
351
protected void execute( HttpServletRequest req, SolrRequestHandler handler, SolrQueryRequest sreq, SolrQueryResponse rsp) {
352
// a custom filter could add more stuff to the request before passing it on.
353
// for example: sreq.getContext().put( "HttpServletRequest", req );
354
// used for logging query stats in SolrCore.execute()
355
sreq.getContext().put( "webapp", req.getContextPath() );
356
sreq.getCore().execute( handler, sreq, rsp );
359
protected void sendError(HttpServletResponse res, Throwable ex) throws IOException {
362
if( ex instanceof SolrException ) {
363
code = ((SolrException)ex).code();
366
// For any regular code, don't include the stack trace
367
if( code == 500 || code < 100 ) {
368
StringWriter sw = new StringWriter();
369
ex.printStackTrace(new PrintWriter(sw));
370
trace = "\n\n"+sw.toString();
372
SolrException.logOnce(log,null,ex );
374
// non standard codes have undefined results with various servers
376
log.warn( "invalid return code: "+code );
380
res.sendError( code, ex.getMessage() + trace );
383
//---------------------------------------------------------------------
384
//---------------------------------------------------------------------
387
* Set the prefix for all paths. This is useful if you want to apply the
388
* filter to something other then /*, perhaps because you are merging this
389
* filter into a larger web application.
391
* For example, if web.xml specifies:
394
* <filter-name>SolrRequestFilter</filter-name>
395
* <url-pattern>/xxx/*</url-pattern>
398
* Make sure to set the PathPrefix to "/xxx" either with this function
402
* <param-name>path-prefix</param-name>
403
* <param-value>/xxx</param-value>
407
public void setPathPrefix(String pathPrefix) {
408
this.pathPrefix = pathPrefix;
411
public String getPathPrefix() {