Skip to content

Commit 4f40e97

Browse files
authored
SOLR-18085: Remove use of "wt=standard" internally to Solr. (#4080)
Updates Solr’s response-writer (wt) handling to remove reliance on the legacy "standard" writer and to fail fast when an unknown response writer is requested. Additionally we migrate from throwing a 500 error to a more appropriate 400 error when a non existing response-write is specified.
1 parent d89a35c commit 4f40e97

File tree

16 files changed

+134
-145
lines changed

16 files changed

+134
-145
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
2+
title: Removed the wt=standard concept that was used internally by Solr.
3+
type: removed # added, changed, fixed, deprecated, removed, dependency_update, security, other
4+
authors:
5+
- name: Eric Pugh
6+
links:
7+
- name: SOLR-18085
8+
url: https://issues.apache.org/jira/browse/SOLR-18085

solr/core/src/java/org/apache/solr/core/PluginBag.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ public T get(String name) {
202202
* Fetches a plugin by name , or the default
203203
*
204204
* @param name name using which it is registered
205-
* @param useDefault Return the default , if a plugin by that name does not exist
205+
* @param useDefault Return the default, if a plugin by that name does not exist
206206
*/
207207
public T get(String name, boolean useDefault) {
208208
T result = get(name);

solr/core/src/java/org/apache/solr/core/RequestHandlers.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ void initHandlersFromConfig(SolrConfig config) {
117117
modifiedInfos.add(applyInitParams(config, info));
118118
}
119119
handlers.init(Collections.emptyMap(), core, modifiedInfos);
120-
handlers.alias(handlers.getDefault(), "");
120+
121121
if (log.isDebugEnabled()) {
122122
log.debug("Registered paths: {}", StrUtils.join(new ArrayList<>(handlers.keySet()), ','));
123123
}

solr/core/src/java/org/apache/solr/core/SolrCore.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3131,14 +3131,11 @@ private void initWriters() {
31313131

31323132
// Initialize with the built defaults
31333133
responseWriters.init(defaultWriters, this);
3134-
3135-
// configure the default response writer; this one should never be null
3136-
if (responseWriters.getDefault() == null) responseWriters.setDefault("standard");
31373134
}
31383135

3139-
/** Finds a writer by name, or returns the default writer if not found. */
3136+
/** Finds a writer by name, or null if not found. */
31403137
public final QueryResponseWriter getQueryResponseWriter(String writerName) {
3141-
return responseWriters.get(writerName, true);
3138+
return responseWriters.get(writerName, false);
31423139
}
31433140

31443141
/**

solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -204,14 +204,7 @@ default CloudDescriptor getCloudDescriptor() {
204204
*/
205205
default QueryResponseWriter getResponseWriter() {
206206
// it's weird this method is here instead of SolrQueryResponse, but it's practical/convenient
207-
SolrCore core = getCore();
208-
String wt = getParams().get(CommonParams.WT);
209-
// Use core writers if available, otherwise fall back to built-in writers
210-
if (core != null) {
211-
return core.getQueryResponseWriter(wt);
212-
} else {
213-
return ResponseWritersRegistry.getWriter(wt);
214-
}
207+
return ResponseWritersRegistry.getWriter(getParams().get(CommonParams.WT), getCore());
215208
}
216209

217210
/**

solr/core/src/java/org/apache/solr/response/RawResponseWriter.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public class RawResponseWriter implements QueryResponseWriter {
4747
*/
4848
public static final String CONTENT = "content";
4949

50-
private String _baseWriter = null;
50+
private String baseWriter = null;
5151

5252
/**
5353
* A fallback writer used for requests that don't return raw content and that aren't associated
@@ -62,14 +62,17 @@ public void init(NamedList<?> n) {
6262
if (n != null) {
6363
Object base = n.get("base");
6464
if (base != null) {
65-
_baseWriter = base.toString();
65+
baseWriter = base.toString();
6666
}
6767
}
6868
}
6969

7070
protected QueryResponseWriter getBaseWriter(SolrQueryRequest request) {
7171
if (request.getCore() != null) {
72-
return request.getCore().getQueryResponseWriter(_baseWriter);
72+
// When baseWriter is null, use the core's default writer (useDefault=true)
73+
// Otherwise, look up the specific writer by name (useDefault=false for explicit lookups)
74+
boolean useDefault = (baseWriter == null);
75+
return request.getCore().getResponseWriters().get(baseWriter, useDefault);
7376
}
7477

7578
// Requests to a specific core already have writers, but we still need a 'default writer' for

solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import static org.apache.solr.util.stats.MetricUtils.PROMETHEUS_METRICS_WT;
2121

2222
import java.util.Map;
23+
import org.apache.solr.common.SolrException;
2324
import org.apache.solr.common.params.CommonParams;
25+
import org.apache.solr.core.SolrCore;
2426
import org.apache.solr.handler.admin.api.ReplicationAPIBase;
2527

2628
/**
@@ -46,23 +48,14 @@ private ResponseWritersRegistry() {
4648
PrometheusResponseWriter prometheusWriter = new PrometheusResponseWriter();
4749

4850
BUILTIN_WRITERS =
49-
Map.of(
50-
CommonParams.JAVABIN,
51-
new JavaBinResponseWriter(),
52-
CommonParams.JSON,
53-
jsonWriter,
54-
"standard",
55-
jsonWriter, // Alias for JSON
56-
"xml",
57-
new XMLResponseWriter(),
58-
"raw",
59-
new RawResponseWriter(),
60-
PROMETHEUS_METRICS_WT,
61-
prometheusWriter,
62-
OPEN_METRICS_WT,
63-
prometheusWriter,
64-
ReplicationAPIBase.FILE_STREAM,
65-
new FileStreamResponseWriter());
51+
Map.ofEntries(
52+
Map.entry(CommonParams.JAVABIN, new JavaBinResponseWriter()),
53+
Map.entry(CommonParams.JSON, jsonWriter),
54+
Map.entry("xml", new XMLResponseWriter()),
55+
Map.entry("raw", new RawResponseWriter()),
56+
Map.entry(PROMETHEUS_METRICS_WT, prometheusWriter),
57+
Map.entry(OPEN_METRICS_WT, prometheusWriter),
58+
Map.entry(ReplicationAPIBase.FILE_STREAM, new FileStreamResponseWriter()));
6659
}
6760

6861
/**
@@ -71,17 +64,68 @@ private ResponseWritersRegistry() {
7164
* <p>Built-in writers are always available and provide essential formats needed by admin APIs and
7265
* core functionality. They do not depend on core configuration or ImplicitPlugins.json settings.
7366
*
74-
* <p>If the requested writer is not available, returns the "standard" (JSON) writer as a
75-
* fallback. This ensures requests always get a valid response format.
67+
* <p>If the requested writer is not available, returns the JSON writer as a fallback. This
68+
* ensures requests always get a valid response format.
7669
*
7770
* @param writerName the writer name (e.g., "json", "xml", "javabin"), or null for default
78-
* @return the response writer, never null (returns "standard"/JSON if not found)
71+
* @return the response writer, never null (returns JSON if not found)
7972
*/
8073
public static QueryResponseWriter getWriter(String writerName) {
8174
if (writerName == null || writerName.isEmpty()) {
82-
return BUILTIN_WRITERS.get("standard");
75+
writerName = CommonParams.JSON;
8376
}
84-
return BUILTIN_WRITERS.getOrDefault(writerName, BUILTIN_WRITERS.get("standard"));
77+
return BUILTIN_WRITERS.get(writerName);
78+
}
79+
80+
/**
81+
* Gets a response writer, trying the core's registry first, then falling back to built-in
82+
* writers. This is the unified entry point for all writer resolution.
83+
*
84+
* <p>Resolution order:
85+
*
86+
* <ol>
87+
* <li>If core is provided, check core's writer registry
88+
* <li>If not found in core (or no core), check built-in writers
89+
* <li>If writer name is explicitly specified but not found anywhere, throw exception
90+
* <li>If writer name is null/empty, return default (JSON)
91+
* </ol>
92+
*
93+
* @param writerName the writer name (e.g., "json", "xml", "javabin"), or null for default
94+
* @param core the SolrCore to check first, or null for node-level requests
95+
* @return the response writer, never null
96+
* @throws SolrException if an explicitly requested writer type is not found
97+
*/
98+
public static QueryResponseWriter getWriter(String writerName, SolrCore core) {
99+
QueryResponseWriter writer = null;
100+
101+
// Try core registry first if available
102+
if (core != null) {
103+
writer = core.getQueryResponseWriter(writerName);
104+
}
105+
106+
// If not found and writer is explicitly requested, validate it exists in built-in
107+
if (writer == null) {
108+
if (!hasWriter(writerName)) {
109+
throw new SolrException(
110+
SolrException.ErrorCode.BAD_REQUEST, "Unknown response writer type: " + writerName);
111+
} else {
112+
writer = getWriter(writerName);
113+
}
114+
}
115+
return writer;
116+
}
117+
118+
/**
119+
* Checks if a writer with the given name exists in the built-in writers.
120+
*
121+
* @param writerName the writer name to check
122+
* @return true if the writer exists, false otherwise
123+
*/
124+
public static boolean hasWriter(String writerName) {
125+
if (writerName == null || writerName.isEmpty()) {
126+
return true; // null/empty is valid, will use default
127+
}
128+
return BUILTIN_WRITERS.containsKey(writerName);
85129
}
86130

87131
/**

solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ private void handleAdminRequest() throws IOException {
727727

728728
protected void logAndFlushAdminRequest(SolrQueryResponse solrResp) throws IOException {
729729
if (solrResp.getToLog().size() > 0) {
730-
// has to come second and in it's own if to keep ./gradlew check happy.
730+
// has to come second and in its own "if" to keep ./gradlew check happy.
731731
if (log.isInfoEnabled()) {
732732
log.info(
733733
handler != null

solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java

Lines changed: 14 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -67,22 +67,14 @@ public class SolrRequestParsers {
6767

6868
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
6969

70-
// Should these constants be in a more public place?
71-
public static final String MULTIPART = "multipart";
72-
public static final String FORMDATA = "formdata";
73-
public static final String RAW = "raw";
74-
public static final String SIMPLE = "simple";
75-
public static final String STANDARD = "standard";
76-
7770
private static final Charset CHARSET_US_ASCII = StandardCharsets.US_ASCII;
7871

7972
public static final String INPUT_ENCODING_KEY = "ie";
8073
private static final byte[] INPUT_ENCODING_BYTES = INPUT_ENCODING_KEY.getBytes(CHARSET_US_ASCII);
8174

8275
public static final String REQUEST_TIMER_SERVLET_ATTRIBUTE = "org.apache.solr.RequestTimer";
8376

84-
private final HashMap<String, SolrRequestParser> parsers = new HashMap<>();
85-
private StandardRequestParser standard;
77+
private StandardRequestParser parser;
8678

8779
/**
8880
* Default instance for e.g. admin requests. Limits to 2 MB uploads and does not allow remote
@@ -91,7 +83,7 @@ public class SolrRequestParsers {
9183
public static final SolrRequestParsers DEFAULT = new SolrRequestParsers();
9284

9385
/**
94-
* Pass in an xml configuration. A null configuration will enable everything with maximum values.
86+
* Pass in a xml configuration. A null configuration will enable everything with maximum values.
9587
*/
9688
public SolrRequestParsers(SolrConfig globalConfig) {
9789
final int multipartUploadLimitKB, formUploadLimitKB;
@@ -116,21 +108,12 @@ private void init(int multipartUploadLimitKB, int formUploadLimitKB) {
116108
MultipartRequestParser multi = new MultipartRequestParser(multipartUploadLimitKB);
117109
RawRequestParser raw = new RawRequestParser();
118110
FormDataRequestParser formdata = new FormDataRequestParser(formUploadLimitKB);
119-
standard = new StandardRequestParser(multi, raw, formdata);
120-
121-
// I don't see a need to have this publicly configured just yet
122-
// adding it is trivial
123-
parsers.put(MULTIPART, multi);
124-
parsers.put(FORMDATA, formdata);
125-
parsers.put(RAW, raw);
126-
parsers.put(SIMPLE, new SimpleRequestParser());
127-
parsers.put(STANDARD, standard);
128-
parsers.put("", standard);
111+
parser = new StandardRequestParser(multi, raw, formdata);
129112
}
130113

131114
private static RTimerTree getRequestTimer(HttpServletRequest req) {
132115
final Object reqTimer = req.getAttribute(REQUEST_TIMER_SERVLET_ATTRIBUTE);
133-
if (reqTimer != null && reqTimer instanceof RTimerTree) {
116+
if (reqTimer instanceof RTimerTree) {
134117
return ((RTimerTree) reqTimer);
135118
}
136119

@@ -139,7 +122,6 @@ private static RTimerTree getRequestTimer(HttpServletRequest req) {
139122

140123
public SolrQueryRequest parse(SolrCore core, String path, HttpServletRequest req)
141124
throws Exception {
142-
SolrRequestParser parser = standard;
143125

144126
// TODO -- in the future, we could pick a different parser based on the request
145127

@@ -164,13 +146,12 @@ public SolrQueryRequest parse(SolrCore core, String path, HttpServletRequest req
164146

165147
/** For embedded Solr use; not related to HTTP. */
166148
public SolrQueryRequest buildRequestFrom(
167-
SolrCore core, SolrParams params, Collection<ContentStream> streams) throws Exception {
149+
SolrCore core, SolrParams params, Collection<ContentStream> streams) {
168150
return buildRequestFrom(core, params, streams, new RTimerTree(), null, null);
169151
}
170152

171153
public SolrQueryRequest buildRequestFrom(
172-
SolrCore core, SolrParams params, Collection<ContentStream> streams, Principal principal)
173-
throws Exception {
154+
SolrCore core, SolrParams params, Collection<ContentStream> streams, Principal principal) {
174155
return buildRequestFrom(core, params, streams, new RTimerTree(), null, principal);
175156
}
176157

@@ -181,7 +162,7 @@ private SolrQueryRequest buildRequestFrom(
181162
RTimerTree requestTimer,
182163
final HttpServletRequest req,
183164
final Principal principal) // from req, if req was provided, otherwise from elsewhere
184-
throws Exception {
165+
{
185166
// ensure streams is non-null and mutable so we can easily add to it
186167
if (streams == null) {
187168
streams = new ArrayList<>();
@@ -213,7 +194,7 @@ public List<CommandOperation> getCommands(boolean validateInput) {
213194

214195
@Override
215196
public Map<String, String> getPathTemplateValues() {
216-
if (httpSolrCall != null && httpSolrCall instanceof V2HttpCall) {
197+
if (httpSolrCall instanceof V2HttpCall) {
217198
return ((V2HttpCall) httpSolrCall).getUrlParts();
218199
}
219200
return super.getPathTemplateValues();
@@ -337,9 +318,9 @@ static long parseFormDataContent(
337318
// we have no charset decoder until now, buffer the keys / values for later
338319
// processing:
339320
buffer.add(keyBytes);
340-
buffer.add(Long.valueOf(keyPos));
321+
buffer.add(keyPos);
341322
buffer.add(valueBytes);
342-
buffer.add(Long.valueOf(valuePos));
323+
buffer.add(valuePos);
343324
} else {
344325
// we already have a charsetDecoder, so we can directly decode without buffering:
345326
final String key = decodeChars(keyBytes, keyPos, charsetDecoder),
@@ -457,7 +438,7 @@ private static int digit16(int b) {
457438
// -----------------------------------------------------------------
458439
// -----------------------------------------------------------------
459440

460-
// I guess we don't really even need the interface, but i'll keep it here just for kicks
441+
// I guess we don't really even need the interface, but I'll keep it here just for kicks
461442
interface SolrRequestParser {
462443
public SolrParams parseParamsAndFillStreams(
463444
final HttpServletRequest req, ArrayList<ContentStream> streams) throws Exception;
@@ -466,15 +447,6 @@ public SolrParams parseParamsAndFillStreams(
466447
// -----------------------------------------------------------------
467448
// -----------------------------------------------------------------
468449

469-
/** The simple parser just uses the params directly, does not support POST URL-encoded forms */
470-
static class SimpleRequestParser implements SolrRequestParser {
471-
@Override
472-
public SolrParams parseParamsAndFillStreams(
473-
final HttpServletRequest req, ArrayList<ContentStream> streams) throws Exception {
474-
return parseQueryString(req.getQueryString());
475-
}
476-
}
477-
478450
/** Wrap an HttpServletRequest as a ContentStream */
479451
static class HttpRequestContentStream extends ContentStreamBase {
480452
private final InputStream inputStream;
@@ -515,7 +487,7 @@ public SolrParams parseParamsAndFillStreams(
515487
|| req.getHeader("Transfer-Encoding") != null
516488
|| !NO_BODY_METHODS.contains(req.getMethod())) {
517489
// If Content-Length > 0 OR Transfer-Encoding exists OR
518-
// it's a method that can have a body (POST/PUT/PATCH etc)
490+
// it's a method that can have a body (POST/PUT/PATCH etc.)
519491
streams.add(new HttpRequestContentStream(req, req.getInputStream()));
520492
}
521493

@@ -543,7 +515,7 @@ public SolrParams parseParamsAndFillStreams(
543515
throw new SolrException(
544516
ErrorCode.BAD_REQUEST, "Not multipart content! " + req.getContentType());
545517
}
546-
// Magic way to tell Jetty dynamically we want multi-part processing.
518+
// Magic way to tell Jetty dynamically we want multipart processing.
547519
// This is taken from:
548520
// https://github.com/eclipse/jetty.project/blob/jetty-10.0.12/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java#L144
549521
req.setAttribute("org.eclipse.jetty.multipartConfig", multipartConfigElement);
@@ -735,7 +707,7 @@ static class StandardRequestParser implements SolrRequestParser {
735707
public SolrParams parseParamsAndFillStreams(
736708
final HttpServletRequest req, ArrayList<ContentStream> streams) throws Exception {
737709
String contentType = req.getContentType();
738-
String method = req.getMethod(); // No need to uppercase... HTTP verbs are case sensitive
710+
String method = req.getMethod(); // No need to uppercase... HTTP verbs are case-sensitive
739711
String uri = req.getRequestURI();
740712
boolean isV2 = getHttpSolrCall(req) instanceof V2HttpCall;
741713
boolean isPost = "POST".equals(method);

0 commit comments

Comments
 (0)