diff --git a/changelog/unreleased/SOLR-18095.yml b/changelog/unreleased/SOLR-18095.yml
new file mode 100644
index 000000000000..8884ef9ab2de
--- /dev/null
+++ b/changelog/unreleased/SOLR-18095.yml
@@ -0,0 +1,8 @@
+# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
+title: Provide NoOpRequestWriter and NoOpRequestHandler that can be used to disable implicitly configured equivalents.
+type: added # added, changed, fixed, deprecated, removed, dependency_update, security, other
+authors:
+ - name: Eric Pugh
+links:
+ - name: SOLR-18095
+ url: https://issues.apache.org/jira/browse/SOLR-18095
diff --git a/solr/core/src/java/org/apache/solr/handler/NotFoundRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/NoOpRequestHandler.java
similarity index 86%
rename from solr/core/src/java/org/apache/solr/handler/NotFoundRequestHandler.java
rename to solr/core/src/java/org/apache/solr/handler/NoOpRequestHandler.java
index 50e2fa1f27ca..6ce2e82d8778 100644
--- a/solr/core/src/java/org/apache/solr/handler/NotFoundRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/NoOpRequestHandler.java
@@ -23,12 +23,12 @@
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.security.AuthorizationContext;
-/** Does nothing other than showing a 404 message */
-public class NotFoundRequestHandler extends RequestHandlerBase {
+/** Does nothing other than showing a 403 message */
+public class NoOpRequestHandler extends RequestHandlerBase {
@Override
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
throw new SolrException(
- SolrException.ErrorCode.NOT_FOUND, "" + req.getContext().get(PATH) + " is not found");
+ SolrException.ErrorCode.FORBIDDEN, req.getContext().get(PATH) + " has been disabled");
}
@Override
diff --git a/solr/core/src/java/org/apache/solr/response/NoOpResponseWriter.java b/solr/core/src/java/org/apache/solr/response/NoOpResponseWriter.java
new file mode 100644
index 000000000000..125f3338f20e
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/response/NoOpResponseWriter.java
@@ -0,0 +1,35 @@
+/*
+ * 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.response;
+
+import java.io.IOException;
+import java.io.Writer;
+import org.apache.solr.request.SolrQueryRequest;
+
+public class NoOpResponseWriter implements TextQueryResponseWriter {
+ static String MESSAGE = "noop response writer";
+
+ @Override
+ public void write(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) throws IOException {
+ writer.write(MESSAGE);
+ }
+
+ @Override
+ public String getContentType(SolrQueryRequest request, SolrQueryResponse response) {
+ return QueryResponseWriter.CONTENT_TYPE_TEXT_UTF8;
+ }
+}
diff --git a/solr/core/src/test-files/solr/collection1/conf/solrconfig-noop.xml b/solr/core/src/test-files/solr/collection1/conf/solrconfig-noop.xml
new file mode 100644
index 000000000000..2e9a5e463d02
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/solrconfig-noop.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+ ${solr.data.dir:}
+
+
+
+ ${tests.luceneMatchVersion:LATEST}
+
+
+
+ ${solr.ulog.dir:}
+
+
+
+
+
+
+
+
+
+ explicit
+ true
+ text
+
+
+
+
+
+
+
diff --git a/solr/core/src/test/org/apache/solr/handler/NoOpRequestHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/NoOpRequestHandlerTest.java
new file mode 100644
index 000000000000..4193a7829bd3
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/NoOpRequestHandlerTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.handler;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test that demonstrates NoOpRequestHandler can be used to disable implicit handlers like
+ * SchemaHandler that are loaded via ImplicitPlugins.json.
+ */
+public class NoOpRequestHandlerTest extends SolrTestCaseJ4 {
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ initCore("solrconfig-noop.xml", "schema.xml");
+ }
+
+ @Test
+ public void testSchemaHandlerDisabled() {
+ // Test that /schema endpoint is disabled and returns 403 FORBIDDEN
+ SolrException exception =
+ expectThrows(
+ SolrException.class,
+ () -> {
+ try (SolrQueryRequest req = req("qt", "/schema")) {
+ SolrQueryResponse rsp = new SolrQueryResponse();
+ h.getCore().execute(h.getCore().getRequestHandler("/schema"), req, rsp);
+ if (rsp.getException() != null) {
+ throw rsp.getException();
+ }
+ }
+ });
+
+ assertEquals(
+ "Should return FORBIDDEN status code",
+ SolrException.ErrorCode.FORBIDDEN.code,
+ exception.code());
+ assertTrue(
+ "Error message should indicate endpoint has been disabled",
+ exception.getMessage().contains("has been disabled"));
+ }
+
+ @Test
+ public void testSchemaHandlerSubPathDisabled() {
+ // Test that /schema/fields endpoint is also disabled
+ SolrException exception =
+ expectThrows(
+ SolrException.class,
+ () -> {
+ try (SolrQueryRequest req = req("qt", "/schema/fields")) {
+ SolrQueryResponse rsp = new SolrQueryResponse();
+ h.getCore().execute(h.getCore().getRequestHandler("/schema"), req, rsp);
+ if (rsp.getException() != null) {
+ throw rsp.getException();
+ }
+ }
+ });
+
+ assertEquals(
+ "Should return FORBIDDEN status code",
+ SolrException.ErrorCode.FORBIDDEN.code,
+ exception.code());
+ }
+
+ @Test
+ public void testNoOpHandlerRegistered() {
+ // Verify that the NoOpRequestHandler is actually registered at /schema
+ assertNotNull("Schema handler should be registered", h.getCore().getRequestHandler("/schema"));
+ assertTrue(
+ "Handler at /schema should be NoOpRequestHandler",
+ h.getCore().getRequestHandler("/schema") instanceof NoOpRequestHandler);
+ }
+
+ @Test
+ public void testOtherHandlersStillWork() {
+ assertQ("Standard query handler should still work", req("q", "*:*"), "//result[@numFound='0']");
+ }
+}
diff --git a/solr/core/src/test/org/apache/solr/response/NoOpResponseWriterTest.java b/solr/core/src/test/org/apache/solr/response/NoOpResponseWriterTest.java
new file mode 100644
index 000000000000..fc8888eda829
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/response/NoOpResponseWriterTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.response;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import org.apache.solr.SolrTestCaseJ4;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test that demonstrates NoOpResponseWriter can be used to disable implicit response writers that
+ * are loaded via ImplicitPlugins.json.
+ */
+public class NoOpResponseWriterTest extends SolrTestCaseJ4 {
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ initCore("solrconfig-noop.xml", "schema.xml");
+ }
+
+ @Test
+ public void testWrite() throws IOException {
+ NoOpResponseWriter writer = new NoOpResponseWriter();
+
+ Writer stringWriter = new StringWriter();
+
+ writer.write(stringWriter, null, null);
+
+ assertEquals(NoOpResponseWriter.MESSAGE, stringWriter.toString());
+ }
+
+ @Test
+ public void testGetContentType() {
+ NoOpResponseWriter writer = new NoOpResponseWriter();
+
+ String contentType = writer.getContentType(null, null);
+ assertEquals(QueryResponseWriter.CONTENT_TYPE_TEXT_UTF8, contentType);
+ }
+
+ @Test
+ public void testCsvResponseWriterDisabled() throws Exception {
+ QueryResponseWriter csvWriter = h.getCore().getQueryResponseWriter("csv");
+
+ assertNotNull("CSV response writer should be registered", csvWriter);
+ assertTrue(
+ "CSV response writer should be NoOpResponseWriter, not the implicit CSVResponseWriter",
+ csvWriter instanceof NoOpResponseWriter);
+
+ // Verify it returns the NoOp message when used
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ csvWriter.write(out, null, null);
+ String output = out.toString(StandardCharsets.UTF_8);
+ assertEquals("CSV writer should return NoOp message", NoOpResponseWriter.MESSAGE, output);
+ }
+}
diff --git a/solr/solr-ref-guide/modules/configuration-guide/pages/implicit-requesthandlers.adoc b/solr/solr-ref-guide/modules/configuration-guide/pages/implicit-requesthandlers.adoc
index 85f25610a0ac..0b82acf0902d 100644
--- a/solr/solr-ref-guide/modules/configuration-guide/pages/implicit-requesthandlers.adoc
+++ b/solr/solr-ref-guide/modules/configuration-guide/pages/implicit-requesthandlers.adoc
@@ -367,3 +367,15 @@ The response will look similar to:
Because implicit request handlers are not present in `solrconfig.xml`, configuration of their associated `default`, `invariant` and `appends` parameters may be edited via the xref:request-parameters-api.adoc[] using the paramset listed in the above table.
However, other parameters, including SearchHandler components, may not be modified.
The invariants and appends specified in the implicit configuration cannot be overridden.
+
+== How to Disable an Implicit Handler
+
+You may want to disable the loading of an implicit handler.
+This is supported by remapping the name of the handler to the `NoOpRequestHandler` in `solrconfig.xml`, which will return a 403 FORBIDDEN status code.
+
+For example, to disable the `/update/csv` handler you would re-define it in `solrconfig.xml` as:
+
+[source,xml]
+----
+
+----
diff --git a/solr/solr-ref-guide/modules/query-guide/pages/response-writers.adoc b/solr/solr-ref-guide/modules/query-guide/pages/response-writers.adoc
index b4f29b8e6815..0aff169b739a 100644
--- a/solr/solr-ref-guide/modules/query-guide/pages/response-writers.adoc
+++ b/solr/solr-ref-guide/modules/query-guide/pages/response-writers.adoc
@@ -32,6 +32,7 @@ The list below describe shows the most common settings for the `wt` parameter, w
* <>
* <>
* <>
+* <>
== JSON Response Writer
@@ -386,3 +387,15 @@ else:
== Smile Response Writer
The Smile format is a JSON-compatible binary format, described in detail here: https://en.wikipedia.org/wiki/Smile_%28data_interchange_format%29[https://en.wikipedia.org/wiki/Smile_(data_interchange_format)]
+
+== NoOp Response Writer
+
+You may want to disable a specific response writer.
+This is supported by remapping the name of the response writer to the `NoOpResponseWriter` in `solrconfig.xml`, which will return a 200 OK status code with the plain text message `noop response writer`.
+
+For example, to disable the `csv` handler you would re-define it in `solrconfig.xml` as:
+
+[source,xml]
+----
+
+----