This is an automated email from the ASF dual-hosted git repository. uce pushed a commit to branch release-1.19 in repository https://gitbox.apache.org/repos/asf/flink.git
commit f6caf4e61114a520a20dff539111c23d36122fe7 Author: Ufuk Celebi <[email protected]> AuthorDate: Tue May 28 09:21:30 2024 +0200 [FLINK-26808][rest] Add MultipartRoutes utility --- .../rest/handler/router/MultipartRoutes.java | 103 +++++++++++++++++++++ .../runtime/rest/handler/router/PathPattern.java | 5 + .../flink/runtime/rest/handler/router/Router.java | 2 +- .../rest/handler/router/MultipartRoutesTest.java | 54 +++++++++++ 4 files changed, 163 insertions(+), 1 deletion(-) diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/router/MultipartRoutes.java b/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/router/MultipartRoutes.java new file mode 100644 index 00000000000..46d4d327483 --- /dev/null +++ b/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/router/MultipartRoutes.java @@ -0,0 +1,103 @@ +/* + * 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.flink.runtime.rest.handler.router; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A utility for {@link org.apache.flink.runtime.rest.FileUploadHandler} to determine whether it + * should accept a request. + */ +public class MultipartRoutes { + + private final List<PathPattern> postRoutes; + private final List<PathPattern> fileUploadRoutes; + + private MultipartRoutes(List<PathPattern> postRoutes, List<PathPattern> fileUploadRoutes) { + this.postRoutes = new ArrayList<>(postRoutes); + this.fileUploadRoutes = new ArrayList<>(fileUploadRoutes); + } + + /** + * Returns <code>true</code> if the handler at the provided <code>requestUri</code> endpoint + * accepts POST requests. + * + * @param requestUri URI for the request + */ + public boolean isPostRoute(String requestUri) { + return checkRoutes(requestUri, postRoutes); + } + + /** + * Returns <code>true</code> if the handler at the provided <code>requestUri</code> endpoint + * accepts file uploads. + * + * @param requestUri URI for the request + */ + public boolean isFileUploadRoute(String requestUri) { + return checkRoutes(requestUri, fileUploadRoutes); + } + + @Override + public String toString() { + return "MultipartRoutes{" + + "postRoutes=" + + postRoutes + + ", fileUploadRoutes=" + + fileUploadRoutes + + '}'; + } + + private boolean checkRoutes(String requestUri, List<PathPattern> routes) { + String[] pathTokens = Router.decodePathTokens(requestUri); + Map<String, String> params = new HashMap<>(); + for (PathPattern route : routes) { + if (route.match(pathTokens, params)) { + return true; + } + } + return false; + } + + /** Builder for {@link MultipartRoutes}. */ + public static class Builder { + + private final List<PathPattern> postRoutes = new ArrayList<>(); + private final List<PathPattern> fileUploadRoutes = new ArrayList<>(); + + public Builder() {} + + public MultipartRoutes.Builder addPostRoute(String pattern) { + postRoutes.add(new PathPattern(pattern)); + return this; + } + + public MultipartRoutes.Builder addFileUploadRoute(String pattern) { + fileUploadRoutes.add(new PathPattern(pattern)); + return this; + } + + public MultipartRoutes build() { + return new MultipartRoutes(postRoutes, fileUploadRoutes); + } + } +} diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/router/PathPattern.java b/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/router/PathPattern.java index a2a3817922d..a1a0c91517d 100644 --- a/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/router/PathPattern.java +++ b/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/router/PathPattern.java @@ -93,6 +93,11 @@ final class PathPattern { return tokens; } + @Override + public String toString() { + return pattern; + } + // -------------------------------------------------------------------------- // Instances of this class can be conveniently used as Map keys. diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/router/Router.java b/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/router/Router.java index 2faa37b9725..664b7d90ba9 100644 --- a/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/router/Router.java +++ b/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/router/Router.java @@ -235,7 +235,7 @@ public class Router<T> { // -------------------------------------------------------------------------- - private String[] decodePathTokens(String uri) { + static String[] decodePathTokens(String uri) { // Need to split the original URI (instead of QueryStringDecoder#path) then decode the // tokens (components), // otherwise /test1/123%2F456 will not match /test1/:p1 diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/rest/handler/router/MultipartRoutesTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/rest/handler/router/MultipartRoutesTest.java new file mode 100644 index 00000000000..b93cb981422 --- /dev/null +++ b/flink-runtime/src/test/java/org/apache/flink/runtime/rest/handler/router/MultipartRoutesTest.java @@ -0,0 +1,54 @@ +/* + * 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.flink.runtime.rest.handler.router; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** Tests for {@link MultipartRoutes}. */ +public class MultipartRoutesTest { + + /** Test that multipart routes work with regular path patterns followed by {@link Router}. */ + @Test + public void testRoutePatternMatching() { + MultipartRoutes.Builder builder = new MultipartRoutes.Builder(); + builder.addPostRoute("/jobs"); + builder.addPostRoute("/jobs/:jobid/stop"); + builder.addPostRoute("/jobs/:jobid/savepoints/:savepointid/delete"); + + builder.addFileUploadRoute("/jobs"); + builder.addFileUploadRoute("/jobs/:jobid/stop"); + builder.addFileUploadRoute("/jobs/:jobid/savepoints/:savepointid/delete"); + + MultipartRoutes routes = builder.build(); + + assertThat(routes.isPostRoute("/jobs")).isTrue(); + assertThat(routes.isPostRoute("/jobs?q1=p1&q2=p2")).isTrue(); + assertThat(routes.isPostRoute("/jobs/abc")).isFalse(); + assertThat(routes.isPostRoute("/jobs/abc/stop")).isTrue(); + assertThat(routes.isPostRoute("/jobs/abc/savepoints/def/delete")).isTrue(); + + assertThat(routes.isFileUploadRoute("/jobs")).isTrue(); + assertThat(routes.isFileUploadRoute("/jobs?q1=p1&q2=p2")).isTrue(); + assertThat(routes.isFileUploadRoute("/jobs/abc")).isFalse(); + assertThat(routes.isFileUploadRoute("/jobs/abc/stop")).isTrue(); + assertThat(routes.isFileUploadRoute("/jobs/abc/savepoints/def/delete")).isTrue(); + } +}
