| /* |
| * 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 io.opentracing.Span; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.http.HttpStatus; |
| import org.apache.solr.api.ApiBag; |
| import org.apache.solr.client.solrj.impl.CloudSolrClient; |
| import org.apache.solr.common.SolrException; |
| import org.apache.solr.common.SolrException.ErrorCode; |
| import org.apache.solr.common.annotation.SolrThreadSafe; |
| import org.apache.solr.common.cloud.Aliases; |
| import org.apache.solr.common.cloud.ClusterState; |
| import org.apache.solr.common.cloud.DocCollection; |
| import org.apache.solr.common.cloud.Replica; |
| import org.apache.solr.common.cloud.Slice; |
| import org.apache.solr.common.cloud.ZkStateReader; |
| import org.apache.solr.common.params.CommonParams; |
| import org.apache.solr.common.params.MapSolrParams; |
| import org.apache.solr.common.params.ModifiableSolrParams; |
| import org.apache.solr.common.params.QoSParams; |
| import org.apache.solr.common.params.SolrParams; |
| import org.apache.solr.common.util.CommandOperation; |
| import org.apache.solr.common.util.ContentStream; |
| import org.apache.solr.common.util.IOUtils; |
| import org.apache.solr.common.util.JsonSchemaValidator; |
| import org.apache.solr.common.util.NamedList; |
| import org.apache.solr.common.util.SimpleOrderedMap; |
| import org.apache.solr.common.util.StrUtils; |
| import org.apache.solr.common.util.TimeSource; |
| import org.apache.solr.common.util.Utils; |
| import org.apache.solr.common.util.ValidatingJsonMap; |
| import org.apache.solr.core.CoreContainer; |
| import org.apache.solr.core.SolrConfig; |
| import org.apache.solr.core.SolrCore; |
| import org.apache.solr.handler.ContentStreamHandlerBase; |
| import org.apache.solr.handler.admin.PrepRecoveryOp; |
| import org.apache.solr.logging.MDCLoggingContext; |
| import org.apache.solr.request.LocalSolrQueryRequest; |
| import org.apache.solr.request.SolrQueryRequest; |
| import org.apache.solr.request.SolrQueryRequestBase; |
| import org.apache.solr.request.SolrRequestHandler; |
| import org.apache.solr.request.SolrRequestInfo; |
| import org.apache.solr.response.QueryResponseWriter; |
| import org.apache.solr.response.QueryResponseWriterUtil; |
| import org.apache.solr.response.SolrQueryResponse; |
| import org.apache.solr.security.AuditEvent; |
| import org.apache.solr.security.AuditEvent.EventType; |
| import org.apache.solr.security.AuthenticationPlugin; |
| import org.apache.solr.security.AuthorizationContext; |
| import org.apache.solr.security.AuthorizationContext.CollectionRequest; |
| import org.apache.solr.security.AuthorizationContext.RequestType; |
| import org.apache.solr.security.AuthorizationResponse; |
| import org.apache.solr.security.PublicKeyHandler; |
| import org.apache.solr.servlet.SolrDispatchFilter.Action; |
| import org.apache.solr.servlet.cache.HttpCacheHeaderUtil; |
| import org.apache.solr.servlet.cache.Method; |
| import org.apache.solr.update.processor.DistributingUpdateProcessorFactory; |
| import org.apache.solr.util.RTimerTree; |
| import org.apache.solr.util.TimeOut; |
| import org.apache.solr.util.tracing.GlobalTracer; |
| import org.apache.zookeeper.KeeperException; |
| import org.eclipse.jetty.client.api.Request; |
| import org.eclipse.jetty.client.api.Response; |
| import org.eclipse.jetty.client.api.Result; |
| import org.eclipse.jetty.client.util.InputStreamContentProvider; |
| import org.eclipse.jetty.client.util.InputStreamResponseListener; |
| import org.eclipse.jetty.http.HttpField; |
| import org.eclipse.jetty.http.HttpHeader; |
| import org.eclipse.jetty.http.HttpVersion; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP; |
| import static org.apache.solr.common.cloud.ZkStateReader.REPLICATION_FACTOR; |
| import static org.apache.solr.common.params.CollectionAdminParams.SYSTEM_COLL; |
| import static org.apache.solr.common.params.CollectionParams.CollectionAction.CREATE; |
| import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETE; |
| import static org.apache.solr.common.params.CollectionParams.CollectionAction.RELOAD; |
| import static org.apache.solr.common.params.CommonParams.NAME; |
| import static org.apache.solr.common.params.CoreAdminParams.ACTION; |
| import static org.apache.solr.servlet.SolrDispatchFilter.Action.ADMIN; |
| import static org.apache.solr.servlet.SolrDispatchFilter.Action.FORWARD; |
| import static org.apache.solr.servlet.SolrDispatchFilter.Action.PASSTHROUGH; |
| import static org.apache.solr.servlet.SolrDispatchFilter.Action.PROCESS; |
| import static org.apache.solr.servlet.SolrDispatchFilter.Action.REMOTEQUERY; |
| import static org.apache.solr.servlet.SolrDispatchFilter.Action.RETRY; |
| import static org.apache.solr.servlet.SolrDispatchFilter.Action.RETURN; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| import java.io.EOFException; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.UnsupportedEncodingException; |
| import java.lang.invoke.MethodHandles; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.security.Principal; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Random; |
| import java.util.Set; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| /** |
| * This class represents a call made to Solr |
| **/ |
| @SolrThreadSafe |
| public class HttpSolrCall { |
| private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| |
| public static final String ORIGINAL_USER_PRINCIPAL_HEADER = "originalUserPrincipal"; |
| |
| public static final Random random; |
| static { |
| // We try to make things reproducible in the context of our tests by initializing the random instance |
| // based on the current seed |
| String seed = System.getProperty("tests.seed"); |
| if (seed == null) { |
| random = new Random(); |
| } else { |
| random = new Random(seed.hashCode()); |
| } |
| } |
| |
| private final boolean preserveHost = false; |
| protected final SolrDispatchFilter solrDispatchFilter; |
| protected final CoreContainer cores; |
| protected final HttpServletRequest req; |
| protected final HttpServletResponse response; |
| protected final boolean retry; |
| protected volatile SolrCore core = null; |
| protected SolrQueryRequest solrReq = null; |
| protected SolrRequestHandler handler = null; |
| protected final SolrParams queryParams; |
| protected volatile String path; |
| protected volatile Action action; |
| protected volatile String coreUrl; |
| protected SolrConfig config; |
| protected Map<String, Integer> invalidStates; |
| |
| //The states of client that is invalid in this request |
| protected String origCorename; // What's in the URL path; might reference a collection/alias or a Solr core name |
| protected List<String> collectionsList; // The list of SolrCloud collections if in SolrCloud (usually 1) |
| |
| public RequestType getRequestType() { |
| return requestType; |
| } |
| |
| protected RequestType requestType; |
| |
| public HttpSolrCall(SolrDispatchFilter solrDispatchFilter, CoreContainer cores, |
| HttpServletRequest request, HttpServletResponse response, boolean retry) { |
| this.solrDispatchFilter = solrDispatchFilter; |
| this.cores = cores; |
| this.req = request; |
| this.response = response; |
| this.retry = retry; |
| this.requestType = RequestType.UNKNOWN; |
| req.setAttribute(HttpSolrCall.class.getName(), this); |
| queryParams = SolrRequestParsers.parseQueryString(req.getQueryString()); |
| // set a request timer which can be reused by requests if needed |
| req.setAttribute(SolrRequestParsers.REQUEST_TIMER_SERVLET_ATTRIBUTE, new RTimerTree()); |
| // put the core container in request attribute |
| req.setAttribute("org.apache.solr.CoreContainer", cores); |
| path = ServletUtils.getPathAfterContext(req); |
| if (log.isTraceEnabled()) log.trace("Path is parsed as {}", path); |
| } |
| |
| public String getPath() { |
| return path; |
| } |
| |
| public HttpServletRequest getReq() { |
| return req; |
| } |
| |
| public SolrParams getQueryParams() { |
| return queryParams; |
| } |
| |
| protected Aliases getAliases() { |
| return cores.isZooKeeperAware() ? cores.getZkController().getZkStateReader().getAliases() : Aliases.EMPTY; |
| } |
| |
| /** The collection(s) referenced in this request. Populated in {@link #init()}. Not null. */ |
| public List<String> getCollectionsList() { |
| return collectionsList != null ? collectionsList : Collections.emptyList(); |
| } |
| |
| protected void init() throws Exception { |
| // check for management path |
| |
| if (!cores.isZooKeeperAware()) { |
| String alternate = cores.getManagementPath(); |
| if (alternate != null && path.startsWith(alternate)) { |
| path = path.substring(0, alternate.length()); |
| } |
| } |
| |
| if (log.isTraceEnabled()) log.trace("Full path {} Path is parsed as {} managment path is {}", req.getRequestURI() + req.getQueryString(), path); |
| // Check for container handlers |
| |
| handler = cores.getRequestHandler(path); |
| if (log.isTraceEnabled()) log.trace("Check for handler {} returned {} handlers={}", path, handler, cores.getRequestHandlers().keySet()); |
| if (handler != null) { |
| solrReq = SolrRequestParsers.DEFAULT.parse(null, path, req); |
| solrReq.getContext().put(CoreContainer.class.getName(), cores); |
| requestType = RequestType.ADMIN; |
| action = ADMIN; |
| return; |
| } |
| |
| // int idx = path.lastIndexOf('/'); |
| // if (idx > 0) { |
| // // save the portion after the ':' for a 'handler' path parameter |
| // path = path.substring(0, idx); |
| // } |
| |
| // Parse a core or collection name from the path and attempt to see if it's a core name |
| int idx = path.indexOf("/", 0); |
| int idx2 = -1; |
| if (idx > -1) { |
| |
| idx2 = path.indexOf('/', 1); |
| if (idx2 > 0) { |
| // save the portion after the ':' for a 'handler' path parameter |
| origCorename = path.substring(1, idx2); |
| } else { |
| origCorename = path.substring(1, path.length()); |
| } |
| |
| // Try to resolve a Solr core name |
| core = cores.getCore(origCorename); |
| |
| if (core == null && cores.isCoreLoading(origCorename)) { |
| log.debug("core is loading, will wait a bit"); |
| cores.waitForLoadingCore(origCorename, 5000); |
| core = cores.getCore(origCorename); |
| } |
| |
| if (core == null && log.isDebugEnabled()) { |
| log.debug("tried to get core by name {} got {}, existing cores {} loading={} found={}", origCorename, core, cores.getAllCoreNames(), cores.getLoadedCoreNames(), core != null); |
| } |
| |
| if (core != null) { |
| if (idx2 > 0) { |
| path = path.substring(idx2); |
| } |
| if (log.isDebugEnabled()) log.debug("Path is parsed as {}", path); |
| } else { |
| if (!cores.isZooKeeperAware()) { |
| core = cores.getCore(""); |
| } |
| } |
| } |
| |
| if (core == null && cores.isZooKeeperAware()) { |
| // init collectionList (usually one name but not when there are aliases) |
| String def = core != null ? core.getCoreDescriptor().getCollectionName() : origCorename; |
| collectionsList = resolveCollectionListOrAlias(queryParams.get(COLLECTION_PROP, def)); // &collection= takes precedence |
| |
| if (core == null) { |
| // lookup core from collection, or route away if need to |
| String collectionName = collectionsList.isEmpty() ? null : collectionsList.get(0); // route to 1st |
| //TODO try the other collections if can't find a local replica of the first? (and do to V2HttpSolrCall) |
| |
| boolean isPreferLeader = (path.endsWith("/update") || path.contains("/update/")); |
| |
| if (SYSTEM_COLL.equals(collectionName)) { |
| autoCreateSystemColl(collectionName); |
| } |
| |
| core = getCoreByCollection(collectionName, isPreferLeader); // find a local replica/core for the collection |
| if (core != null) { |
| if (idx2 > 0) { |
| path = path.substring(idx2); |
| } |
| if (log.isDebugEnabled()) log.debug("Path is parsed as {}", path); |
| } else { |
| // if we couldn't find it locally, look on other nodes |
| if (log.isDebugEnabled()) log.debug("check remote path extraction {} {}", collectionName, origCorename); |
| |
| extractRemotePath(origCorename); |
| |
| if (action == REMOTEQUERY) { |
| if (idx2 > 0) { |
| path = path.substring(idx2); |
| } |
| if (log.isDebugEnabled()) log.debug("Path is parsed as {}", path); |
| return; |
| } else { |
| extractRemotePath(collectionName); |
| if (action == REMOTEQUERY) { |
| if (idx2 > 0) { |
| path = path.substring(idx2); |
| } |
| if (log.isDebugEnabled()) log.debug("Path is parsed as {}", path); |
| return; |
| } |
| } |
| } |
| //core is not available locally or remotely |
| |
| if (action != null) return; |
| } |
| } |
| |
| // With a valid core... |
| if (core != null) { |
| config = core.getSolrConfig(); |
| // get or create/cache the parser for the core |
| SolrRequestParsers parser = config.getRequestParsers(); |
| |
| // Determine the handler from the url path if not set |
| // (we might already have selected the cores handler) |
| extractHandlerFromURLPath(parser); |
| if (action != null) return; |
| |
| // 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); |
| } |
| |
| invalidStates = checkStateVersionsAreValid(getCollectionsList(), queryParams.get(CloudSolrClient.STATE_VERSION)); |
| |
| addCollectionParamIfNeeded(getCollectionsList()); |
| |
| action = PROCESS; |
| return; // we are done with a valid handler |
| } else { |
| if (req.getMethod().equals("HEAD")) { |
| action = RETURN; |
| return; |
| } |
| } |
| } |
| log.debug("no handler or core retrieved for {}, follow through...", path); |
| |
| action = PASSTHROUGH; |
| } |
| |
| protected void autoCreateSystemColl(String corename) throws Exception { |
| if (core == null && |
| SYSTEM_COLL.equals(corename) && |
| "POST".equals(req.getMethod()) && |
| !cores.getZkController().getClusterState().hasCollection(SYSTEM_COLL)) { |
| log.info("Going to auto-create {} collection", SYSTEM_COLL); |
| SolrQueryResponse rsp = new SolrQueryResponse(); |
| String repFactor = String.valueOf(Math.min(3, cores.getZkController().getZkStateReader().getLiveNodes().size())); |
| cores.getCollectionsHandler().handleRequestBody(new LocalSolrQueryRequest(null, |
| new ModifiableSolrParams() |
| .add(ACTION, CREATE.toString()) |
| .add( NAME, SYSTEM_COLL) |
| .add(REPLICATION_FACTOR, repFactor)), rsp); |
| if (rsp.getValues().get("success") == null) { |
| throw new SolrException(ErrorCode.SERVER_ERROR, "Could not auto-create " + SYSTEM_COLL + " collection: "+ Utils.toJSONString(rsp.getValues())); |
| } |
| TimeOut timeOut = new TimeOut(1, TimeUnit.SECONDS, TimeSource.NANO_TIME); |
| for (; ; ) { |
| if (cores.getZkController().getClusterState().getCollectionOrNull(SYSTEM_COLL) != null) { |
| break; |
| } else { |
| if (timeOut.hasTimedOut()) { |
| throw new SolrException(ErrorCode.SERVER_ERROR, "Could not find " + SYSTEM_COLL + " collection even after 1 second"); |
| } |
| timeOut.sleep(50); |
| } |
| } |
| |
| action = RETRY; |
| } |
| } |
| |
| /** |
| * Resolves the parameter as a potential comma delimited list of collections, and resolves aliases too. |
| * One level of aliases pointing to another alias is supported. |
| * De-duplicates and retains the order. |
| * {@link #getCollectionsList()} |
| */ |
| protected List<String> resolveCollectionListOrAlias(String collectionStr) { |
| if (collectionStr == null || collectionStr.trim().isEmpty()) { |
| return Collections.emptyList(); |
| } |
| List<String> result = null; |
| LinkedHashSet<String> uniqueList = null; |
| Aliases aliases = getAliases(); |
| List<String> inputCollections = StrUtils.splitSmart(collectionStr, ",", true); |
| if (inputCollections.size() > 1) { |
| uniqueList = new LinkedHashSet<>(); |
| } |
| for (String inputCollection : inputCollections) { |
| List<String> resolvedCollections = aliases.resolveAliases(inputCollection); |
| if (uniqueList != null) { |
| uniqueList.addAll(resolvedCollections); |
| } else { |
| result = resolvedCollections; |
| } |
| } |
| if (uniqueList != null) { |
| return Collections.unmodifiableList(new ArrayList<>(uniqueList)); |
| } else { |
| return Collections.unmodifiableList(result); |
| } |
| } |
| |
| /** |
| * Extract handler from the URL path if not set. |
| */ |
| protected void extractHandlerFromURLPath(SolrRequestParsers parser) throws Exception { |
| if (log.isTraceEnabled()) { |
| log.trace("Extract handler from url path {} {}", handler, path); |
| } |
| if (handler == null && path.length() > 1) { // don't match "" or "/" as valid path |
| handler = core.getRequestHandler(path); |
| if (log.isTraceEnabled()) { |
| log.trace("handler={} name={}", handler, path); |
| } |
| // no handler yet but <requestDispatcher> allows us to handle /select with a 'qt' param |
| 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); |
| } |
| if (qt != null && qt.startsWith("/") && (handler instanceof ContentStreamHandlerBase)) { |
| //For security reasons it's a bad idea to allow a leading '/', ex: /select?qt=/update see SOLR-3161 |
| //There was no restriction from Solr 1.4 thru 3.5 and it's not supported for update handlers. |
| throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid Request Handler ('qt'). Do not use /select to access: " + qt); |
| } |
| } |
| } |
| } |
| } |
| |
| protected void extractRemotePath(String collectionName) throws UnsupportedEncodingException, KeeperException, InterruptedException, SolrException, TimeoutException { |
| |
| |
| coreUrl = getRemoteCoreUrl(collectionName); |
| // don't proxy for internal update requests |
| // Map<String,Integer> invalidStates = checkStateVersionsAreValid(getCollectionsList(), queryParams.get(CloudSolrClient.STATE_VERSION)); |
| if (coreUrl != null |
| && queryParams.get(DistributingUpdateProcessorFactory.DISTRIB_UPDATE_PARAM) == null) { |
| // if (invalidStates != null) { |
| // //it does not make sense to send the request to a remote node |
| // throw new SolrException(SolrException.ErrorCode.INVALID_STATE, new String(Utils.toJSON(invalidStates), org.apache.lucene.util.IOUtils.UTF_8)); |
| // } |
| action = REMOTEQUERY; |
| } else { |
| if (!retry) { |
| // we couldn't find a core to work with, try reloading aliases & this collection |
| // cores.getZkController().getZkStateReader().aliasesManager.update(); |
| // action = RETRY; |
| } |
| } |
| } |
| |
| Action authorize() throws IOException { |
| AuthorizationContext context = getAuthCtx(); |
| log.debug("AuthorizationContext : {}", context); |
| AuthorizationResponse authResponse = cores.getAuthorizationPlugin().authorize(context); |
| int statusCode = authResponse.statusCode; |
| log.info("Authorization response status code {}", authResponse.statusCode); |
| |
| if (statusCode == AuthorizationResponse.PROMPT.statusCode) { |
| Map<String, String> headers = (Map) getReq().getAttribute(AuthenticationPlugin.class.getName()); |
| if (headers != null) { |
| for (Map.Entry<String, String> e : headers.entrySet()) response.setHeader(e.getKey(), e.getValue()); |
| } |
| if (log.isDebugEnabled()) { |
| log.debug("USER_REQUIRED {} {}", req.getHeader("Authorization"), req.getUserPrincipal()); |
| } |
| sendError(statusCode, |
| "Authentication failed, Response code: " + statusCode); |
| if (shouldAudit(EventType.REJECTED)) { |
| cores.getAuditLoggerPlugin().doAudit(new AuditEvent(EventType.REJECTED, req, context)); |
| } |
| return RETURN; |
| } |
| if (statusCode == AuthorizationResponse.FORBIDDEN.statusCode) { |
| if (log.isDebugEnabled()) { |
| log.debug("UNAUTHORIZED auth header {} context : {}, msg: {}", req.getHeader("Authorization"), context, authResponse.getMessage()); |
| } |
| sendError(statusCode, |
| "Unauthorized request, Response code: " + statusCode); |
| if (shouldAudit(EventType.UNAUTHORIZED)) { |
| cores.getAuditLoggerPlugin().doAudit(new AuditEvent(EventType.UNAUTHORIZED, req, context)); |
| } |
| return RETURN; |
| } |
| if (!(statusCode == HttpStatus.SC_ACCEPTED) && !(statusCode == HttpStatus.SC_OK)) { |
| log.warn("ERROR {} during authentication: {}", statusCode, authResponse.getMessage()); |
| sendError(statusCode, |
| "ERROR during authorization, Response code: " + statusCode); |
| if (shouldAudit(EventType.ERROR)) { |
| cores.getAuditLoggerPlugin().doAudit(new AuditEvent(EventType.ERROR, req, context)); |
| } |
| return RETURN; |
| } |
| if (shouldAudit(EventType.AUTHORIZED)) { |
| cores.getAuditLoggerPlugin().doAudit(new AuditEvent(EventType.AUTHORIZED, req, context)); |
| } |
| return ADMIN; |
| } |
| |
| /** |
| * This method processes the request. |
| */ |
| public Action call() throws IOException { |
| MDCLoggingContext.reset(); |
| Span activeSpan = GlobalTracer.getTracer().activeSpan(); |
| if (activeSpan != null) { |
| MDCLoggingContext.setTracerId(activeSpan.context().toTraceId()); |
| } |
| if (cores.isZooKeeperAware()) { |
| MDCLoggingContext.setNode(cores.getZkController().getNodeName()); |
| } |
| |
| if (cores == null) { |
| sendError(503, "Server is shutting down or failed to initialize"); |
| return RETURN; |
| } |
| |
| if (solrDispatchFilter.abortErrorMessage != null) { |
| sendError(500, solrDispatchFilter.abortErrorMessage); |
| if (shouldAudit(EventType.ERROR)) { |
| cores.getAuditLoggerPlugin().doAudit(new AuditEvent(EventType.ERROR, getReq())); |
| } |
| return RETURN; |
| } |
| |
| try { |
| if (action == null) { |
| init(); |
| } |
| |
| // Perform authorization here, if: |
| // (a) Authorization is enabled, and |
| // (b) The requested resource is not a known static file |
| // (c) And this request should be handled by this node (see NOTE below) |
| // NOTE: If the query is to be handled by another node, then let that node do the authorization. |
| // In case of authentication using BasicAuthPlugin, for example, the internode request |
| // is secured using PKI authentication and the internode request here will contain the |
| // original user principal as a payload/header, using which the receiving node should be |
| // able to perform the authorization. |
| if (cores.getAuthorizationPlugin() != null && shouldAuthorize() |
| && !(action == REMOTEQUERY || action == FORWARD)) { |
| Action authorizationAction = authorize(); |
| if (authorizationAction == RETURN) { |
| return authorizationAction; |
| } |
| } |
| |
| HttpServletResponse resp = response; |
| switch (action) { |
| case ADMIN: |
| handleAdminRequest(); |
| return RETURN; |
| case REMOTEQUERY: |
| SolrRequestInfo.setRequestInfo(new SolrRequestInfo(req, new SolrQueryResponse(), action)); |
| Action a = remoteQuery(coreUrl + path); |
| return a; |
| case PROCESS: |
| 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(); |
| /* 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) |
| */ |
| SolrRequestInfo.setRequestInfo(new SolrRequestInfo(solrReq, solrRsp, action)); |
| execute(solrRsp); |
| if (shouldAudit()) { |
| EventType eventType = solrRsp.getException() == null ? EventType.COMPLETED : EventType.ERROR; |
| if (shouldAudit(eventType)) { |
| cores.getAuditLoggerPlugin().doAudit( |
| new AuditEvent(eventType, req, getAuthCtx(), solrReq.getRequestTimer().getTime(), solrRsp.getException())); |
| } |
| } |
| HttpCacheHeaderUtil.checkHttpCachingVeto(solrRsp, resp, reqMethod); |
| Iterator<Map.Entry<String, String>> headers = solrRsp.httpHeaders(); |
| while (headers.hasNext()) { |
| Map.Entry<String, String> entry = headers.next(); |
| resp.addHeader(entry.getKey(), entry.getValue()); |
| } |
| QueryResponseWriter responseWriter = getResponseWriter(); |
| //if (invalidStates != null) solrReq.getContext().put(CloudSolrClient.STATE_VERSION, invalidStates); |
| writeResponse(solrRsp, responseWriter, reqMethod); |
| } |
| return RETURN; |
| default: return action; |
| } |
| } catch (Throwable ex) { |
| if (!(ex instanceof PrepRecoveryOp.NotValidLeader) && shouldAudit(EventType.ERROR)) { |
| cores.getAuditLoggerPlugin().doAudit(new AuditEvent(EventType.ERROR, ex, req)); |
| } |
| sendError(ex); |
| // walk the the entire cause chain to search for an Error |
| Throwable t = ex; |
| while (t != null) { |
| if (t instanceof Error) { |
| if (t != ex) { |
| log.error("An Error was wrapped in another exception - please report complete stacktrace on SOLR-6161", ex); |
| } |
| throw (Error) t; |
| } |
| t = t.getCause(); |
| } |
| return RETURN; |
| } |
| |
| } |
| |
| private boolean shouldAudit() { |
| return cores.getAuditLoggerPlugin() != null; |
| } |
| |
| private boolean shouldAudit(AuditEvent.EventType eventType) { |
| return shouldAudit() && cores.getAuditLoggerPlugin().shouldLog(eventType); |
| } |
| |
| private boolean shouldAuthorize() { |
| if(PublicKeyHandler.PATH.equals(path)) return false; |
| //admin/info/key is the path where public key is exposed . it is always unsecured |
| if ("/".equals(path) || "/solr/".equals(path)) return false; // Static Admin UI files must always be served |
| if (cores.getPkiAuthenticationPlugin() != null && req.getUserPrincipal() != null) { |
| boolean b = cores.getPkiAuthenticationPlugin().needsAuthorization(req); |
| log.debug("PkiAuthenticationPlugin says authorization required : {} ", b); |
| return b; |
| } |
| return true; |
| } |
| |
| void destroy() { |
| try { |
| if (solrReq != null) { |
| if (log.isTraceEnabled()) { |
| log.trace("Closing out SolrRequest: {}", solrReq); |
| } |
| IOUtils.closeQuietly(solrReq); |
| } |
| } finally { |
| try { |
| if (core != null) { |
| IOUtils.closeQuietly(core); |
| } |
| } finally { |
| SolrRequestInfo.clearRequestInfo(); |
| } |
| AuthenticationPlugin authcPlugin = cores.getAuthenticationPlugin(); |
| if (authcPlugin != null) authcPlugin.closeRequest(); |
| } |
| } |
| |
| private Action remoteQuery(String coreUrl) throws IOException { |
| if (req != null) { |
| |
| log.info("proxy to:" + coreUrl + "?" + req.getQueryString()); |
| // MRM TODO: - dont proxy around too much |
| String fhost = req.getHeader(HttpHeader.X_FORWARDED_FOR.toString()); |
| if (fhost != null) { |
| // Already proxied |
| log.warn("Already proxied, return 404"); |
| sendError(404, "No SolrCore found to service request."); |
| return RETURN; |
| } |
| |
| //System.out.println("protocol:" + req.getProtocol()); |
| URL url = new URL(coreUrl + "?" + (req.getQueryString() != null ? req.getQueryString() : "")); |
| final Request proxyRequest; |
| try { |
| proxyRequest = solrDispatchFilter.httpClient.newRequest(url.toURI()) |
| .method(req.getMethod()) |
| .version(HttpVersion.fromString(req.getProtocol())); |
| } catch(IllegalArgumentException e) { |
| log.error("Error parsing URI for proxying " + url, e); |
| throw new SolrException(ErrorCode.SERVER_ERROR, e); |
| } catch (URISyntaxException e) { |
| log.error("Error parsing URI for proxying " + url, e); |
| throw new SolrException(ErrorCode.SERVER_ERROR, e); |
| } |
| |
| copyRequestHeaders(req, proxyRequest); |
| |
| addProxyHeaders(req, proxyRequest); |
| |
| |
| if (hasContent(req)) { |
| InputStreamContentProvider defferedContent = new InputStreamContentProvider(req.getInputStream(), 8192, false); |
| proxyRequest.content(defferedContent); |
| } |
| AtomicReference<Throwable> failException = new AtomicReference<>(); |
| InputStreamResponseListener listener = new InputStreamResponseListener() { |
| |
| public void onComplete(Result result) { |
| super.onComplete(result); |
| |
| if (result.isFailed()) { |
| failException.set(result.getFailure()); |
| } |
| } |
| |
| @Override |
| public void onHeaders(Response resp) { |
| super.onHeaders(resp); |
| //System.out.println("resp code:" + resp.getStatus()); |
| for (HttpField field : resp.getHeaders()) { |
| String headerName = field.getName(); |
| String lowerHeaderName = headerName.toLowerCase(Locale.ROOT); |
| // System.out.println("response header: " + headerName + " : " + field.getValue() + " status:" + |
| // resp.getStatus()); |
| if (HOP_HEADERS.contains(lowerHeaderName)) |
| continue; |
| |
| response.addHeader(headerName, field.getValue()); |
| } |
| response.setStatus(resp.getStatus()); |
| super.onHeaders(resp); |
| } |
| }; |
| |
| proxyRequest.send(listener); |
| |
| try { |
| listener.get(60, TimeUnit.SECONDS); |
| } catch (Exception e) { |
| throw new SolrException(ErrorCode.SERVER_ERROR, e); |
| } |
| |
| listener.getInputStream().transferTo(response.getOutputStream()); |
| |
| if (failException.get() != null) { |
| sendError(failException.get()); |
| } |
| |
| } |
| |
| return RETURN; |
| } |
| |
| protected boolean hasContent(HttpServletRequest clientRequest) { |
| boolean hasContent = clientRequest.getContentLength() > 0 || |
| clientRequest.getContentType() != null || |
| clientRequest.getHeader(HttpHeader.TRANSFER_ENCODING.asString()) != null; |
| return hasContent; |
| } |
| |
| protected void addProxyHeaders(HttpServletRequest clientRequest, Request proxyRequest) { |
| proxyRequest.header(HttpHeader.VIA, "HTTP/2.0 Solr Proxy"); //MRM TODO: protocol hard code |
| proxyRequest.header(HttpHeader.X_FORWARDED_FOR, clientRequest.getRemoteAddr()); |
| // we have some tricky to see in tests header size limitations |
| // proxyRequest.header(HttpHeader.X_FORWARDED_PROTO, clientRequest.getScheme()); |
| // proxyRequest.header(HttpHeader.X_FORWARDED_HOST, clientRequest.getHeader(HttpHeader.HOST.asString())); |
| // proxyRequest.header(HttpHeader.X_FORWARDED_SERVER, clientRequest.getLocalName()); |
| proxyRequest.header(QoSParams.REQUEST_SOURCE, QoSParams.INTERNAL); |
| } |
| |
| protected void copyRequestHeaders(HttpServletRequest clientRequest, Request proxyRequest) { |
| // First clear possibly existing headers, as we are going to copy those from the client request. |
| proxyRequest.getHeaders().clear(); |
| |
| Set<String> headersToRemove = findConnectionHeaders(clientRequest); |
| |
| for (Enumeration<String> headerNames = clientRequest.getHeaderNames(); headerNames.hasMoreElements();) { |
| String headerName = headerNames.nextElement(); |
| String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH); |
| |
| if (HttpHeader.HOST.is(headerName) && !preserveHost) |
| continue; |
| |
| // Remove hop-by-hop headers. |
| if (HOP_HEADERS.contains(lowerHeaderName)) |
| continue; |
| if (headersToRemove != null && headersToRemove.contains(lowerHeaderName)) |
| continue; |
| |
| for (Enumeration<String> headerValues = clientRequest.getHeaders(headerName); headerValues.hasMoreElements();) { |
| String headerValue = headerValues.nextElement(); |
| if (headerValue != null) { |
| proxyRequest.header(headerName, headerValue); |
| //System.out.println("request header: " + headerName + " : " + headerValue); |
| } |
| } |
| } |
| |
| // Force the Host header if configured |
| // if (_hostHeader != null) |
| // proxyRequest.header(HttpHeader.HOST, _hostHeader); |
| } |
| |
| protected Set<String> findConnectionHeaders(HttpServletRequest clientRequest) |
| { |
| // Any header listed by the Connection header must be removed: |
| // http://tools.ietf.org/html/rfc7230#section-6.1. |
| Set<String> hopHeaders = null; |
| Enumeration<String> connectionHeaders = clientRequest.getHeaders(HttpHeader.CONNECTION.asString()); |
| while (connectionHeaders.hasMoreElements()) |
| { |
| String value = connectionHeaders.nextElement(); |
| String[] values = value.split(","); |
| for (String name : values) |
| { |
| name = name.trim().toLowerCase(Locale.ENGLISH); |
| if (hopHeaders == null) |
| hopHeaders = new HashSet<>(); |
| hopHeaders.add(name); |
| } |
| } |
| return hopHeaders; |
| } |
| |
| protected void sendError(Throwable ex) throws IOException { |
| Exception exp = null; |
| try { |
| SolrQueryResponse solrResp = new SolrQueryResponse(); |
| if (ex instanceof Exception) { |
| solrResp.setException((Exception) ex); |
| } else { |
| solrResp.setException(new RuntimeException(ex)); |
| } |
| if (solrReq == null) { |
| final SolrParams solrParams; |
| if (req != null) { |
| // use GET parameters if available: |
| solrParams = SolrRequestParsers.parseQueryString(req.getQueryString()); |
| } else { |
| // we have no params at all, use empty ones: |
| solrParams = new MapSolrParams(Collections.emptyMap()); |
| } |
| solrReq = new SolrQueryRequestBase(core, solrParams) { |
| }; |
| } |
| QueryResponseWriter writer = getResponseWriter(); |
| writeResponse(solrResp, writer, Method.GET); |
| } catch (Exception e) { // This error really does not matter |
| exp = e; |
| } finally { |
| |
| if (exp != null) { |
| SimpleOrderedMap info = new SimpleOrderedMap(); |
| int code = ResponseUtils.getErrorInfo(ex, info, log); |
| sendError(code, info.toString()); |
| } |
| |
| } |
| } |
| |
| protected void sendError(int code, String message) throws IOException { |
| try { |
| response.sendError(code, message); |
| } catch (Exception e) { |
| log.info("Unable to write error response, client closed connection or we are shutting down", e); |
| } |
| } |
| |
| protected void execute(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() |
| solrReq.getContext().put("webapp", req.getContextPath()); |
| solrReq.getCore().execute(handler, solrReq, rsp); |
| } |
| |
| private void handleAdminRequest() throws IOException { |
| SolrQueryResponse solrResp = new SolrQueryResponse(); |
| SolrCore.preDecorateResponse(solrReq, solrResp); |
| handleAdmin(solrResp); |
| SolrCore.postDecorateResponse(handler, solrReq, solrResp); |
| if (log.isInfoEnabled() && solrResp.getToLog().size() > 0) { |
| log.info(solrResp.getToLogAsString("[admin]")); |
| } |
| QueryResponseWriter respWriter = SolrCore.DEFAULT_RESPONSE_WRITERS.get(solrReq.getParams().get(CommonParams.WT)); |
| if (respWriter == null) respWriter = getResponseWriter(); |
| writeResponse(solrResp, respWriter, Method.getMethod(req.getMethod())); |
| if (shouldAudit()) { |
| EventType eventType = solrResp.getException() == null ? EventType.COMPLETED : EventType.ERROR; |
| if (shouldAudit(eventType)) { |
| cores.getAuditLoggerPlugin().doAudit( |
| new AuditEvent(eventType, req, getAuthCtx(), solrReq.getRequestTimer().getTime(), solrResp.getException())); |
| } |
| } |
| } |
| |
| /** |
| * Returns {@link QueryResponseWriter} to be used. |
| * When {@link CommonParams#WT} not specified in the request or specified value doesn't have |
| * corresponding {@link QueryResponseWriter} then, returns the default query response writer |
| * Note: This method must not return null |
| */ |
| protected QueryResponseWriter getResponseWriter() { |
| String wt = solrReq.getParams().get(CommonParams.WT); |
| if (core != null) { |
| return core.getQueryResponseWriter(wt); |
| } else { |
| return SolrCore.DEFAULT_RESPONSE_WRITERS.getOrDefault(wt, |
| SolrCore.DEFAULT_RESPONSE_WRITERS.get("standard")); |
| } |
| } |
| |
| protected void handleAdmin(SolrQueryResponse solrResp) { |
| handler.handleRequest(solrReq, solrResp); |
| } |
| |
| /** |
| * Sets the "collection" parameter on the request to the list of alias-resolved collections for this request. |
| * It can be avoided sometimes. |
| * Note: {@link org.apache.solr.handler.component.HttpShardHandler} processes this param. |
| * @see #getCollectionsList() |
| */ |
| protected void addCollectionParamIfNeeded(List<String> collections) { |
| if (collections.isEmpty()) { |
| return; |
| } |
| assert cores.isZooKeeperAware(); |
| String collectionParam = queryParams.get(COLLECTION_PROP); |
| // if there is no existing collection param and the core we go to is for the expected collection, |
| // then we needn't add a collection param |
| if (collectionParam == null && // if collection param already exists, we may need to over-write it |
| core != null && collections.equals(Collections.singletonList(core.getCoreDescriptor().getCollectionName()))) { |
| return; |
| } |
| String newCollectionParam = StrUtils.join(collections, ','); |
| if (newCollectionParam.equals(collectionParam)) { |
| return; |
| } |
| // TODO add a SolrRequest.getModifiableParams ? |
| ModifiableSolrParams params = new ModifiableSolrParams(solrReq.getParams()); |
| params.set(COLLECTION_PROP, newCollectionParam); |
| solrReq.setParams(params); |
| } |
| |
| private void writeResponse(SolrQueryResponse solrRsp, QueryResponseWriter responseWriter, Method reqMethod) |
| throws IOException { |
| try { |
| Object invalidStates = solrReq.getContext().get(CloudSolrClient.STATE_VERSION); |
| //This is the last item added to the response and the client would expect it that way. |
| //If that assumption is changed , it would fail. This is done to avoid an O(n) scan on |
| // the response for each request |
| if (invalidStates != null) solrRsp.add(CloudSolrClient.STATE_VERSION, invalidStates); |
| // Now write it out |
| final String ct = responseWriter.getContentType(solrReq, solrRsp); |
| // don't call setContentType on null |
| if (null != ct) response.setContentType(ct); |
| |
| if (solrRsp.getException() != null) { |
| NamedList info = new SimpleOrderedMap(); |
| int code = ResponseUtils.getErrorInfo(solrRsp.getException(), info, log); |
| solrRsp.add("error", info); |
| response.setStatus(code); |
| } |
| |
| if (Method.HEAD != reqMethod) { |
| OutputStream out = response.getOutputStream(); |
| QueryResponseWriterUtil.writeQueryResponse(out, responseWriter, solrReq, solrRsp, ct); |
| } |
| //else http HEAD request, nothing to write out, waited this long just to get ContentType |
| } catch (EOFException e) { |
| log.info("Unable to write response, client closed connection or we are shutting down", e); |
| } |
| } |
| |
| /** Returns null if the state ({@link CloudSolrClient#STATE_VERSION}) is good; otherwise returns state problems. */ |
| private Map<String, Integer> checkStateVersionsAreValid(List<String> collectionsList, String stateVer) { |
| Map<String, Integer> result = null; |
| String[] pairs; |
| if (stateVer != null && !stateVer.isEmpty() && cores.isZooKeeperAware()) { |
| // many have multiple collections separated by | |
| pairs = StringUtils.split(stateVer, '|'); |
| for (String pair : pairs) { |
| String[] pcs = StringUtils.split(pair, ':'); |
| if (pcs.length == 2 && !pcs[0].isEmpty() && !pcs[1].isEmpty()) { |
| |
| String[] versionAndUpdatesHash = pcs[1].split(">"); |
| int version = Integer.parseInt(versionAndUpdatesHash[0]); |
| int updateHash = Integer.parseInt(versionAndUpdatesHash[1]); |
| |
| Integer status = cores.getZkController().getZkStateReader().compareStateVersions(pcs[0], version, updateHash); |
| if (status != null) { |
| if (result == null) result = new HashMap<>(); |
| result.put(pcs[0], status); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| private Map<String, Integer> getStateVersions(String stateVer) { |
| // TODO: for collections that are local and watched, we should just wait for the right min state, not eager fetch everything |
| Map<String, Integer> result = null; |
| String[] pairs; |
| if (stateVer != null && !stateVer.isEmpty() && cores.isZooKeeperAware()) { |
| // many have multiple collections separated by | |
| pairs = StringUtils.split(stateVer, '|'); |
| for (String pair : pairs) { |
| String[] pcs = StringUtils.split(pair, ':'); |
| if (pcs.length == 2 && !pcs[0].isEmpty() && !pcs[1].isEmpty()) { |
| if (log.isDebugEnabled()) { |
| log.debug("compare version states {} {}", pcs[0], Integer.parseInt(pcs[1])); |
| } |
| |
| if (result == null) result = new HashMap<>(); |
| result.put(pcs[0], Integer.parseInt(pcs[1])); |
| |
| } |
| } |
| } |
| if (log.isTraceEnabled()) { |
| log.trace("compare version states result {} {}", stateVer, result); |
| } |
| return result; |
| } |
| |
| protected SolrCore getCoreByCollection(String collectionName, boolean isPreferLeader) throws TimeoutException, InterruptedException { |
| log.debug("get core by collection {} {}", collectionName, isPreferLeader); |
| |
| ZkStateReader zkStateReader = cores.getZkController().getZkStateReader(); |
| |
| ClusterState clusterState = zkStateReader.getClusterState(); |
| DocCollection collection = clusterState.getCollectionOrNull(collectionName); |
| |
| if (collection == null) { |
| log.debug("no local core found for collection={}", collectionName); |
| return null; |
| } |
| |
| if (isPreferLeader) { |
| List<Replica> leaderReplicas = collection.getLeaderReplicas(cores.getZkController().getNodeName()); |
| log.debug("preferLeader leaderReplicas={}", leaderReplicas); |
| SolrCore core = randomlyGetSolrCore(leaderReplicas, true); |
| if (core != null) return core; |
| } |
| |
| List<Replica> replicas = collection.getReplicas(cores.getZkController().getNodeName()); |
| if (log.isDebugEnabled()) log.debug("replicas for node {} {}", replicas, cores.getZkController().getNodeName()); |
| SolrCore returnCore = randomlyGetSolrCore(replicas, true); |
| if (log.isDebugEnabled()) log.debug("returning core by collection {}", returnCore == null ? null : returnCore.getName()); |
| return returnCore; |
| } |
| |
| private SolrCore randomlyGetSolrCore(List<Replica> replicas, boolean checkActive) { |
| if (replicas != null) { |
| RandomIterator<Replica> it = new RandomIterator<>(random, replicas); |
| while (it.hasNext()) { |
| Replica replica = it.next(); |
| |
| SolrCore core = checkProps(replica); |
| if (core != null && checkActive) { |
| try { |
| cores.getZkController().getZkStateReader().waitForState(core.getCoreDescriptor().getCollectionName(), 1, TimeUnit.SECONDS, (liveNodes1, coll) -> { |
| if (coll == null) { |
| return false; |
| } |
| Replica rep = coll.getReplica(replica.getName()); |
| if (rep == null) { |
| return false; |
| } |
| if (rep.getState() == Replica.State.ACTIVE) { |
| return true; |
| } |
| return false; |
| }); |
| } catch (InterruptedException e) { |
| log.debug("interrupted waiting to see active replica"); |
| return null; |
| } catch (TimeoutException e) { |
| log.debug("timeout waiting to see active replica {} {}", replica.getName(), replica.getState()); |
| return null; |
| } |
| return core; |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| private SolrCore checkProps(Replica replica) { |
| SolrCore core = null; |
| boolean nodeMatches = cores.getZkController().getNodeName().equals(replica.getNodeName()); |
| if (nodeMatches) { |
| if (cores.isCoreLoading(replica.getName())) { |
| cores.waitForLoadingCore(replica.getName(), 10000); |
| } |
| core = cores.getCore(replica.getName()); |
| } |
| log.debug("check local core has correct props replica={} nodename={} nodematches={} core found={}", replica, cores.getZkController().getNodeName(), nodeMatches, core != null); |
| return core; |
| } |
| |
| protected String getRemoteCoreUrl(String collectionName) throws SolrException { |
| ClusterState clusterState = cores.getZkController().getClusterState(); |
| final DocCollection docCollection = clusterState.getCollectionOrNull(collectionName, false); |
| if (docCollection == null) { |
| return null; |
| } |
| |
| String coreUrl = getCoreUrl(docCollection.getSlices()); |
| |
| if (log.isDebugEnabled()) { |
| log.debug("get remote core url returning {} for {} {}", coreUrl, collectionName, origCorename); |
| } |
| return coreUrl; |
| } |
| |
| private String getCoreUrl(Collection<Slice> slices) { |
| String coreUrl; |
| |
| for (Slice slice : slices) { |
| List<Replica> randomizedReplicas = new ArrayList<>(slice.getReplicas()); |
| Collections.shuffle(randomizedReplicas, random); |
| |
| for (Replica replica : randomizedReplicas) { |
| log.debug("check replica {} with node name {} against live nodes {} with state {}", |
| replica.getName(), replica.getNodeName(), cores.getZkController().getZkStateReader().getLiveNodes(), replica.getState()); |
| if (!replica.getNodeName().equals(cores.getZkController().getNodeName()) && cores.getZkController().zkStateReader.getLiveNodes().contains(replica.getNodeName()) |
| && replica.getState() == Replica.State.ACTIVE) { |
| |
| coreUrl = replica.getCoreUrl(); |
| |
| return coreUrl; |
| } |
| } |
| } |
| return null; |
| } |
| |
| protected Object _getHandler(){ |
| return handler; |
| } |
| |
| private AuthorizationContext getAuthCtx() { |
| |
| String resource = getPath(); |
| |
| SolrParams params = getQueryParams(); |
| final ArrayList<CollectionRequest> collectionRequests = new ArrayList<>(); |
| for (String collection : getCollectionsList()) { |
| collectionRequests.add(new CollectionRequest(collection)); |
| } |
| |
| // Extract collection name from the params in case of a Collection Admin request |
| if (getPath().equals("/admin/collections")) { |
| if (CREATE.isEqual(params.get("action"))|| |
| RELOAD.isEqual(params.get("action"))|| |
| DELETE.isEqual(params.get("action"))) |
| collectionRequests.add(new CollectionRequest(params.get("name"))); |
| else if (params.get(COLLECTION_PROP) != null) |
| collectionRequests.add(new CollectionRequest(params.get(COLLECTION_PROP))); |
| } |
| |
| // Populate the request type if the request is select or update |
| if(requestType == RequestType.UNKNOWN) { |
| if(resource.startsWith("/select") || resource.startsWith("/get")) |
| requestType = RequestType.READ; |
| if(resource.startsWith("/update")) |
| requestType = RequestType.WRITE; |
| } |
| |
| return new AuthorizationContext() { |
| @Override |
| public SolrParams getParams() { |
| return null == solrReq ? null : solrReq.getParams(); |
| } |
| |
| @Override |
| public Principal getUserPrincipal() { |
| return getReq().getUserPrincipal(); |
| } |
| |
| @Override |
| public String getHttpHeader(String s) { |
| return getReq().getHeader(s); |
| } |
| |
| @Override |
| public Enumeration<String> getHeaderNames() { |
| return getReq().getHeaderNames(); |
| } |
| |
| @Override |
| public List<CollectionRequest> getCollectionRequests() { |
| return collectionRequests; |
| } |
| |
| @Override |
| public RequestType getRequestType() { |
| return requestType; |
| } |
| |
| public String getResource() { |
| return path; |
| } |
| |
| @Override |
| public String getHttpMethod() { |
| return getReq().getMethod(); |
| } |
| |
| @Override |
| public Object getHandler() { |
| return _getHandler(); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder response = new StringBuilder("userPrincipal: [").append(getUserPrincipal()).append("]") |
| .append(" type: [").append(requestType.toString()).append("], collections: ["); |
| for (CollectionRequest collectionRequest : collectionRequests) { |
| response.append(collectionRequest.collectionName).append(", "); |
| } |
| if(collectionRequests.size() > 0) |
| response.delete(response.length() - 1, response.length()); |
| |
| response.append("], Path: [").append(resource).append("]"); |
| response.append(" path : ").append(path).append(" params :").append(getParams()); |
| return response.toString(); |
| } |
| |
| @Override |
| public String getRemoteAddr() { |
| return getReq().getRemoteAddr(); |
| } |
| |
| @Override |
| public String getRemoteHost() { |
| return getReq().getRemoteHost(); |
| } |
| }; |
| |
| } |
| |
| static final String CONNECTION_HEADER = "Connection"; |
| static final String TRANSFER_ENCODING_HEADER = "Transfer-Encoding"; |
| static final String CONTENT_LENGTH_HEADER = "Content-Length"; |
| List<CommandOperation> parsedCommands; |
| |
| public List<CommandOperation> getCommands(boolean validateInput) { |
| if (parsedCommands == null) { |
| Iterable<ContentStream> contentStreams = solrReq.getContentStreams(); |
| if (contentStreams == null) parsedCommands = Collections.EMPTY_LIST; |
| else { |
| parsedCommands = ApiBag.getCommandOperations(contentStreams.iterator().next(), getValidators(), validateInput); |
| } |
| } |
| return CommandOperation.clone(parsedCommands); |
| } |
| protected ValidatingJsonMap getSpec() { |
| return null; |
| } |
| |
| protected Map<String, JsonSchemaValidator> getValidators(){ |
| return Collections.EMPTY_MAP; |
| } |
| |
| /** |
| * A faster method for randomly picking items when you do not need to |
| * consume all items. |
| */ |
| private static class RandomIterator<E> implements Iterator<E> { |
| private Random rand; |
| private ArrayList<E> elements; |
| private int size; |
| |
| public RandomIterator(Random rand, Collection<E> elements) { |
| this.rand = rand; |
| this.elements = new ArrayList<>(elements); |
| this.size = elements.size(); |
| } |
| |
| @Override |
| public boolean hasNext() { |
| return size > 0; |
| } |
| |
| @Override |
| public E next() { |
| int idx = rand.nextInt(size); |
| E e1 = elements.get(idx); |
| E e2 = elements.get(size-1); |
| elements.set(idx,e2); |
| size--; |
| return e1; |
| } |
| } |
| |
| protected static final Set<String> HOP_HEADERS; |
| static |
| { |
| Set<String> hopHeaders = new HashSet<>(12); |
| hopHeaders.add("accept-encoding"); |
| hopHeaders.add("connection"); |
| hopHeaders.add("keep-alive"); |
| hopHeaders.add("proxy-authorization"); |
| hopHeaders.add("proxy-authenticate"); |
| hopHeaders.add("proxy-connection"); |
| hopHeaders.add("transfer-encoding"); |
| hopHeaders.add("te"); |
| hopHeaders.add("trailer"); |
| hopHeaders.add("upgrade"); |
| // hopHeaders.add(HttpHeader.X_FORWARDED_FOR.asString()); |
| // hopHeaders.add(HttpHeader.X_FORWARDED_PROTO.asString()); |
| // hopHeaders.add(HttpHeader.VIA.asString()); |
| // hopHeaders.add(HttpHeader.X_FORWARDED_HOST.asString()); |
| // hopHeaders.add(HttpHeader.SERVER.asString()); |
| // |
| HOP_HEADERS = Collections.unmodifiableSet(hopHeaders); |
| } |
| } |