fix(gateway-platforms-*) Unify handling of forward slashes in paths when connecting to backend.
test(apiman-gateways-test) Test backend connector behaviour when API endpoint has no slash.
docs(gateway-platforms-*) Minor javadoc fixes.
MODIFY
common/util/src/main/java/io/apiman/common/util/ApimanPathUtils.java
common/util/src/main/java/io/apiman/common/util/ApimanPathUtils.java
*/
package io.apiman.common.util;
+import org.apache.commons.lang3.StringUtils;
+
+@SuppressWarnings("nls")
public class ApimanPathUtils {
- public static final String X_API_VERSION_HEADER = "X-API-Version"; //$NON-NLS-1$
- public static final String ACCEPT_HEADER = "Accept"; //$NON-NLS-1$
+ public static final String X_API_VERSION_HEADER = "X-API-Version";
+ public static final String ACCEPT_HEADER = "Accept";
private ApimanPathUtils() {
}
@@ -26,25 +29,23 @@ public class ApimanPathUtils {
/**
* Parses the HTTP request and returns an object containing all of the API
* information (Org, Id, Version).
- * @param apiVersionHeader
- * @param acceptHeader
- * @param pathInfo
+ * @param apiVersionHeader the API Version header
+ * @param acceptHeader the accept header
+ * @param pathInfo the path info
+ * @return the parsed object containing API information.
*/
public static final ApiRequestPathInfo parseApiRequestPath(String apiVersionHeader, String acceptHeader, String pathInfo) {
- //String pathInfo = request.getPathInfo();
ApiRequestPathInfo info = new ApiRequestPathInfo();
boolean versionFound = false;
- //String apiVersionHeader = request.getHeader("X-API-Version"); //$NON-NLS-1$
if (apiVersionHeader != null && apiVersionHeader.trim().length() > 0) {
info.apiVersion = apiVersionHeader;
versionFound = true;
} else {
- //String acceptHeader = request.getHeader("Accept"); //$NON-NLS-1$
- if (acceptHeader != null && acceptHeader.startsWith("application/apiman.")) { //$NON-NLS-1$
- String [] split = acceptHeader.split("\\+"); //$NON-NLS-1$
- info.apiVersion = split[0].substring("application/apiman.".length()); //$NON-NLS-1$
+ if (acceptHeader != null && acceptHeader.startsWith("application/apiman.")) {
+ String [] split = acceptHeader.split("\\+");
+ info.apiVersion = split[0].substring("application/apiman.".length());
versionFound = true;
}
}
@@ -52,7 +53,7 @@ public class ApimanPathUtils {
int minParts = versionFound ? 3 : 4;
if (pathInfo != null) {
- String[] split = pathInfo.split("/"); //$NON-NLS-1$
+ String[] split = pathInfo.split("/");
if (split.length >= minParts) {
info.orgId = split[1];
info.apiId = split[2];
@@ -65,7 +66,7 @@ public class ApimanPathUtils {
resource.append('/');
resource.append(urlEncode(split[idx]));
}
- if (pathInfo.endsWith("/")) { //$NON-NLS-1$
+ if (pathInfo.endsWith("/")) {
resource.append('/');
}
info.resource = resource.toString();
@@ -78,13 +79,34 @@ public class ApimanPathUtils {
}
/**
- * @param string
+ * @param string string to replace # with %23
+ * @return the encoded string
*/
- @SuppressWarnings("nls")
public static String urlEncode(String string) {
return string.replace("#", "%23");
}
+ /**
+ * Join endpoint and path with sensible / behaviour.
+ *
+ * @param endpoint the endpoint
+ * @param path the destination (path)
+ * @return the joined endpoint + destination.
+ */
+ public static String join(String endpoint, String path) {
+ if (endpoint == null || endpoint.isEmpty())
+ return path;
+ if (path == null || path.isEmpty())
+ return endpoint;
+
+ if (StringUtils.endsWith(endpoint, "/") && path.startsWith("/")) {
+ return endpoint + path.substring(1);
+ } else if (StringUtils.endsWith(endpoint, "/") ^ path.startsWith("/")) {
+ return endpoint + path;
+ }
+ return endpoint + "/" + path;
+ }
+
/**
* Parsed API request path information.
*/

MODIFY
gateway/platforms/servlet/src/main/java/io/apiman/gateway/platforms/servlet/connectors/HttpApiConnection.java
gateway/platforms/servlet/src/main/java/io/apiman/gateway/platforms/servlet/connectors/HttpApiConnection.java
package io.apiman.gateway.platforms.servlet.connectors;
import io.apiman.common.config.options.BasicAuthOptions;
+import io.apiman.common.util.ApimanPathUtils;
import io.apiman.gateway.engine.IApiConnection;
import io.apiman.gateway.engine.IApiConnectionResponse;
import io.apiman.gateway.engine.async.AsyncResultImpl;
@@ -137,13 +138,8 @@ public class HttpApiConnection implements IApiConnection, IApiConnectionResponse
final Set<String> suppressedHeaders = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
suppressedHeaders.addAll(SUPPRESSED_REQUEST_HEADERS);
- String endpoint = api.getEndpoint();
- if (endpoint.endsWith("/")) { //$NON-NLS-1$
- endpoint = endpoint.substring(0, endpoint.length() - 1);
- }
- if (request.getDestination() != null) {
- endpoint += request.getDestination();
- }
+ String endpoint = ApimanPathUtils.join(api.getEndpoint(), request.getDestination());
+
if (request.getQueryParams() != null && !request.getQueryParams().isEmpty()) {
String delim = "?"; //$NON-NLS-1$
for (Entry<String, String> entry : request.getQueryParams()) {

MODIFY
gateway/platforms/vertx3/vertx3/src/main/java/io/apiman/gateway/platforms/vertx3/connector/HttpConnector.java
gateway/platforms/vertx3/vertx3/src/main/java/io/apiman/gateway/platforms/vertx3/connector/HttpConnector.java
@@ -17,6 +17,7 @@ package io.apiman.gateway.platforms.vertx3.connector;
import io.apiman.common.config.options.BasicAuthOptions;
import io.apiman.common.config.options.TLSOptions;
+import io.apiman.common.util.ApimanPathUtils;
import io.apiman.common.util.Basic;
import io.apiman.gateway.engine.IApiConnection;
import io.apiman.gateway.engine.IApiConnectionResponse;
@@ -56,8 +57,6 @@ import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
-import org.apache.commons.lang3.StringUtils;
-
/**
* A vert.x-based HTTP connector; implementing both {@link ISignalReadStream} and {@link ISignalWriteStream}.
*
@@ -166,7 +165,7 @@ class HttpConnector implements IApiConnectionResponse, IApiConnection {
}
private void doConnection() {
- String endpoint = join(apiPath, destination + queryParams(apiRequest.getQueryParams()));
+ String endpoint = ApimanPathUtils.join(apiPath, destination + queryParams(apiRequest.getQueryParams()));
logger.debug(String.format("Connecting to %s | port: %d verb: %s path: %s", apiHost, apiPort,
HttpMethod.valueOf(apiRequest.getType()), endpoint));
@@ -217,15 +216,6 @@ class HttpConnector implements IApiConnectionResponse, IApiConnection {
}
}
- private String join(String a, String b) {
- if (StringUtils.endsWith(a, "/") && b.startsWith("/")) {
- return a + b.substring(1);
- } else if (StringUtils.endsWith(a, "/") ^ b.startsWith("/")) {
- return a + b;
- }
- return a + "/" + b;
- }
-
private void addMandatoryRequestHeaders(MultiMap headers) {
String port = apiEndpoint.getPort() == -1 ? "" : ":" + apiEndpoint.getPort();
headers.add("Host", apiEndpoint.getHost() + port);

MODIFY
gateway/test/src/test/resources/test-plan-data/simple/simple-echo-1227/003-echo-2.resttest
gateway/test/src/test/resources/test-plan-data/simple/simple-echo-1227/003-echo-2.resttest
@@ -7,8 +7,8 @@ Content-Type: application/json
{
"method" : "GET",
- "resource" : "/endpoint/with-trailing-slash",
- "uri" : "/endpoint/with-trailing-slash",
+ "resource" : "/endpoint/with-trailing-slash/",
+ "uri" : "/endpoint/with-trailing-slash/",
"headers" : {
}
}

ADD
gateway/test/src/test/resources/test-plan-data/simple/simple-echo-1227/noslash-001-publish-api.resttest
+PUT /apis admin/admin
+Content-Type: application/json
+
+{
+ "organizationId" : "SimpleEcho1227Test",
+ "apiId" : "echo-noslash",
+ "version" : "1.0",
+ "publicAPI" : true,
+ "endpointType" : "REST",
+ "endpoint" : "${apiman-gateway-test.endpoints.echo}/endpoint/without-trailing-slash",
+ "apiPolicies" : [ ]
+}
+----
+204

ADD
gateway/test/src/test/resources/test-plan-data/simple/simple-echo-1227/ns-002-echo.resttest
+GET /SimpleEcho1227Test/echo-noslash/1.0/ admin/admin
+Accept: application/json
+
+----
+200
+Content-Type: application/json
+
+{
+ "method" : "GET",
+ "resource" : "/endpoint/without-trailing-slash/",
+ "uri" : "/endpoint/without-trailing-slash/",
+ "headers" : {
+ }
+}

ADD
gateway/test/src/test/resources/test-plan-data/simple/simple-echo-1227/ns-003-echo-2.resttest
+GET /SimpleEcho1227Test/echo-noslash/1.0 admin/admin
+Accept: application/json
+
+----
+200
+Content-Type: application/json
+
+{
+ "method" : "GET",
+ "resource" : "/endpoint/without-trailing-slash",
+ "uri" : "/endpoint/without-trailing-slash",
+ "headers" : {
+ }
+}

ADD
gateway/test/src/test/resources/test-plan-data/simple/simple-echo-1227/ns-004-echo-3.resttest
+GET /SimpleEcho1227Test/echo-noslash/1.0/some/resource admin/admin
+Accept: application/json
+
+----
+200
+Content-Type: application/json
+
+{
+ "method" : "GET",
+ "resource" : "/endpoint/without-trailing-slash/some/resource",
+ "uri" : "/endpoint/without-trailing-slash/some/resource",
+ "headers" : {
+ }
+}

ADD
gateway/test/src/test/resources/test-plan-data/simple/simple-echo-1227/ns-005-echo-4.resttest
+GET /SimpleEcho1227Test/echo-noslash/1.0/some/resource/ admin/admin
+Accept: application/json
+
+----
+200
+Content-Type: application/json
+
+{
+ "method" : "GET",
+ "resource" : "/endpoint/without-trailing-slash/some/resource/",
+ "uri" : "/endpoint/without-trailing-slash/some/resource/",
+ "headers" : {
+ }
+}

MODIFY
gateway/test/src/test/resources/test-plans/simple/simple-echo-1227-testPlan.xml
gateway/test/src/test/resources/test-plans/simple/simple-echo-1227-testPlan.xml
<testGroup name="setup" endpoint="api">
<test name="Publish Api">test-plan-data/simple/simple-echo-1227/001-publish-api.resttest</test>
+ <test name="Publish Api (No Final Slash API)">test-plan-data/simple/simple-echo-1227/noslash-001-publish-api.resttest</test>
</testGroup>
- <testGroup name="test">
+ <testGroup name="slash-test">
<test name="Echo 1">test-plan-data/simple/simple-echo-1227/002-echo.resttest</test>
<test name="Echo 2">test-plan-data/simple/simple-echo-1227/003-echo-2.resttest</test>
<test name="Echo 3">test-plan-data/simple/simple-echo-1227/004-echo-3.resttest</test>
<test name="Echo 4">test-plan-data/simple/simple-echo-1227/005-echo-4.resttest</test>
</testGroup>
+
+ <testGroup name="noslash-test">
+ <test name="Echo NS 1">test-plan-data/simple/simple-echo-1227/ns-002-echo.resttest</test>
+ <test name="Echo NS 2">test-plan-data/simple/simple-echo-1227/ns-003-echo-2.resttest</test>
+ <test name="Echo NS 3">test-plan-data/simple/simple-echo-1227/ns-004-echo-3.resttest</test>
+ <test name="Echo NS 4">test-plan-data/simple/simple-echo-1227/ns-005-echo-4.resttest</test>
+ </testGroup>
</testPlan>

fix(gateway-platforms-*) Unify handling of forward slashes in paths when connecting to backend.

test(apiman-gateways-test) Test backend connector behaviour when API endpoint has no slash.

docs(gateway-platforms-*) Minor javadoc fixes.