Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SOLR-15590 - Context Listener to start CoreContainer #397

Closed
wants to merge 10 commits into from
49 changes: 49 additions & 0 deletions solr/core/src/java/org/apache/solr/servlet/AdminServlet.java
@@ -0,0 +1,49 @@
package org.apache.solr.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Pattern;

import static org.apache.solr.servlet.ServletUtils.closeShield;
import static org.apache.solr.servlet.ServletUtils.configExcludes;
import static org.apache.solr.servlet.ServletUtils.excludedPath;

@WebServlet
public class AdminServlet extends HttpServlet implements PathExcluder{
private ArrayList<Pattern> excludes;

@Override
public void init() throws ServletException {
configExcludes(this, getInitParameter("excludePatterns"));
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req = closeShield(req, false);
resp = closeShield(resp, false);
if (excludedPath(excludes,req,resp,null)) {
return;
}
super.doGet(req, resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req = closeShield(req, false);
resp = closeShield(resp, false);
if (excludedPath(excludes,req,resp,null)) {
return;
}
super.doPost(req, resp);
}

@Override
public void setExcludePatterns(ArrayList<Pattern> excludePatterns) {
this.excludes = excludePatterns;
}
}
Expand Up @@ -48,8 +48,8 @@ public void doGet(HttpServletRequest _request, HttpServletResponse _response) th
"ENABLED in bin/solr.in.sh or solr.in.cmd.");
return;
}
HttpServletRequest request = SolrDispatchFilter.closeShield(_request, false);
HttpServletResponse response = SolrDispatchFilter.closeShield(_response, false);
HttpServletRequest request = ServletUtils.closeShield(_request, false);
HttpServletResponse response = ServletUtils.closeShield(_response, false);


response.addHeader("X-Frame-Options", "DENY"); // security: SOLR-7966 - avoid clickjacking for admin interface
Expand Down
8 changes: 8 additions & 0 deletions solr/core/src/java/org/apache/solr/servlet/PathExcluder.java
@@ -0,0 +1,8 @@
package org.apache.solr.servlet;

import java.util.ArrayList;
import java.util.regex.Pattern;

public interface PathExcluder {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need this? And I wonder if "excludePatterns" is a feature that like one person/company uses.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a valid question, but probably deserves it's own issue/discussion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did notice this: which seems to say that this is relied upon by the admin UI. https://issues.apache.org/jira/browse/SOLR-11228

void setExcludePatterns(ArrayList<Pattern> excludePatterns);
}
171 changes: 171 additions & 0 deletions solr/core/src/java/org/apache/solr/servlet/ServletUtils.java
Expand Up @@ -17,12 +17,33 @@

package org.apache.solr.servlet;

import javax.servlet.FilterChain;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Various Util methods for interaction on servlet level, i.e. HttpServletRequest
*/
public abstract class ServletUtils {
static String CLOSE_STREAM_MSG = "Attempted close of http request or response stream - in general you should not do this, "
+ "you may spoil connection reuse and possibly disrupt a client. If you must close without actually needing to close, "
+ "use a CloseShield*Stream. Closing or flushing the response stream commits the response and prevents us from modifying it. "
+ "Closing the request stream prevents us from guaranteeing ourselves that streams are fully read for proper connection reuse."
+ "Let the container manage the lifecycle of these streams when possible.";

private ServletUtils() { /* only static methods in this class */ }

/**
Expand All @@ -34,4 +55,154 @@ private ServletUtils() { /* only static methods in this class */ }
public static String getPathAfterContext(HttpServletRequest request) {
return request.getServletPath() + (request.getPathInfo() != null ? request.getPathInfo() : "");
}

/**
* Wrap the request's input stream with a close shield. If this is a
* retry, we will assume that the stream has already been wrapped and do nothing.
*
* Only the container should ever actually close the servlet output stream.
*
* @param request The request to wrap.
* @param retry If this is an original request or a retry.
* @return A request object with an {@link InputStream} that will ignore calls to close.
*/
public static HttpServletRequest closeShield(HttpServletRequest request, boolean retry) {
if (!retry) {
return new HttpServletRequestWrapper(request) {

@Override
public ServletInputStream getInputStream() throws IOException {

return new ServletInputStreamWrapper(super.getInputStream()) {
@Override
public void close() {
// even though we skip closes, we let local tests know not to close so that a full understanding can take
// place
assert !Thread.currentThread().getStackTrace()[2].getClassName().matches(
"org\\.apache\\.(?:solr|lucene).*") : CLOSE_STREAM_MSG;
this.stream = ClosedServletInputStream.CLOSED_SERVLET_INPUT_STREAM;
}
};

}
};
} else {
return request;
}
}

/**
* Wrap the response's output stream with a close shield. If this is a
* retry, we will assume that the stream has already been wrapped and do nothing.
*
* Only the container should ever actually close the servlet request stream.
*
* @param response The response to wrap.
* @param retry If this response corresponds to an original request or a retry.
* @return A response object with an {@link OutputStream} that will ignore calls to close.
*/
public static HttpServletResponse closeShield(HttpServletResponse response, boolean retry) {
if (!retry) {
return new HttpServletResponseWrapper(response) {

@Override
public ServletOutputStream getOutputStream() throws IOException {

return new ServletOutputStreamWrapper(super.getOutputStream()) {
@Override
public void close() {
// even though we skip closes, we let local tests know not to close so that a full understanding can take
// place
assert !Thread.currentThread().getStackTrace()[2].getClassName().matches(
"org\\.apache\\.(?:solr|lucene).*") : CLOSE_STREAM_MSG;
stream = ClosedServletOutputStream.CLOSED_SERVLET_OUTPUT_STREAM;
}
};
}

};
} else {
return response;
}
}

static boolean excludedPath(ArrayList<Pattern> excludePatterns, HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
gus-asf marked this conversation as resolved.
Show resolved Hide resolved
String requestPath = getPathAfterContext(request);
// No need to even create the HttpSolrCall object if this path is excluded.
if (excludePatterns != null) {
for (Pattern p : excludePatterns) {
Matcher matcher = p.matcher(requestPath);
if (matcher.lookingAt()) {
if (chain != null) {
chain.doFilter(request, response);
}
return true;
}
}
}
return false;
}

static boolean excludedPath(ArrayList<Pattern> excludePatterns, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
gus-asf marked this conversation as resolved.
Show resolved Hide resolved
return excludedPath(excludePatterns,request,response, null);
}

static void configExcludes(PathExcluder excluder, String patternConfig) {
if(patternConfig != null) {
String[] excludeArray = patternConfig.split(",");
ArrayList<Pattern> patterns = new ArrayList<>();
excluder.setExcludePatterns(patterns);
for (String element : excludeArray) {
patterns.add(Pattern.compile(element));
}
}
}

public static class ClosedServletInputStream extends ServletInputStream {

public static final ClosedServletInputStream CLOSED_SERVLET_INPUT_STREAM = new ClosedServletInputStream();

@Override
public int read() {
return -1;
}

@Override
public boolean isFinished() {
return false;
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setReadListener(ReadListener arg0) {}
}

public static class ClosedServletOutputStream extends ServletOutputStream {

public static final ClosedServletOutputStream CLOSED_SERVLET_OUTPUT_STREAM = new ClosedServletOutputStream();

@Override
public void write(final int b) throws IOException {
throw new IOException("write(" + b + ") failed: stream is closed");
}

@Override
public void flush() throws IOException {
throw new IOException("flush() failed: stream is closed");
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setWriteListener(WriteListener arg0) {
throw new RuntimeException("setWriteListener() failed: stream is closed");
}
}
}