summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Marius Venstad <jonmv@gmail.com>2022-04-06 19:35:30 +0200
committerJon Marius Venstad <jonmv@gmail.com>2022-04-06 19:35:30 +0200
commit039589faf5f989d80b9fec2b28ed955ac6fd86f6 (patch)
tree45c314cc9ede2d5c26a5d6b4f030ad3db2246a91
parentec92b5f8882e400f94b851dffcf0b3511373e890 (diff)
Use HttpURL.Path for Path.getRest()
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java20
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/application/HttpProxy.java21
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentHandler.java9
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentRequest.java11
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java21
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandler.java3
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/ApplicationContentRequest.java3
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/SessionContentRequestV2.java15
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/serviceview/StateRequestHandler.java17
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/application/HttpProxyTest.java5
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/HandlerTest.java2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java17
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java4
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java3
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/serviceview/StateRequestHandlerTest.java5
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/HttpURL.java21
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/Path.java16
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApi.java4
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java8
-rw-r--r--container-core/src/test/java/com/yahoo/restapi/HttpURLTest.java5
-rw-r--r--container-core/src/test/java/com/yahoo/restapi/PathTest.java8
-rw-r--r--container-search-gui/src/main/java/com/yahoo/search/query/gui/GUIHandler.java26
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java40
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java17
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java45
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java26
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java21
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java10
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImplTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java15
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java9
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java8
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java11
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java2
-rw-r--r--vespajlib/abi-spec.json1
-rw-r--r--vespajlib/src/main/java/com/yahoo/path/Path.java5
-rw-r--r--vespajlib/src/main/java/com/yahoo/text/Text.java1
39 files changed, 256 insertions, 215 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
index cb1c1a461e3..76d7ff2fc7f 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
@@ -31,6 +31,7 @@ import com.yahoo.docproc.jdisc.metric.NullMetric;
import com.yahoo.io.IOUtils;
import com.yahoo.jdisc.Metric;
import com.yahoo.path.Path;
+import com.yahoo.restapi.HttpURL;
import com.yahoo.slime.Slime;
import com.yahoo.transaction.NestedTransaction;
import com.yahoo.transaction.Transaction;
@@ -558,27 +559,24 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
}
}
- public HttpResponse serviceStatusPage(ApplicationId applicationId, String hostName, String serviceName, String pathSuffix) {
+ public HttpResponse serviceStatusPage(ApplicationId applicationId, String hostName, String serviceName, HttpURL.Path pathSuffix) {
// WARNING: pathSuffix may be given by the external user. Make sure no security issues arise...
// We should be OK here, because at most, pathSuffix may change the parent path, but cannot otherwise
// change the hostname and port. Exposing other paths on the cluster controller should be fine.
// TODO: It would be nice to have a simple check to verify pathSuffix doesn't contain /../ components.
- String pathPrefix;
+ HttpURL.Path pathPrefix = HttpURL.Path.empty();
switch (serviceName) {
- case "container-clustercontroller": {
- pathPrefix = "clustercontroller-status/v1/";
+ case "container-clustercontroller":
+ pathPrefix = pathPrefix.append("clustercontroller-status").append("v1");
break;
- }
case "distributor":
- case "storagenode": {
- pathPrefix = "";
+ case "storagenode":
break;
- }
default:
throw new NotFoundException("No status page for service: " + serviceName);
}
- return httpProxy.get(getApplication(applicationId), hostName, serviceName, pathPrefix + pathSuffix);
+ return httpProxy.get(getApplication(applicationId), hostName, serviceName, pathPrefix.append(pathSuffix));
}
public Map<String, ClusterReindexing> getClusterReindexingStatus(ApplicationId applicationId) {
@@ -659,9 +657,9 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
return getOptionalApplication(applicationId).map(app -> app.getModel().fileReferences()).orElse(Set.of());
}
- public ApplicationFile getApplicationFileFromSession(TenantName tenantName, long sessionId, String path, Session.Mode mode) {
+ public ApplicationFile getApplicationFileFromSession(TenantName tenantName, long sessionId, HttpURL.Path path, Session.Mode mode) {
Tenant tenant = tenantRepository.getTenant(tenantName);
- return getLocalSession(tenant, sessionId).getApplicationFile(Path.fromString(path), mode);
+ return getLocalSession(tenant, sessionId).getApplicationFile(Path.from(path.segments()), mode);
}
public Tenant getTenant(ApplicationId applicationId) {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/HttpProxy.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/HttpProxy.java
index a8915d187f3..14acd1b5630 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/HttpProxy.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/HttpProxy.java
@@ -10,8 +10,14 @@ import com.yahoo.container.jdisc.HttpResponse;
import java.util.HashSet;
import java.util.List;
import java.util.logging.Level;
+
+import com.yahoo.net.DomainName;
+import com.yahoo.restapi.HttpURL;
+import com.yahoo.restapi.HttpURL.Path;
+import com.yahoo.restapi.HttpURL.Scheme;
import com.yahoo.vespa.config.server.http.HttpErrorResponse;
import com.yahoo.vespa.config.server.http.HttpFetcher;
+import com.yahoo.vespa.config.server.http.HttpFetcher.Params;
import com.yahoo.vespa.config.server.http.NotFoundException;
import com.yahoo.vespa.config.server.http.SimpleHttpFetcher;
@@ -33,7 +39,7 @@ public class HttpProxy {
this.fetcher = fetcher;
}
- public HttpResponse get(Application application, String hostName, String serviceType, String relativePath) {
+ public HttpResponse get(Application application, String hostName, String serviceType, Path relativePath) {
HostInfo host = application.getModel().getHosts().stream()
.filter(hostInfo -> hostInfo.getHostname().equals(hostName))
.findFirst()
@@ -54,18 +60,15 @@ public class HttpProxy {
return internalGet(host.getHostname(), port.getPort(), relativePath);
}
- private HttpResponse internalGet(String hostname, int port, String relativePath) {
- String urlString = "http://" + hostname + ":" + port + "/" + relativePath;
- URL url;
+ private HttpResponse internalGet(String hostname, int port, Path relativePath) {
+ HttpURL url = HttpURL.create(Scheme.http, DomainName.of(hostname), port, relativePath);
try {
- url = new URL(urlString);
+ return fetcher.get(new Params(2000), // 2_000 ms read timeout
+ url.asURI().toURL());
} catch (MalformedURLException e) {
- logger.log(Level.WARNING, "Badly formed url: " + urlString, e);
+ logger.log(Level.WARNING, "Badly formed url: " + url, e);
return HttpErrorResponse.internalServerError("Failed to construct URL for backend");
}
-
- HttpFetcher.Params params = new HttpFetcher.Params(2000); // 2_000 ms read timeout
- return fetcher.get(params, url);
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentHandler.java
index 13acc121fa0..0ceb459233b 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentHandler.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.config.server.http;
import com.yahoo.config.application.api.ApplicationFile;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
@@ -32,7 +33,7 @@ public class ContentHandler {
public HttpResponse put(ContentRequest request) {
ApplicationFile file = request.getFile();
- if (request.getPath().endsWith("/")) {
+ if (request.getPath().hasTrailingSlash()) {
createDirectory(request, file);
} else {
createFile(request, file);
@@ -62,9 +63,9 @@ public class ContentHandler {
return new SessionContentStatusResponse(file, urlBase);
}
- private static List<ApplicationFile> listSortedFiles(ApplicationFile file, String path, boolean recursive) {
- if (!path.isEmpty() && !path.endsWith("/")) {
- return Arrays.asList(file);
+ private static List<ApplicationFile> listSortedFiles(ApplicationFile file, Path path, boolean recursive) {
+ if ( ! path.segments().isEmpty() && ! path.hasTrailingSlash()) {
+ return List.of(file);
}
List<ApplicationFile> files = file.listFiles(recursive);
Collections.sort(files);
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentRequest.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentRequest.java
index 995c4b2dc56..f06e1dabf8c 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentRequest.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/ContentRequest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.config.server.http;
import com.yahoo.config.application.api.ApplicationFile;
import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.restapi.HttpURL.Path;
import java.io.InputStream;
@@ -20,11 +21,11 @@ public abstract class ContentRequest {
enum ReturnType {CONTENT, STATUS}
private final long sessionId;
- private final String path;
+ private final Path path;
private final ApplicationFile file;
private final HttpRequest request;
- protected ContentRequest(HttpRequest request, long sessionId, String path, ApplicationFile applicationFile) {
+ protected ContentRequest(HttpRequest request, long sessionId, Path path, ApplicationFile applicationFile) {
this.request = request;
this.sessionId = sessionId;
this.path = path;
@@ -77,7 +78,7 @@ public abstract class ContentRequest {
}
- String getPath() {
+ Path getPath() {
return path;
}
@@ -86,8 +87,8 @@ public abstract class ContentRequest {
}
ApplicationFile getExistingFile() {
- if (!file.exists()) {
- throw new NotFoundException("Session " + sessionId + " does not contain a file '" + path + "'");
+ if ( ! file.exists()) {
+ throw new NotFoundException("Session " + sessionId + " does not contain a file at " + path);
}
return file;
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
index 0707261bb45..885456ff69c 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
@@ -16,6 +16,7 @@ import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.io.IOUtils;
import com.yahoo.jdisc.Response;
import com.yahoo.restapi.ErrorResponse;
+import com.yahoo.restapi.HttpURL;
import com.yahoo.restapi.MessageResponse;
import com.yahoo.restapi.Path;
import com.yahoo.slime.Cursor;
@@ -132,23 +133,23 @@ public class ApplicationHandler extends HttpHandler {
return HttpServiceResponse.createResponse(response, hostAndPort, request.getUri());
}
- private HttpResponse serviceStatusPage(ApplicationId applicationId, String service, String hostname, String pathSuffix) {
+ private HttpResponse serviceStatusPage(ApplicationId applicationId, String service, String hostname, HttpURL.Path pathSuffix) {
return applicationRepository.serviceStatusPage(applicationId, hostname, service, pathSuffix);
}
- private HttpResponse content(ApplicationId applicationId, String contentPath, HttpRequest request) {
+ private HttpResponse content(ApplicationId applicationId, HttpURL.Path contentPath, HttpRequest request) {
long sessionId = applicationRepository.getSessionIdForApplication(applicationId);
ApplicationFile applicationFile =
applicationRepository.getApplicationFileFromSession(applicationId.tenant(),
- sessionId,
- contentPath,
- ContentRequest.getApplicationFileMode(request.getMethod()));
+ sessionId,
+ contentPath,
+ ContentRequest.getApplicationFileMode(request.getMethod()));
ApplicationContentRequest contentRequest = new ApplicationContentRequest(request,
- sessionId,
- applicationId,
- zone,
- contentPath,
- applicationFile);
+ sessionId,
+ applicationId,
+ zone,
+ contentPath,
+ applicationFile);
return new ContentHandler().get(contentRequest);
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandler.java
index 17a8e1449c4..f6af9e616a9 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandler.java
@@ -6,6 +6,7 @@ import com.yahoo.config.application.api.ApplicationFile;
import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.vespa.config.server.ApplicationRepository;
import com.yahoo.vespa.config.server.http.ContentRequest;
import com.yahoo.vespa.config.server.http.ContentHandler;
@@ -52,7 +53,7 @@ public class SessionContentHandler extends SessionHandler {
TenantName tenantName = Utils.getTenantNameFromSessionRequest(request);
validateRequest(tenantName);
long sessionId = getSessionIdV2(request);
- String contentPath = SessionContentRequestV2.getContentPath(request);
+ Path contentPath = SessionContentRequestV2.getContentPath(request);
ApplicationFile applicationFile =
applicationRepository.getApplicationFileFromSession(tenantName,
sessionId,
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/ApplicationContentRequest.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/ApplicationContentRequest.java
index 34c842eca44..15d6c5c18ff 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/ApplicationContentRequest.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/ApplicationContentRequest.java
@@ -5,6 +5,7 @@ import com.yahoo.config.application.api.ApplicationFile;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.vespa.config.server.http.ContentRequest;
/**
@@ -22,7 +23,7 @@ public class ApplicationContentRequest extends ContentRequest {
long sessionId,
ApplicationId applicationId,
Zone zone,
- String contentPath,
+ Path contentPath,
ApplicationFile applicationFile) {
super(request, sessionId, contentPath, applicationFile);
this.applicationId = applicationId;
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/SessionContentRequestV2.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/SessionContentRequestV2.java
index da71fd89054..449058eb911 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/SessionContentRequestV2.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/request/SessionContentRequestV2.java
@@ -5,6 +5,8 @@ import com.yahoo.config.application.api.ApplicationFile;
import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.jdisc.application.BindingMatch;
+import com.yahoo.restapi.HttpURL;
+import com.yahoo.restapi.Path;
import com.yahoo.vespa.config.server.http.ContentRequest;
import com.yahoo.vespa.config.server.http.Utils;
@@ -16,14 +18,14 @@ import com.yahoo.vespa.config.server.http.Utils;
* @since 5.3
*/
public class SessionContentRequestV2 extends ContentRequest {
- private static final String uriPattern = "http://*/application/v2/tenant/*/session/*/content/*";
+
private final TenantName tenantName;
private final long sessionId;
public SessionContentRequestV2(HttpRequest request,
long sessionId,
TenantName tenantName,
- String path,
+ HttpURL.Path path,
ApplicationFile applicationFile) {
super(request, sessionId, path, applicationFile);
this.tenantName = tenantName;
@@ -35,8 +37,11 @@ public class SessionContentRequestV2 extends ContentRequest {
return "/application/v2/tenant/" + tenantName.value() + "/session/" + sessionId;
}
- public static String getContentPath(HttpRequest request) {
- BindingMatch<?> bm = Utils.getBindingMatch(request, uriPattern);
- return bm.group(4);
+ public static HttpURL.Path getContentPath(HttpRequest request) {
+ Path path = new Path(request.getUri());
+ if ( ! path.matches("/application/v2/tenant/{tenant}/session/{session}/content/{*}"))
+ throw new IllegalStateException("error in request routing");
+ return path.getRest();
}
+
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/serviceview/StateRequestHandler.java b/configserver/src/main/java/com/yahoo/vespa/serviceview/StateRequestHandler.java
index 30975be61e2..8b9714c3bfb 100644
--- a/configserver/src/main/java/com/yahoo/vespa/serviceview/StateRequestHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/serviceview/StateRequestHandler.java
@@ -5,6 +5,11 @@ import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import com.google.inject.Inject;
import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
+import com.yahoo.net.DomainName;
+import com.yahoo.restapi.HttpURL;
+import com.yahoo.restapi.HttpURL.Path;
+import com.yahoo.restapi.HttpURL.Query;
+import com.yahoo.restapi.HttpURL.Scheme;
import com.yahoo.restapi.RestApi;
import com.yahoo.restapi.RestApiRequestHandler;
import com.yahoo.restapi.UriBuilder;
@@ -106,7 +111,7 @@ public class StateRequestHandler extends RestApiRequestHandler<StateRequestHandl
String regionName = context.pathParameters().getStringOrThrow("regionName");
String instanceName = context.pathParameters().getStringOrThrow("instanceName");
String identifier = context.pathParameters().getStringOrThrow("serviceIdentifier");
- String apiParams = context.pathParameters().getString("*").orElse("");
+ Path apiParams = context.pathParameters().getRest().orElse(Path.empty());
return singleService(context.uriBuilder(), context.request().getUri(), tenantName, applicationName, environmentName, regionName, instanceName, identifier, apiParams);
}
@@ -125,7 +130,7 @@ public class StateRequestHandler extends RestApiRequestHandler<StateRequestHandl
}
protected HashMap<?, ?> singleService(
- UriBuilder uriBuilder, URI requestUri, String tenantName, String applicationName, String environmentName, String regionName, String instanceName, String identifier, String apiParams) {
+ UriBuilder uriBuilder, URI requestUri, String tenantName, String applicationName, String environmentName, String regionName, String instanceName, String identifier, Path apiParams) {
ServiceModel model = new ServiceModel(getModelConfig(tenantName, applicationName, environmentName, regionName, instanceName));
Service s = model.getService(identifier);
int requestedPort = s.matchIdentifierWithPort(identifier);
@@ -135,11 +140,9 @@ public class StateRequestHandler extends RestApiRequestHandler<StateRequestHandl
return apiResult;
}
- protected HealthClient getHealthClient(String apiParams, Service s, int requestedPort, String uriQuery, Client client) {
- final StringBuilder uriBuffer = new StringBuilder("http://").append(s.host).append(':').append(requestedPort).append('/')
- .append(apiParams);
- addQuery(uriQuery, uriBuffer);
- WebTarget target = client.target(uriBuffer.toString());
+ protected HealthClient getHealthClient(Path apiParams, Service s, int requestedPort, String uriQuery, Client client) {
+ URI uri = HttpURL.create(Scheme.http, DomainName.of(s.host), requestedPort, apiParams, Query.parse(uriQuery)).asURI();
+ WebTarget target = client.target(uri);
return WebResourceFactory.newResource(HealthClient.class, target);
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/HttpProxyTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/HttpProxyTest.java
index ac0a6491100..83cae04cbfd 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/HttpProxyTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/HttpProxyTest.java
@@ -6,6 +6,7 @@ import com.yahoo.config.model.api.Model;
import com.yahoo.config.model.api.ServiceInfo;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.vespa.config.server.http.HttpFetcher;
import com.yahoo.vespa.config.server.http.RequestTimeoutException;
import com.yahoo.vespa.config.server.http.StaticResponse;
@@ -48,7 +49,7 @@ public class HttpProxyTest {
when(fetcher.get(actualParams.capture(), actualUrl.capture())).thenReturn(response);
HttpResponse actualResponse = proxy.get(applicationMock, hostname, CLUSTERCONTROLLER_CONTAINER.serviceName,
- "clustercontroller-status/v1/clusterName");
+ Path.parse("clustercontroller-status/v1/clusterName"));
assertEquals(1, actualParams.getAllValues().size());
assertEquals(2000, actualParams.getValue().readTimeoutMs);
@@ -67,7 +68,7 @@ public class HttpProxyTest {
when(fetcher.get(any(), any())).thenThrow(new RequestTimeoutException("timed out"));
proxy.get(applicationMock, hostname, CLUSTERCONTROLLER_CONTAINER.serviceName,
- "clustercontroller-status/v1/clusterName");
+ Path.parse("clustercontroller-status/v1/clusterName"));
}
private static MockModel createClusterController() {
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HandlerTest.java
index 69caf86729f..0732379e0d9 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HandlerTest.java
@@ -34,7 +34,7 @@ public class HandlerTest {
if (contentType != null) {
assertEquals(renderedString, contentType, response.getContentType());
}
- assertTrue(renderedString.contains(message));
+ assertTrue("\n" + renderedString + "\n should contain \n" + message, renderedString.contains(message));
}
public static void assertHttpStatusCodeErrorCodeAndMessage(HttpResponse response, int statusCode, HttpErrorResponse.ErrorCode errorCode, String message) throws IOException {
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
index 005dd715dd4..9f7e539a2e3 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
@@ -15,6 +15,7 @@ import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.jdisc.Response;
import com.yahoo.jdisc.http.HttpRequest.Method;
+import com.yahoo.restapi.HttpURL;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.config.server.ApplicationRepository;
import com.yahoo.vespa.config.server.MockLogRetriever;
@@ -356,16 +357,20 @@ public class ApplicationHandlerTest {
.withHttpProxy(mockHttpProxy)
.build();
ApplicationHandler mockHandler = createApplicationHandler(applicationRepository);
- doAnswer(invoc -> new StaticResponse(200, "text/html", "<html>" +
- "host=" + invoc.getArgument(1, String.class) + "," +
- "service=" + invoc.getArgument(2, String.class) + "," +
- "path=" + invoc.getArgument(3, String.class) + "</html>")).when(mockHttpProxy).get(any(), any(), any(), any());
+ doAnswer(invoc -> new StaticResponse(200,
+ "text/html",
+ "<html>" +
+ "host=" + invoc.getArgument(1, String.class) + "," +
+ "service=" + invoc.getArgument(2, String.class) + "," +
+ "path=" + invoc.getArgument(3, HttpURL.Path.class) +
+ "</html>"))
+ .when(mockHttpProxy).get(any(), any(), any(), any());
HttpResponse response = mockHandler.handle(createTestRequest(toUrlPath(applicationId, Zone.defaultZone(), true) + "/service/container-clustercontroller/" + host + "/status/some/path/clusterName1", GET));
- assertHttpStatusCodeAndMessage(response, 200, "text/html", "<html>host=foo.yahoo.com,service=container-clustercontroller,path=clustercontroller-status/v1/some/path/clusterName1</html>");
+ assertHttpStatusCodeAndMessage(response, 200, "text/html", "<html>host=foo.yahoo.com,service=container-clustercontroller,path=path '/clustercontroller-status/v1/some/path/clusterName1'</html>");
response = mockHandler.handle(createTestRequest(toUrlPath(applicationId, Zone.defaultZone(), true) + "/service/distributor/" + host + "/status/something", GET));
- assertHttpStatusCodeAndMessage(response, 200, "text/html", "<html>host=foo.yahoo.com,service=distributor,path=something</html>");
+ assertHttpStatusCodeAndMessage(response, 200, "text/html", "<html>host=foo.yahoo.com,service=distributor,path=path '/something'</html>");
response = mockHandler.handle(createTestRequest(toUrlPath(applicationId, Zone.defaultZone(), true) + "/service/fake-service/" + host + "/status/something", GET));
assertHttpStatusCodeAndMessage(response, 404, "{\"error-code\":\"NOT_FOUND\",\"message\":\"No status page for service: fake-service\"}");
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java
index 790be6d45ba..7c2e0be0c3a 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java
@@ -121,13 +121,13 @@ public class SessionContentHandlerTest extends ContentHandlerTestBase {
@Test
public void require_that_nonexistent_file_returns_not_found_when_deleted() throws IOException {
- assertDeleteFile(Response.Status.NOT_FOUND, "/test2.txt", "{\"error-code\":\"NOT_FOUND\",\"message\":\"Session " + sessionId + " does not contain a file 'test2.txt'\"}");
+ assertDeleteFile(Response.Status.NOT_FOUND, "/test2.txt", "{\"error-code\":\"NOT_FOUND\",\"message\":\"Session " + sessionId + " does not contain a file at path '/test2.txt'\"}");
}
@Test
public void require_that_files_can_be_deleted() throws IOException {
assertDeleteFile(Response.Status.OK, "/test.txt");
- assertDeleteFile(Response.Status.NOT_FOUND, "/test.txt", "{\"error-code\":\"NOT_FOUND\",\"message\":\"Session " + sessionId + " does not contain a file 'test.txt'\"}");
+ assertDeleteFile(Response.Status.NOT_FOUND, "/test.txt", "{\"error-code\":\"NOT_FOUND\",\"message\":\"Session " + sessionId + " does not contain a file at path '/test.txt'\"}");
assertDeleteFile(Response.Status.BAD_REQUEST, "/newtest", "{\"error-code\":\"BAD_REQUEST\",\"message\":\"File 'newtest' is not an empty directory\"}");
assertDeleteFile(Response.Status.OK, "/newtest/testfile.txt");
assertDeleteFile(Response.Status.OK, "/newtest");
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java
index 702dd2792da..f77c7611858 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java
@@ -7,6 +7,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.vespa.config.server.ApplicationRepository;
import com.yahoo.vespa.config.server.MockProvisioner;
import com.yahoo.vespa.config.server.application.CompressedApplicationInputStreamTest;
@@ -155,7 +156,7 @@ public class SessionCreateHandlerTest extends SessionHandlerTest {
public void require_that_handler_unpacks_application() throws IOException {
File outFile = CompressedApplicationInputStreamTest.createTarFile();
createHandler().handle(post(outFile));
- ApplicationFile applicationFile = applicationRepository.getApplicationFileFromSession(tenant, 2, "services.xml", Session.Mode.READ);
+ ApplicationFile applicationFile = applicationRepository.getApplicationFileFromSession(tenant, 2, Path.parse("services.xml"), Session.Mode.READ);
assertTrue(applicationFile.exists());
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/serviceview/StateRequestHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/serviceview/StateRequestHandlerTest.java
index fe025c9e861..35e63c46bf8 100644
--- a/configserver/src/test/java/com/yahoo/vespa/serviceview/StateRequestHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/serviceview/StateRequestHandlerTest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.serviceview;
import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.jdisc.test.MockMetric;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.restapi.UriBuilder;
import com.yahoo.vespa.serviceview.bindings.ApplicationView;
import com.yahoo.vespa.serviceview.bindings.HealthClient;
@@ -45,7 +46,7 @@ public class StateRequestHandlerTest {
}
@Override
- protected HealthClient getHealthClient(String apiParams, Service s, int requestedPort, String uriQuery, Client client) {
+ protected HealthClient getHealthClient(Path apiParams, Service s, int requestedPort, String uriQuery, Client client) {
HealthClient healthClient = Mockito.mock(HealthClient.class);
HashMap<Object, Object> dummyHealthData = new HashMap<>();
HashMap<String, String> dummyLink = new HashMap<>();
@@ -75,7 +76,7 @@ public class StateRequestHandlerTest {
public final void test() {
Service s = correspondingModel.resolve("vespa.yahoo.com", 8080, null);
String api = "/state/v1";
- HashMap<?, ?> boom = testHandler.singleService(new UriBuilder("http://someserver:8080"), URI.create(EXTERNAL_BASE_URI), "default", "default", "default", "default", "default", s.getIdentifier(8080), api);
+ HashMap<?, ?> boom = testHandler.singleService(new UriBuilder("http://someserver:8080"), URI.create(EXTERNAL_BASE_URI), "default", "default", "default", "default", "default", s.getIdentifier(8080), Path.parse(api));
assertEquals(EXTERNAL_BASE_URI + "tenant/default/application/default/environment/default/region/default/instance/default/service/" + s.getIdentifier(8080) + api,
((Map<?, ?>) ((List<?>) boom.get("resources")).get(0)).get("url"));
}
diff --git a/container-core/src/main/java/com/yahoo/restapi/HttpURL.java b/container-core/src/main/java/com/yahoo/restapi/HttpURL.java
index 9705b6f0e40..e890b0fe71a 100644
--- a/container-core/src/main/java/com/yahoo/restapi/HttpURL.java
+++ b/container-core/src/main/java/com/yahoo/restapi/HttpURL.java
@@ -223,14 +223,24 @@ public class HttpURL {
segment, "path segments cannot be \"\", \".\", or \"..\"");
}
+ /** Returns a copy of this where only the first segments are retained, and with a trailing slash. */
+ public Path head(int count) {
+ return count == segments.size() ? this : new Path(segments.subList(0, count), true, validator);
+ }
+
+ /** Returns a copy of this where only the last segments are retained. */
+ public Path tail(int count) {
+ return count == segments.size() ? this : new Path(segments.subList(segments.size() - count, segments.size()), trailingSlash, validator);
+ }
+
/** Returns a copy of this where the first segments are skipped. */
public Path skip(int count) {
- return new Path(segments.subList(count, segments.size()), trailingSlash, validator);
+ return count == 0 ? this : new Path(segments.subList(count, segments.size()), trailingSlash, validator);
}
- /** Returns a copy of this where the last segments are cut off. */
+ /** Returns a copy of this where the last segments are cut off, and with a trailing slash. */
public Path cut(int count) {
- return new Path(segments.subList(0, segments.size() - count), trailingSlash, validator);
+ return count == 0 ? this : new Path(segments.subList(0, segments.size() - count), true, validator);
}
/** Returns a copy of this with the <em>decoded</em> segment appended at the end; it may not be either of {@code ""}, {@code "."} or {@code ".."}. */
@@ -254,6 +264,11 @@ public class HttpURL {
return new Path(copy, trailingSlash, validator);
}
+ /** Whether this path has a trailing slash. */
+ public boolean hasTrailingSlash() {
+ return trailingSlash;
+ }
+
/** Returns a copy of this which encodes a trailing slash. */
public Path withTrailingSlash() {
return new Path(segments, true, validator);
diff --git a/container-core/src/main/java/com/yahoo/restapi/Path.java b/container-core/src/main/java/com/yahoo/restapi/Path.java
index 8a494d47be4..c639432db89 100644
--- a/container-core/src/main/java/com/yahoo/restapi/Path.java
+++ b/container-core/src/main/java/com/yahoo/restapi/Path.java
@@ -66,11 +66,9 @@ public class Path {
else if ( ! specElements.get(i).equals(path.segments().get(i)))
return false;
}
-
- if (matchPrefix) {
- this.rest = path.skip(specElements.size());
- }
-
+
+ rest = matchPrefix ? path.skip(specElements.size()) : null;
+
return true;
}
@@ -99,12 +97,10 @@ public class Path {
}
/**
- * Returns the rest of the last matched path.
- * This is always the empty string (never null) unless the path spec ends with {*}
+ * Returns the rest of the last matched path, or {@code null} if the path spec didn't end with {*}.
*/
- public String getRest() {
- String raw = rest.raw();
- return raw.isEmpty() ? raw : raw.substring(1);
+ public HttpURL.Path getRest() {
+ return rest;
}
@Override
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApi.java b/container-core/src/main/java/com/yahoo/restapi/RestApi.java
index d6cc0cccf3f..353ac3eb5cc 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApi.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApi.java
@@ -153,7 +153,9 @@ public interface RestApi {
default double getDoubleOrThrow(String name) { return Double.parseDouble(getStringOrThrow(name)); }
}
- interface PathParameters extends Parameters {}
+ interface PathParameters extends Parameters {
+ Optional<HttpURL.Path> getRest();
+ }
interface QueryParameters extends Parameters {
List<String> getStringList(String name);
}
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java b/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
index 09de7ffa133..cc243a3e92b 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
@@ -429,16 +429,16 @@ class RestApiImpl implements RestApi {
private class PathParametersImpl implements RestApi.RequestContext.PathParameters {
@Override
public Optional<String> getString(String name) {
- if (name.equals("*")) {
- String rest = pathMatcher.getRest();
- return rest.isEmpty() ? Optional.empty() : Optional.of(rest);
- }
return Optional.ofNullable(pathMatcher.get(name));
}
@Override public String getStringOrThrow(String name) {
return getString(name)
.orElseThrow(() -> new RestApiException.BadRequest("Path parameter '" + name + "' is missing"));
}
+ @Override public Optional<HttpURL.Path> getRest() {
+ return Optional.ofNullable(pathMatcher.getRest());
+ }
+
}
private class QueryParametersImpl implements RestApi.RequestContext.QueryParameters {
diff --git a/container-core/src/test/java/com/yahoo/restapi/HttpURLTest.java b/container-core/src/test/java/com/yahoo/restapi/HttpURLTest.java
index 05a218b0f04..858513c2a69 100644
--- a/container-core/src/test/java/com/yahoo/restapi/HttpURLTest.java
+++ b/container-core/src/test/java/com/yahoo/restapi/HttpURLTest.java
@@ -124,6 +124,11 @@ class HttpURLTest {
assertEquals(List.of(expected.get(2), expected.get(0)),
path.append(path).cut(2).skip(2).segments());
+ for (int i = 0; i < 3; i++) {
+ assertEquals(path.head(i), path.cut(3 - i));
+ assertEquals(path.tail(i), path.skip(3 - i));
+ }
+
assertThrows(NullPointerException.class,
() -> path.append((String) null));
diff --git a/container-core/src/test/java/com/yahoo/restapi/PathTest.java b/container-core/src/test/java/com/yahoo/restapi/PathTest.java
index 065b02be0e4..4786eb9775c 100644
--- a/container-core/src/test/java/com/yahoo/restapi/PathTest.java
+++ b/container-core/src/test/java/com/yahoo/restapi/PathTest.java
@@ -35,7 +35,7 @@ public class PathTest {
assertTrue(path.matches("/a/{foo}/bar/{b}/{*}"));
assertEquals("1", path.get("foo"));
assertEquals("fuz", path.get("b"));
- assertEquals("", path.getRest());
+ assertEquals("/", path.getRest().raw());
}
{
@@ -43,7 +43,7 @@ public class PathTest {
assertTrue(path.matches("/a/{foo}/bar/{b}/{*}"));
assertEquals("1", path.get("foo"));
assertEquals("fuz", path.get("b"));
- assertEquals("kanoo", path.getRest());
+ assertEquals("/kanoo", path.getRest().raw());
}
{
@@ -51,7 +51,7 @@ public class PathTest {
assertTrue(path.matches("/a/{foo}/bar/{b}/{*}"));
assertEquals("1", path.get("foo"));
assertEquals("fuz", path.get("b"));
- assertEquals("kanoo/trips", path.getRest());
+ assertEquals("/kanoo/trips", path.getRest().raw());
}
{
@@ -59,7 +59,7 @@ public class PathTest {
assertTrue(path.matches("/a/{foo}/bar/{b}/{*}"));
assertEquals("1", path.get("foo"));
assertEquals("fuz", path.get("b"));
- assertEquals("kanoo/trips/", path.getRest());
+ assertEquals("/kanoo/trips/", path.getRest().raw());
}
}
diff --git a/container-search-gui/src/main/java/com/yahoo/search/query/gui/GUIHandler.java b/container-search-gui/src/main/java/com/yahoo/search/query/gui/GUIHandler.java
index b2b9dd47514..bbce02ed97a 100644
--- a/container-search-gui/src/main/java/com/yahoo/search/query/gui/GUIHandler.java
+++ b/container-search-gui/src/main/java/com/yahoo/search/query/gui/GUIHandler.java
@@ -28,6 +28,7 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.UncheckedIOException;
import java.util.logging.Level;
@@ -71,24 +72,23 @@ public class GUIHandler extends ThreadedHttpRequestHandler {
if (path.matches("/querybuilder/")) {
return new FileResponse("_includes/index.html", null, null);
}
- if (!path.matches("/querybuilder/{*}") ) {
+ if ( ! path.matches("/querybuilder/{*}") ) {
return ErrorResponse.notFoundError("Nothing at path:" + path);
}
- String filepath = path.getRest();
- if (!isValidPath(filepath) && !filepath.equals("config.json")){
+ String filepath = String.join("/", path.getRest().segments());
+ if ( ! filepath.equals("config.json") && ! isValidPath(filepath)){
return ErrorResponse.notFoundError("Nothing at path:" + filepath);
}
return new FileResponse(filepath, indexModel, rankProfilesConfig);
}
private static boolean isValidPath(String path) {
- InputStream in = GUIHandler.class.getClassLoader().getResourceAsStream("gui/"+path);
- boolean isValid = (in != null);
- if(isValid){
- try { in.close(); } catch (IOException e) {/* Problem with closing inputstream */}
+ InputStream in = GUIHandler.class.getClassLoader().getResourceAsStream("gui/" + path);
+ if (in != null){
+ try { in.close(); } catch (IOException e) { throw new UncheckedIOException(e); }
+ return true;
}
-
- return isValid;
+ return false;
}
private static class FileResponse extends HttpResponse {
@@ -112,12 +112,12 @@ public class GUIHandler extends ThreadedHttpRequestHandler {
String json = "{}";
try { json = getGUIConfig(); } catch (IOException e) { /*Something happened while parsing JSON */ }
is = new ByteArrayInputStream(json.getBytes());
- } else{
- is = GUIHandler.class.getClassLoader().getResourceAsStream("gui/"+this.path);
+ } else {
+ is = GUIHandler.class.getClassLoader().getResourceAsStream("gui/" + this.path);
}
byte[] buf = new byte[1024];
int numRead;
- while ( (numRead = is.read(buf) ) >= 0) {
+ while ((numRead = is.read(buf)) >= 0) {
out.write(buf, 0, numRead);
}
}
@@ -152,8 +152,6 @@ public class GUIHandler extends ThreadedHttpRequestHandler {
return "image/x-icon";
} else if (path.endsWith(".json")) {
return "application/json";
- } else if (path.endsWith(".ttf")) {
- return "font/ttf";
}
return "text/html";
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java
index b9b7881745a..42368fa358d 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java
@@ -4,6 +4,8 @@ package com.yahoo.vespa.hosted.controller.api.integration.configserver;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.net.DomainName;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.vespa.flags.json.FlagData;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeploymentData;
@@ -52,9 +54,9 @@ public interface ConfigServer {
ApplicationView getApplicationView(String tenantName, String applicationName, String instanceName, String environment, String region);
- Map<?,?> getServiceApiResponse(DeploymentId deployment, String serviceName, String restPath);
+ Map<?,?> getServiceApiResponse(DeploymentId deployment, String serviceName, Path restPath);
- String getServiceStatusPage(DeploymentId deployment, String serviceName, String node, String subPath);
+ String getServiceStatusPage(DeploymentId deployment, String serviceName, DomainName node, Path subPath);
/**
* Gets the Vespa logs of the given deployment.
@@ -73,7 +75,7 @@ public interface ConfigServer {
* @param path path within package to get
* @param requestUri request URI on the controller, used to rewrite paths in response from config server
*/
- ProxyResponse getApplicationPackageContent(DeploymentId deployment, String path, URI requestUri);
+ ProxyResponse getApplicationPackageContent(DeploymentId deployment, Path path, URI requestUri);
List<ClusterMetrics> getDeploymentMetrics(DeploymentId deployment);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java
index 2a1b8a19475..7e1c9c8884f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java
@@ -3,6 +3,9 @@ package com.yahoo.vespa.hosted.controller.proxy;
import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.restapi.HttpURL;
+import com.yahoo.restapi.HttpURL.Path;
+import com.yahoo.restapi.HttpURL.Query;
import com.yahoo.text.Text;
import java.io.InputStream;
import java.net.URI;
@@ -22,28 +25,27 @@ import static com.yahoo.jdisc.http.HttpRequest.Method;
public class ProxyRequest {
private final Method method;
- private final URI requestUri;
+ private final HttpURL requestUri;
private final Map<String, List<String>> headers;
private final InputStream requestData;
private final List<URI> targets;
- private final String targetPath;
+ private final Path targetPath;
- ProxyRequest(Method method, URI url, Map<String, List<String>> headers, InputStream body, List<URI> targets,
- String path) {
- Objects.requireNonNull(url);
- if (!url.getPath().endsWith(path)) {
- throw new IllegalArgumentException(Text.format("Request path '%s' does not end with proxy path '%s'", url.getPath(), path));
+ ProxyRequest(Method method, URI uri, Map<String, List<String>> headers, InputStream body, List<URI> targets, Path path) {
+ this.requestUri = HttpURL.from(uri);
+ if ( requestUri.path().segments().size() < path.segments().size()
+ || ! requestUri.path().tail(path.segments().size()).equals(path)) {
+ throw new IllegalArgumentException(Text.format("Request %s does not end with proxy %s", requestUri.path(), path));
}
if (targets.isEmpty()) {
throw new IllegalArgumentException("targets must be non-empty");
}
this.method = Objects.requireNonNull(method);
- this.requestUri = Objects.requireNonNull(url);
this.headers = Objects.requireNonNull(headers);
this.requestData = body;
this.targets = List.copyOf(targets);
- this.targetPath = path.startsWith("/") ? path : "/" + path;
+ this.targetPath = path;
}
@@ -64,24 +66,12 @@ public class ProxyRequest {
}
public URI createConfigServerRequestUri(URI baseURI) {
- try {
- return new URI(baseURI.getScheme(), baseURI.getUserInfo(), baseURI.getHost(),
- baseURI.getPort(), targetPath, requestUri.getQuery(), requestUri.getFragment());
- } catch (URISyntaxException e) {
- throw new RuntimeException(e);
- }
+ return HttpURL.from(baseURI).withPath(targetPath).withQuery(requestUri.query()).asURI();
}
public URI getControllerPrefixUri() {
- String prefixPath = targetPath.equals("/") && !requestUri.getPath().endsWith("/") ?
- requestUri.getPath() + targetPath :
- requestUri.getPath().substring(0, requestUri.getPath().length() - targetPath.length() + 1);
- try {
- return new URI(requestUri.getScheme(), requestUri.getUserInfo(), requestUri.getHost(),
- requestUri.getPort(), prefixPath, null, null);
- } catch (URISyntaxException e) {
- throw new RuntimeException(e);
- }
+ Path prefixPath = requestUri.path().cut(targetPath.segments().size()).withTrailingSlash();
+ return requestUri.withPath(prefixPath).withQuery(Query.empty()).asURI();
}
@Override
@@ -90,7 +80,7 @@ public class ProxyRequest {
}
/** Create a proxy request that repeatedly tries a single target */
- public static ProxyRequest tryOne(URI target, String path, HttpRequest request) {
+ public static ProxyRequest tryOne(URI target, Path path, HttpRequest request) {
return new ProxyRequest(request.getMethod(), request.getUri(), request.getJDiscRequest().headers(),
request.getData(), List.of(target), path);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java
index 0b629a577d0..9ac30898f8b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java
@@ -2,6 +2,8 @@
package com.yahoo.vespa.hosted.controller.proxy;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.HttpURL;
+import com.yahoo.restapi.HttpURL.Path;
import org.apache.http.client.utils.URIBuilder;
import java.io.IOException;
@@ -29,19 +31,8 @@ public class ProxyResponse extends HttpResponse {
super(statusResponse);
this.contentType = contentType;
- final String configServerPrefix;
- final String controllerRequestPrefix;
- try {
- configServerPrefix = new URIBuilder()
- .setScheme(configServer.getScheme())
- .setHost(configServer.getHost())
- .setPort(configServer.getPort())
- .setPath("/")
- .build().toString();
- controllerRequestPrefix = controllerRequest.getControllerPrefixUri().toString();
- } catch (URISyntaxException e) {
- throw new RuntimeException(e);
- }
+ String configServerPrefix = HttpURL.from(configServer).withPath(Path.empty()).asURI().toString();
+ String controllerRequestPrefix = controllerRequest.getControllerPrefixUri().toString();
bodyResponseRewritten = bodyResponse.replace(configServerPrefix, controllerRequestPrefix);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index d9a38a5b578..c2c4049bcc3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.restapi.application;
import ai.vespa.hosted.api.Signatures;
+import ai.vespa.validation.Validation;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
@@ -25,8 +26,10 @@ import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
import com.yahoo.io.IOUtils;
+import com.yahoo.net.DomainName;
import com.yahoo.restapi.ByteArrayResponse;
import com.yahoo.restapi.ErrorResponse;
+import com.yahoo.restapi.HttpURL;
import com.yahoo.restapi.MessageResponse;
import com.yahoo.restapi.Path;
import com.yahoo.restapi.ResourceResponse;
@@ -155,6 +158,7 @@ import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import static ai.vespa.validation.Validation.requireAtLeast;
import static com.yahoo.jdisc.Response.Status.BAD_REQUEST;
import static com.yahoo.jdisc.Response.Status.CONFLICT;
import static com.yahoo.yolean.Exceptions.uncheck;
@@ -270,7 +274,8 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/reindexing")) return getReindexing(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/suspended")) return suspended(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/service")) return services(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/service/{service}/{*}")) return service(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("service"), path.getRest(), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/service/{service}/state/v1/{*}")) return service(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("service"), path.getRest(), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/service/{service}/{host}/status/{*}")) return status(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("service"), path.get("host"), path.getRest(), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/nodes")) return nodes(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/clusters")) return clusters(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/content/{*}")) return content(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.getRest(), request);
@@ -284,7 +289,8 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}")) return deployment(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/suspended")) return suspended(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/service")) return services(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/service/{service}/{*}")) return service(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("service"), path.getRest(), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/service/{service}/state/v1/{*}")) return service(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("service"), path.getRest(), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/service/{service}/{host}/status/{*}")) return status(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("service"), path.get("host"), path.getRest(), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/nodes")) return nodes(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/clusters")) return clusters(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/logs")) return logs(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request.propertyMap());
@@ -1857,40 +1863,29 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
return response;
}
- private HttpResponse service(String tenantName, String applicationName, String instanceName, String environment, String region, String serviceName, String restPath, HttpRequest request) {
+ private HttpResponse status(String tenantName, String applicationName, String instanceName, String environment, String region, String serviceName, String host, HttpURL.Path restPath, HttpRequest request) {
DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName), requireZone(environment, region));
+ String result = controller.serviceRegistry().configServer().getServiceStatusPage(deploymentId,
+ serviceName,
+ DomainName.of(host),
+ restPath); // TODO: add query
+ return new HtmlResponse(result);
+ }
- if (restPath.contains("/status/")) {
- String[] parts = restPath.split("/status/", 2);
- String result = controller.serviceRegistry().configServer().getServiceStatusPage(deploymentId, serviceName, parts[0], parts[1]);
- return new HtmlResponse(result);
- }
-
- String normalizedRestPath = URI.create(restPath).normalize().toString();
- // Only state/v1 is allowed
- if (! normalizedRestPath.startsWith("state/v1/")) {
- return ErrorResponse.forbidden("Access denied");
- }
-
+ private HttpResponse service(String tenantName, String applicationName, String instanceName, String environment, String region, String serviceName, HttpURL.Path restPath, HttpRequest request) {
+ DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName), requireZone(environment, region));
Map<?,?> result = controller.serviceRegistry().configServer().getServiceApiResponse(deploymentId, serviceName, restPath);
ServiceApiResponse response = new ServiceApiResponse(deploymentId.zoneId(),
deploymentId.applicationId(),
List.of(controller.zoneRegistry().getConfigServerVipUri(deploymentId.zoneId())),
request.getUri());
- response.setResponse(result, serviceName, restPath);
+ response.setResponse(result, serviceName, HttpURL.Path.parse("/state/v1").append(restPath));
return response;
}
- private HttpResponse content(String tenantName, String applicationName, String instanceName, String environment, String region, String restPath, HttpRequest request) {
+ private HttpResponse content(String tenantName, String applicationName, String instanceName, String environment, String region, HttpURL.Path restPath, HttpRequest request) {
DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName), requireZone(environment, region));
-
- String normalizedRestPath = URI.create("content/" + restPath).normalize().toString();
- // Only content/ is allowed
- if ( ! normalizedRestPath.startsWith("content/")) {
- return ErrorResponse.forbidden("Access denied");
- }
-
- return controller.serviceRegistry().configServer().getApplicationPackageContent(deploymentId, "/" + restPath, request.getUri());
+ return controller.serviceRegistry().configServer().getApplicationPackageContent(deploymentId, restPath, request.getUri());
}
private HttpResponse updateTenant(String tenantName, HttpRequest request) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java
index 888c402b6ec..a21f93bdaea 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java
@@ -4,6 +4,9 @@ package com.yahoo.vespa.hosted.controller.restapi.application;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.HttpURL;
+import com.yahoo.restapi.HttpURL.Path;
+import com.yahoo.restapi.HttpURL.Query;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.JsonFormat;
import com.yahoo.slime.Slime;
@@ -34,11 +37,11 @@ class ServiceApiResponse extends HttpResponse {
private final ApplicationId application;
private final List<URI> configServerURIs;
private final Slime slime;
- private final UriBuilder requestUri;
+ private final HttpURL requestUri;
// Only set for one of the setResponse calls
private String serviceName = null;
- private String restPath = null;
+ private Path restPath = null;
public ServiceApiResponse(ZoneId zone, ApplicationId application, List<URI> configServerURIs, URI requestUri) {
super(200);
@@ -46,7 +49,7 @@ class ServiceApiResponse extends HttpResponse {
this.application = application;
this.configServerURIs = configServerURIs;
this.slime = new Slime();
- this.requestUri = new UriBuilder(requestUri).withoutParameters();
+ this.requestUri = HttpURL.from(requestUri).withQuery(Query.empty());
}
public void setResponse(ApplicationView applicationView) {
@@ -68,7 +71,7 @@ class ServiceApiResponse extends HttpResponse {
}
}
- public void setResponse(Map<?,?> responseData, String serviceName, String restPath) {
+ public void setResponse(Map<?,?> responseData, String serviceName, Path restPath) {
this.serviceName = serviceName;
this.restPath = restPath;
mapToSlime(responseData, slime.setObject());
@@ -138,7 +141,7 @@ class ServiceApiResponse extends HttpResponse {
mapToSlime((Map)entry, array.addObject());
}
- private String rewriteIfUrl(String urlOrAnyString, UriBuilder requestUri) {
+ private String rewriteIfUrl(String urlOrAnyString, HttpURL requestUri) {
if (urlOrAnyString == null) return null;
String hostPattern = "(" +
@@ -163,19 +166,18 @@ class ServiceApiResponse extends HttpResponse {
if (matcher.find()) {
String proxiedPath = urlOrAnyString.substring(matcher.group().length());
- return requestUri.append(proxiedPath).toString();
+ return requestUri.withPath(requestUri.path().append(Path.parse(proxiedPath))).asURI().toString();
} else {
return urlOrAnyString; // not a service url
}
}
- private UriBuilder generateLocalLinkPrefix(String identifier, String restPath) {
- String proxiedPath = identifier + "/" + restPath;
-
- if (this.requestUri.toString().endsWith(proxiedPath)) {
- return new UriBuilder(this.requestUri.toString().substring(0, this.requestUri.toString().length() - proxiedPath.length()));
+ private HttpURL generateLocalLinkPrefix(String identifier, Path restPath) {
+ Path proxiedPath = Path.parse(identifier).append(restPath);
+ if (requestUri.path().tail(proxiedPath.segments().size()).equals(proxiedPath)) {
+ return requestUri.withPath(requestUri.path().cut(proxiedPath.segments().size()));
} else {
- throw new IllegalStateException("Expected the resource path '" + this.requestUri + "' to end with '" + proxiedPath + "'");
+ throw new IllegalStateException("Expected the resource " + requestUri.path() + " to end with " + proxiedPath);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java
index 0e09825ec41..27a8cbeaf3e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java
@@ -6,6 +6,7 @@ import com.yahoo.config.provision.zone.ZoneList;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.restapi.ErrorResponse;
+import com.yahoo.restapi.HttpURL;
import com.yahoo.restapi.Path;
import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.slime.Cursor;
@@ -21,8 +22,11 @@ import com.yahoo.yolean.Exceptions;
import java.net.URI;
import java.util.List;
import java.util.logging.Level;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
+import static com.yahoo.restapi.HttpURL.Path.parse;
+
/**
* REST API for proxying operator APIs to config servers in a given zone.
*
@@ -32,7 +36,9 @@ import java.util.stream.Stream;
public class ConfigServerApiHandler extends AuditLoggingRequestHandler {
private static final URI CONTROLLER_URI = URI.create("https://localhost:4443/");
- private static final List<String> WHITELISTED_APIS = List.of("/flags/v1/", "/nodes/v2/", "/orchestrator/v1/");
+ private static final List<HttpURL.Path> WHITELISTED_APIS = List.of(parse("/flags/v1/"),
+ parse("/nodes/v2/"),
+ parse("/orchestrator/v1/"));
private final ZoneRegistry zoneRegistry;
private final ConfigServerRestExecutor proxy;
@@ -84,17 +90,18 @@ public class ConfigServerApiHandler extends AuditLoggingRequestHandler {
}
ZoneId zoneId = ZoneId.from(path.get("environment"), path.get("region"));
- if (! zoneRegistry.hasZone(zoneId) && ! controllerZone.equals(zoneId)) {
+ if ( ! zoneRegistry.hasZone(zoneId) && ! controllerZone.equals(zoneId)) {
throw new IllegalArgumentException("No such zone: " + zoneId.value());
}
- String cfgPath = "/" + path.getRest();
- if (WHITELISTED_APIS.stream().noneMatch(cfgPath::startsWith)) {
- return ErrorResponse.forbidden("Cannot access '" + cfgPath +
- "' through /configserver/v1, following APIs are permitted: " + String.join(", ", WHITELISTED_APIS));
+ if (path.getRest().segments().size() < 2 || ! WHITELISTED_APIS.contains(path.getRest().head(2).withTrailingSlash())) {
+ return ErrorResponse.forbidden("Cannot access " + path.getRest() +
+ " through /configserver/v1, following APIs are permitted: " + WHITELISTED_APIS.stream()
+ .map(p -> "/" + String.join("/", p.segments()) + "/")
+ .collect(Collectors.joining(", ")));
}
- return proxy.handle(ProxyRequest.tryOne(getEndpoint(zoneId), cfgPath, request));
+ return proxy.handle(ProxyRequest.tryOne(getEndpoint(zoneId), path.getRest(), request));
}
private HttpResponse root(HttpRequest request) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
index a7416cfa0ed..40a402f209b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
@@ -7,6 +7,7 @@ import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
import com.yahoo.restapi.ErrorResponse;
+import com.yahoo.restapi.HttpURL;
import com.yahoo.restapi.Path;
import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.slime.Cursor;
@@ -107,7 +108,7 @@ public class ZoneApiHandler extends AuditLoggingRequestHandler {
return ErrorResponse.notFoundError("Nothing at " + path);
}
- private ProxyRequest proxyRequest(ZoneId zoneId, String path, HttpRequest request) {
+ private ProxyRequest proxyRequest(ZoneId zoneId, HttpURL.Path path, HttpRequest request) {
return ProxyRequest.tryOne(zoneRegistry.getConfigServerVipUri(zoneId), path, request);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
index e0ce1c060bc..1643f1f818b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
@@ -13,6 +13,8 @@ import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.net.DomainName;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.text.Text;
import com.yahoo.vespa.flags.json.FlagData;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics;
@@ -521,7 +523,7 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
// Returns a canned example response
@Override
- public Map<?,?> getServiceApiResponse(DeploymentId deployment, String serviceName, String restPath) {
+ public Map<?,?> getServiceApiResponse(DeploymentId deployment, String serviceName, Path restPath) {
Map<String,List<?>> root = new HashMap<>();
List<Map<?,?>> resources = new ArrayList<>();
Map<String,String> resource = new HashMap<>();
@@ -532,7 +534,7 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
}
@Override
- public String getServiceStatusPage(DeploymentId deployment, String serviceName, String node, String subPath) {
+ public String getServiceStatusPage(DeploymentId deployment, String serviceName, DomainName node, Path subPath) {
return "<h1>OK</h1>";
}
@@ -567,8 +569,8 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
}
@Override
- public ProxyResponse getApplicationPackageContent(DeploymentId deployment, String path, URI requestUri) {
- return new ProxyResponse(("{\"path\":\"" + path + "\"}").getBytes(StandardCharsets.UTF_8), "application/json", 200);
+ public ProxyResponse getApplicationPackageContent(DeploymentId deployment, Path path, URI requestUri) {
+ return new ProxyResponse(("{\"path\":\"/" + String.join("/", path.segments()) + "\"}").getBytes(StandardCharsets.UTF_8), "application/json", 200);
}
public void setLogStream(Supplier<String> log) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImplTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImplTest.java
index 0b28d94386d..e10a41cbae3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImplTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImplTest.java
@@ -6,6 +6,7 @@ import com.github.tomakehurst.wiremock.stubbing.Scenario;
import com.yahoo.config.provision.SystemName;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.HttpURL.Path;
import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock;
import com.yahoo.yolean.concurrent.Sleeper;
import org.apache.http.protocol.HttpContext;
@@ -46,7 +47,7 @@ public class ConfigServerRestExecutorImplTest {
stubRequests(path);
HttpRequest request = HttpRequest.createTestRequest(url.toString(), com.yahoo.jdisc.http.HttpRequest.Method.GET);
- ProxyRequest proxyRequest = ProxyRequest.tryOne(url, path, request);
+ ProxyRequest proxyRequest = ProxyRequest.tryOne(url, Path.parse(path), request);
// Request is retried
HttpResponse response = proxy.handle(proxyRequest);
@@ -71,7 +72,7 @@ public class ConfigServerRestExecutorImplTest {
stubRequests(path);
HttpRequest request = HttpRequest.createTestRequest(url.toString(), com.yahoo.jdisc.http.HttpRequest.Method.GET);
- ProxyRequest proxyRequest = ProxyRequest.tryOne(url, path, request);
+ ProxyRequest proxyRequest = ProxyRequest.tryOne(url, Path.parse(path), request);
// Connections are reused
assertEquals(200, proxy.handle(proxyRequest).getStatus());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java
index 8542530628c..e8b5df7efa1 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.proxy;
import com.yahoo.jdisc.http.HttpRequest;
+import com.yahoo.restapi.HttpURL.Path;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -11,20 +12,18 @@ import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
/**
* @author Haakon Dybdahl
*/
public class ProxyRequestTest {
- @SuppressWarnings("deprecation")
- @Rule
- public final ExpectedException exception = ExpectedException.none();
-
@Test
public void testBadUri() {
- exception.expectMessage("Request path '/path' does not end with proxy path '/zone/v2/'");
- testRequest("http://domain.tld/path", "/zone/v2/");
+ assertThrows("Request path '/path' does not end with proxy path '/zone/v2/'",
+ IllegalArgumentException.class,
+ () -> testRequest("http://domain.tld/path", "/zone/v2/"));
}
@Test
@@ -33,7 +32,7 @@ public class ProxyRequestTest {
// Root request
ProxyRequest request = testRequest("http://controller.domain.tld/my/path", "");
assertEquals(URI.create("http://controller.domain.tld/my/path/"), request.getControllerPrefixUri());
- assertEquals(URI.create("https://cfg.prod.us-north-1.domain.tld:1234/"),
+ assertEquals(URI.create("https://cfg.prod.us-north-1.domain.tld:1234"),
request.createConfigServerRequestUri(URI.create("https://cfg.prod.us-north-1.domain.tld:1234/")));
}
@@ -64,7 +63,7 @@ public class ProxyRequestTest {
private static ProxyRequest testRequest(String url, String pathPrefix) {
return new ProxyRequest(HttpRequest.Method.GET, URI.create(url), Map.of(), null,
- List.of(URI.create("http://example.com")), pathPrefix);
+ List.of(URI.create("http://example.com")), Path.parse(pathPrefix));
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java
index 4cc2c1cc79e..1ba0200eec3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.proxy;
import com.yahoo.jdisc.http.HttpRequest;
+import com.yahoo.restapi.HttpURL.Path;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
@@ -20,7 +21,7 @@ public class ProxyResponseTest {
@Test
public void testRewriteUrl() throws Exception {
ProxyRequest request = new ProxyRequest(HttpRequest.Method.GET, URI.create("http://domain.tld/zone/v2/dev/us-north-1/configserver"),
- Map.of(), null, List.of(URI.create("http://example.com")), "configserver");
+ Map.of(), null, List.of(URI.create("http://example.com")), Path.parse("configserver"));
ProxyResponse proxyResponse = new ProxyResponse(
request,
"response link is http://configserver:1234/bla/bla/",
@@ -31,14 +32,14 @@ public class ProxyResponseTest {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
proxyResponse.render(outputStream);
- String document = new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
+ String document = outputStream.toString(StandardCharsets.UTF_8);
assertEquals("response link is http://domain.tld/zone/v2/dev/us-north-1/bla/bla/", document);
}
@Test
public void testRewriteSecureUrl() throws Exception {
ProxyRequest request = new ProxyRequest(HttpRequest.Method.GET, URI.create("https://domain.tld/zone/v2/prod/eu-south-3/configserver"),
- Map.of(), null, List.of(URI.create("http://example.com")), "configserver");
+ Map.of(), null, List.of(URI.create("http://example.com")), Path.parse("configserver"));
ProxyResponse proxyResponse = new ProxyResponse(
request,
"response link is http://configserver:1234/bla/bla/",
@@ -49,7 +50,7 @@ public class ProxyResponseTest {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
proxyResponse.render(outputStream);
- String document = new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
+ String document = outputStream.toString(StandardCharsets.UTF_8);
assertEquals("response link is https://domain.tld/zone/v2/prod/eu-south-3/bla/bla/", document);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
index 581c0160640..9736745d257 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
@@ -1657,15 +1657,15 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request(serviceApi + "/storagenode-awe3slno6mmq2fye191y324jl/document/v1/", GET)
.userIdentity(USER_ID)
.oAuthCredentials(OKTA_CREDENTIALS),
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"Access denied\"}",
- 403);
+ "{\"error-code\":\"NOT_FOUND\",\"message\":\"Nothing at path '/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/service/storagenode-awe3slno6mmq2fye191y324jl/document/v1/'\"}",
+ 404);
// Test path traversal
tester.assertResponse(request(serviceApi + "/storagenode-awe3slno6mmq2fye191y324jl/state/v1/../../document/v1/", GET)
.userIdentity(USER_ID)
.oAuthCredentials(OKTA_CREDENTIALS),
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"Access denied\"}",
- 403);
+ "{\"error-code\":\"NOT_FOUND\",\"message\":\"Nothing at path '/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/service/storagenode-awe3slno6mmq2fye191y324jl/document/v1/'\"}",
+ 404);
// Test urlencoded path traversal
tester.assertResponse(request(serviceApi + "/storagenode-awe3slno6mmq2fye191y324jl/state%2Fv1%2F..%2F..%2Fdocument%2Fv1%2F", GET)
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java
index 9d0054f327a..ce6f713a13d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java
@@ -50,6 +50,11 @@ public class ConfigServerApiHandlerTest extends ControllerContainerTest {
tester.assertResponse(operatorRequest("http://localhost:8080/configserver/v1"),
new File("root.json"));
+ // GET /configserver/v1/nodes/v2
+ tester.assertResponse(operatorRequest("http://localhost:8080/configserver/v1/prod/us-north-1/nodes/v2"),
+ "ok");
+ assertLastRequest("https://cfg.prod.us-north-1.test.vip:4443/", "GET");
+
// GET /configserver/v1/nodes/v2/node/?recursive=true
tester.assertResponse(operatorRequest("http://localhost:8080/configserver/v1/prod/us-north-1/nodes/v2/node/?recursive=true"),
"ok");
@@ -83,12 +88,12 @@ public class ConfigServerApiHandlerTest extends ControllerContainerTest {
@Test
public void test_allowed_apis() {
// GET /configserver/v1/prod/us-north-1
- tester.assertResponse(() -> operatorRequest("http://localhost:8080/configserver/v1/prod/us-north-1"),
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"Cannot access '/' through /configserver/v1, following APIs are permitted: /flags/v1/, /nodes/v2/, /orchestrator/v1/\"}",
+ tester.assertResponse(() -> operatorRequest("http://localhost:8080/configserver/v1/prod/us-north-1/"),
+ "{\"error-code\":\"FORBIDDEN\",\"message\":\"Cannot access path '/' through /configserver/v1, following APIs are permitted: /flags/v1/, /nodes/v2/, /orchestrator/v1/\"}",
403);
tester.assertResponse(() -> operatorRequest("http://localhost:8080/configserver/v1/prod/us-north-1/application/v2/tenant/vespa"),
- "{\"error-code\":\"FORBIDDEN\",\"message\":\"Cannot access '/application/v2/tenant/vespa' through /configserver/v1, following APIs are permitted: /flags/v1/, /nodes/v2/, /orchestrator/v1/\"}",
+ "{\"error-code\":\"FORBIDDEN\",\"message\":\"Cannot access path '/application/v2/tenant/vespa' through /configserver/v1, following APIs are permitted: /flags/v1/, /nodes/v2/, /orchestrator/v1/\"}",
403);
}
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java
index f59b9f1e372..29c572422c3 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java
@@ -1472,7 +1472,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
return new DocumentId("id:" + requireNonNull(path.get("namespace")) +
":" + requireNonNull(path.get("documentType")) +
":" + group.map(Group::docIdPart).orElse("") +
- ":" + requireNonNull(path.getRest()));
+ ":" + String.join("/", requireNonNull(path.getRest()).segments())); // :'(
}
String rawPath() { return rawPath; }
diff --git a/vespajlib/abi-spec.json b/vespajlib/abi-spec.json
index e69631e8375..642fdd5c16f 100644
--- a/vespajlib/abi-spec.json
+++ b/vespajlib/abi-spec.json
@@ -583,6 +583,7 @@
"final"
],
"methods": [
+ "public static com.yahoo.path.Path from(java.util.List)",
"public boolean isChildOf(com.yahoo.path.Path)",
"public com.yahoo.path.Path append(java.lang.String)",
"public com.yahoo.path.Path append(com.yahoo.path.Path)",
diff --git a/vespajlib/src/main/java/com/yahoo/path/Path.java b/vespajlib/src/main/java/com/yahoo/path/Path.java
index 16370059c8c..737a27c57d8 100644
--- a/vespajlib/src/main/java/com/yahoo/path/Path.java
+++ b/vespajlib/src/main/java/com/yahoo/path/Path.java
@@ -41,6 +41,11 @@ public final class Path {
this.delimiter = delimiter;
}
+ /** Creates a new path with the given segments. */
+ public static Path from(List<String> segments) {
+ return new Path(segments, "/");
+ }
+
/** Returns whether this path is an immediate child of the given path */
public boolean isChildOf(Path parent) {
return toString().startsWith(parent.toString()) && this.elements.size() -1 == parent.elements.size();
diff --git a/vespajlib/src/main/java/com/yahoo/text/Text.java b/vespajlib/src/main/java/com/yahoo/text/Text.java
index 30eba3ebd65..501ca980187 100644
--- a/vespajlib/src/main/java/com/yahoo/text/Text.java
+++ b/vespajlib/src/main/java/com/yahoo/text/Text.java
@@ -53,6 +53,7 @@ public final class Text {
: isTextCharAboveUsAscii(codepoint);
}
private static boolean isTextCharAboveUsAscii(int codepoint) {
+ // TODO jonmv: compute modulo?
if (codepoint < 0xFDD0) return true;
if (codepoint <= 0xFDDF) return false;
if (codepoint < 0x1FFFE) return true;