Fix get mappings HEAD requests

Get mappings HEAD requests incorrectly return a content-length header of
0. This commit addresses this by removing the special handling for get
mappings HEAD requests, and just relying on the general mechanism that
exists for handling HEAD requests in the REST layer.

Relates #23192
This commit is contained in:
Jason Tedor 2017-06-11 14:58:56 -04:00 committed by GitHub
parent 9b8754e4c2
commit dcf57f296e
6 changed files with 176 additions and 110 deletions

View File

@ -275,7 +275,6 @@ import org.elasticsearch.rest.action.admin.indices.RestRefreshAction;
import org.elasticsearch.rest.action.admin.indices.RestRolloverIndexAction;
import org.elasticsearch.rest.action.admin.indices.RestShrinkIndexAction;
import org.elasticsearch.rest.action.admin.indices.RestSyncedFlushAction;
import org.elasticsearch.rest.action.admin.indices.RestTypesExistsAction;
import org.elasticsearch.rest.action.admin.indices.RestUpdateSettingsAction;
import org.elasticsearch.rest.action.admin.indices.RestUpgradeAction;
import org.elasticsearch.rest.action.admin.indices.RestValidateQueryAction;
@ -547,7 +546,6 @@ public class ActionModule extends AbstractModule {
registerHandler.accept(new RestGetAllAliasesAction(settings, restController));
registerHandler.accept(new RestGetAllMappingsAction(settings, restController));
registerHandler.accept(new RestGetAllSettingsAction(settings, restController, indexScopedSettings, settingsFilter));
registerHandler.accept(new RestTypesExistsAction(settings, restController));
registerHandler.accept(new RestGetIndicesAction(settings, restController, indexScopedSettings, settingsFilter));
registerHandler.accept(new RestIndicesStatsAction(settings, restController));
registerHandler.accept(new RestIndicesSegmentsAction(settings, restController));

View File

@ -19,6 +19,7 @@
package org.elasticsearch.rest.action.admin.indices;
import com.carrotsearch.hppc.cursors.ObjectCursor;
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest;
@ -28,7 +29,9 @@ import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.indices.TypeMissingException;
@ -37,21 +40,33 @@ import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.RestBuilderListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.SortedSet;
import java.util.stream.Collectors;
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestRequest.Method.HEAD;
import static org.elasticsearch.rest.RestStatus.OK;
public class RestGetMappingAction extends BaseRestHandler {
public RestGetMappingAction(Settings settings, RestController controller) {
public RestGetMappingAction(final Settings settings, final RestController controller) {
super(settings);
controller.registerHandler(GET, "/{index}/{type}/_mapping", this);
controller.registerHandler(GET, "/{index}/_mappings", this);
controller.registerHandler(GET, "/{index}/_mapping", this);
controller.registerHandler(GET, "/{index}/_mappings/{type}", this);
controller.registerHandler(GET, "/{index}/_mapping/{type}", this);
controller.registerHandler(HEAD, "/{index}/_mapping/{type}", this);
controller.registerHandler(GET, "/_mapping/{type}", this);
}
@ -64,48 +79,87 @@ public class RestGetMappingAction extends BaseRestHandler {
public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
final String[] indices = Strings.splitStringByCommaToArray(request.param("index"));
final String[] types = request.paramAsStringArrayOrEmptyIfAll("type");
GetMappingsRequest getMappingsRequest = new GetMappingsRequest();
final GetMappingsRequest getMappingsRequest = new GetMappingsRequest();
getMappingsRequest.indices(indices).types(types);
getMappingsRequest.indicesOptions(IndicesOptions.fromRequest(request, getMappingsRequest.indicesOptions()));
getMappingsRequest.local(request.paramAsBoolean("local", getMappingsRequest.local()));
return channel -> client.admin().indices().getMappings(getMappingsRequest, new RestBuilderListener<GetMappingsResponse>(channel) {
@Override
public RestResponse buildResponse(GetMappingsResponse response, XContentBuilder builder) throws Exception {
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappingsByIndex = response.getMappings();
if (mappingsByIndex.isEmpty()) {
if (indices.length != 0 && types.length != 0) {
return new BytesRestResponse(OK, builder.startObject().endObject());
} else if (indices.length != 0) {
public RestResponse buildResponse(final GetMappingsResponse response, final XContentBuilder builder) throws Exception {
final ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappingsByIndex = response.getMappings();
if (mappingsByIndex.isEmpty() && (indices.length != 0 || types.length != 0)) {
if (indices.length != 0 && types.length == 0) {
builder.close();
return new BytesRestResponse(channel, new IndexNotFoundException(indices[0]));
} else if (types.length != 0) {
builder.close();
return new BytesRestResponse(channel, new TypeMissingException("_all", types[0]));
return new BytesRestResponse(channel, new IndexNotFoundException(String.join(",", indices)));
} else {
return new BytesRestResponse(OK, builder.startObject().endObject());
builder.close();
return new BytesRestResponse(channel, new TypeMissingException("_all", String.join(",", types)));
}
}
final Set<String> typeNames = new HashSet<>();
for (final ObjectCursor<ImmutableOpenMap<String, MappingMetaData>> cursor : mappingsByIndex.values()) {
for (final ObjectCursor<String> inner : cursor.value.keys()) {
typeNames.add(inner.value);
}
}
final SortedSet<String> difference = Sets.sortedDifference(Arrays.stream(types).collect(Collectors.toSet()), typeNames);
// now remove requested aliases that contain wildcards that are simple matches
final List<String> matches = new ArrayList<>();
outer:
for (final String pattern : difference) {
if (pattern.contains("*")) {
for (final String typeName : typeNames) {
if (Regex.simpleMatch(pattern, typeName)) {
matches.add(pattern);
continue outer;
}
}
}
}
difference.removeAll(matches);
final RestStatus status;
builder.startObject();
for (ObjectObjectCursor<String, ImmutableOpenMap<String, MappingMetaData>> indexEntry : mappingsByIndex) {
builder.startObject(indexEntry.key);
builder.startObject(Fields.MAPPINGS);
for (ObjectObjectCursor<String, MappingMetaData> typeEntry : indexEntry.value) {
builder.field(typeEntry.key);
builder.map(typeEntry.value.sourceAsMap());
{
if (difference.isEmpty()) {
status = RestStatus.OK;
} else {
status = RestStatus.NOT_FOUND;
final String message;
if (difference.size() == 1) {
message = String.format(Locale.ROOT, "type [%s] missing", toNamesString(difference.iterator().next()));
} else {
message = String.format(Locale.ROOT, "types [%s] missing", toNamesString(difference.toArray(new String[0])));
}
builder.field("error", message);
builder.field("status", status.getStatus());
}
builder.endObject();
builder.endObject();
}
for (final ObjectObjectCursor<String, ImmutableOpenMap<String, MappingMetaData>> indexEntry : mappingsByIndex) {
builder.startObject(indexEntry.key);
{
builder.startObject("mappings");
{
for (final ObjectObjectCursor<String, MappingMetaData> typeEntry : indexEntry.value) {
builder.field(typeEntry.key, typeEntry.value.sourceAsMap());
}
}
builder.endObject();
}
builder.endObject();
}
}
builder.endObject();
return new BytesRestResponse(OK, builder);
return new BytesRestResponse(status, builder);
}
});
}
static class Fields {
static final String MAPPINGS = "mappings";
private static String toNamesString(final String... names) {
return Arrays.stream(names).collect(Collectors.joining(","));
}
}

View File

@ -1,75 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.elasticsearch.rest.action.admin.indices;
import org.elasticsearch.action.admin.indices.exists.types.TypesExistsRequest;
import org.elasticsearch.action.admin.indices.exists.types.TypesExistsResponse;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.action.RestResponseListener;
import java.io.IOException;
import static org.elasticsearch.rest.RestRequest.Method.HEAD;
import static org.elasticsearch.rest.RestStatus.NOT_FOUND;
import static org.elasticsearch.rest.RestStatus.OK;
/**
* Rest api for checking if a type exists.
*/
public class RestTypesExistsAction extends BaseRestHandler {
public RestTypesExistsAction(Settings settings, RestController controller) {
super(settings);
controller.registerWithDeprecatedHandler(
HEAD, "/{index}/_mapping/{type}", this,
HEAD, "/{index}/{type}", deprecationLogger);
}
@Override
public String getName() {
return "types_exists_action";
}
@Override
public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
TypesExistsRequest typesExistsRequest = new TypesExistsRequest(
Strings.splitStringByCommaToArray(request.param("index")), Strings.splitStringByCommaToArray(request.param("type"))
);
typesExistsRequest.local(request.paramAsBoolean("local", typesExistsRequest.local()));
typesExistsRequest.indicesOptions(IndicesOptions.fromRequest(request, typesExistsRequest.indicesOptions()));
return channel -> client.admin().indices().typesExists(typesExistsRequest, new RestResponseListener<TypesExistsResponse>(channel) {
@Override
public RestResponse buildResponse(TypesExistsResponse response) throws Exception {
if (response.isExists()) {
return new BytesRestResponse(OK, BytesRestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY);
} else {
return new BytesRestResponse(NOT_FOUND, BytesRestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY);
}
}
});
}
}

View File

@ -23,9 +23,9 @@ following are some examples:
[source,js]
--------------------------------------------------
GET /_mapping/tweet,kimchy
GET /_mapping/tweet
GET /_all/_mapping/tweet,book
GET /_all/_mapping/tweet
--------------------------------------------------
// CONSOLE
// TEST[setup:twitter]

View File

@ -76,8 +76,14 @@ public class Netty4HeadBodyIsEmptyIT extends ESRestTestCase {
public void testTypeExists() throws IOException {
createTestDoc();
headTestCase("/test/test", emptyMap(), equalTo(0));
headTestCase("/test/test", singletonMap("pretty", "true"), equalTo(0));
headTestCase("/test/_mapping/test", emptyMap(), greaterThan(0));
headTestCase("/test/_mapping/test", singletonMap("pretty", "true"), greaterThan(0));
}
public void testTypeDoesNotExist() throws IOException {
createTestDoc();
headTestCase("/test/_mapping/does-not-exist", emptyMap(), NOT_FOUND.getStatus(), greaterThan(0));
headTestCase("/text/_mapping/test,does-not-exist", emptyMap(), NOT_FOUND.getStatus(), greaterThan(0));
}
public void testAliasExists() throws IOException {

View File

@ -1,5 +1,8 @@
---
"Return empty response when type doesn't exist":
"Non-existent type returns 404":
- skip:
version: " - 5.99.99"
reason: Previous versions did not 404 on missing types
- do:
indices.create:
index: test_index
@ -12,11 +15,91 @@
analyzer: whitespace
- do:
catch: missing
indices.get_mapping:
index: test_index
type: not_test_type
- match: { '': {}}
- match: { status: 404 }
- match: { error.reason: 'type[[not_test_type]] missing' }
---
"No type matching pattern returns 404":
- skip:
version: " - 5.99.99"
reason: Previous versions did not 404 on missing types
- do:
indices.create:
index: test_index
body:
mappings:
test_type:
properties:
text:
type: text
analyzer: whitespace
- do:
catch: missing
indices.get_mapping:
index: test_index
type: test*,not*
- match: { status: 404 }
- match: { error: 'type [not*] missing' }
- is_true: test_index.mappings.test_type
---
"Existent and non-existent type returns 404 and the existing type":
- skip:
version: " - 5.99.99"
reason: Previous versions did not 404 on missing types
- do:
indices.create:
index: test_index
body:
mappings:
test_type:
properties:
text:
type: text
analyzer: whitespace
- do:
catch: missing
indices.get_mapping:
index: test_index
type: test_type,not_test_type
- match: { status: 404 }
- match: { error: 'type [not_test_type] missing' }
- is_true: test_index.mappings.test_type
---
"Existent and non-existent types returns 404 and the existing type":
- skip:
version: " - 5.99.99"
reason: Previous versions did not 404 on missing types
- do:
indices.create:
index: test_index
body:
mappings:
test_type:
properties:
text:
type: text
analyzer: whitespace
- do:
catch: missing
indices.get_mapping:
index: test_index
type: test_type,not_test_type,another_not_test_type
- match: { status: 404 }
- match: { error: 'types [another_not_test_type,not_test_type] missing' }
- is_true: test_index.mappings.test_type
---
"Type missing when no types exist":