Unexpected application root path behaviour
Closed this issue · 11 comments
Currently the following combination :
- API Gateway resource path = "/api/{+proxy}"
- JAX-RS Resource class @path("/helloworld")
Does not resolve/work. It is expecting the root resource classes to have the same path prefix as the AG resource path (so @path("/api/..") in this example). This is unexpected. The combination above should result in :
https://mysite.com/{stage}/api/helloworld
Since the configuration implies the jersey application route is served by the {+proxy} section. It would be good if the Application instance root path is configurable (e.g. through @ApplicationPath which does not seem to be respected) or if the default is changed.
Hi @remonvv ,
You can already configure the baseUri:
public class YourRequestHandler extends GatewayRequestObjectHandler {
public YourRequestHandler() {
super(URI.create("/api/")); // use a trailing slash!
...
init(config);
start();
}
}
Does this solve your problem?
Supporting @ApplicationPath might be an idea for the future but I never saw it being supported by any container except servlet containers; I will re-check.
I am not sure if I understand what you mean by "or if the default is changed". One can have multiple API Gateway resource paths e.g. "/api1/{+proxy}" , "/api2/{+proxy}", ... or even "/api3/some/thing" which all invoke the same handler/function, so it's difficult to assume the baseUri automatically.
Hi @bbilger
That does address it somewhat perhaps.
So, my expectation was/is that the JAX-RS resources have as their root the base URI of the API Gateway/Lamba proxy endpoint. So in your example I'd expect "api1/api2/api3" to be separate functions/containers defined in serverless.yml for example rather than having to be defined in @path, especially since most services consist of many JAX-RS resource classes that'd all have to prefix their @path with "api1/2/3" in your example (if i understand correctly). Basically I'd expect the {+proxy} section of the path to be JAX-RS container root rather than having them overlap :
How it is now :
path: api/{+proxy) + @path("/api/auth") -> reachable at https://domain/api/auth
path: api/{+proxy) + @path("/auth") -> unreachable
Expectation :
path: api/{+proxy) + @path("/api/auth") -> reachable at https://domain/api/api/auth
path: api/{+proxy) + @path("/auth") -> reachable at https://domain/api/auth
Right now it feels like I'm defining part of the path twice. Hope that makes sense.
Hi @remonvv
So in your example I'd expect "api1/api2/api3" to be separate functions/containers defined in serverless.yml
No that's not the case. See for example: https://github.com/bbilger/jrestless-examples/blob/master/aws/gateway/aws-gateway-security-cognito-authorizer/serverless.yml In the example 4 APIGW endpoints are defined: api/public
, api/private
, ui
, ui/{proxy+}
and all of them use the same function/container. So there really is no way to "assume" the base URI, here.
Well, you can define the base URI for a resource (class), so you don't have to repeat it on all endpoints (method). See for example https://github.com/bbilger/jrestless-examples/blob/master/aws/gateway/aws-gateway-security-cognito-authorizer/src/main/java/com/jrestless/aws/examples/ApiResource.java
If you are sure about your base URI, I suggest you set it in the handler as suggested above: super(URI.create("/api/"));
So you no longer have to mention "api" on the resource or endpoint.
I can't think of any other solutions, here, that I could implement in the framework as a general solution since the function (handler, container, resources, endpoints) don't have any clue about APIGW endpoints defined in serverless.yml
If you are sure about your base URI, I suggest you set it in the handler as suggested above: super(URI.create("/api/")); So you no longer have to mention "api" on the resource or endpoint.
Scratch this! This is not doing what it is supposed to do. I think there's a bug; I'll check. (the other points are still valid, though)
What do you think about the following, @remonvv ?
import java.io.IOException;
import java.net.URI;
import javax.inject.Inject;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.Application;
@PreMatching
public class ApplicationPathFilter implements ContainerRequestFilter {
private static final URI ROOT_URI = URI.create("/");
private final URI applicationPathUri;
@Inject
ApplicationPathFilter(Application applicationConfig) {
ApplicationPath ap = applicationConfig.getClass().getAnnotation(ApplicationPath.class);
if (ap != null) {
String path = ap.value();
path = path.trim();
if (!path.startsWith("/")) {
path = "/" + path;
}
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
if (path.endsWith("/*")) {
path = path.substring(0, path.length() - 2);
}
applicationPathUri = URI.create(path);
} else {
applicationPathUri = null;
}
}
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
if (applicationPathUri != null) {
URI requestUri = requestContext.getUriInfo().getRequestUri();
URI newUri = applicationPathUri.relativize(requestUri);
newUri = ROOT_URI.resolve(newUri);
requestContext.setRequestUri(newUri);
}
}
}
You would use if as follows for APIGW:
@ApplicationPath("api")
public class YourConfig extends ResourceConfig {
public YourConfig() {
register(ApplicationPathFilter.class);
register(GatewayFeature.class);
...
}
}
public class RequestHandler extends GatewayRequestObjectHandler {
public RequestHandler() {
ResourceConfig config = new YourConfig();
init(config);
start();
}
}
@bbilger That's seems like a viable solution to implement my requirement but I think the core design issue remains unaddressed. I think we're having a disconnect about the core problem I'm having. I am short on time now but I'll respond with my thoughts on this today.
Hi @remonvv
I think I understand now that you don't want to repeat yourself. So what do you think about the following?
Some background:
GatewayRequest#getResource returns what is configured in APIGW e.g. /api/{proxy+}
. The requestUri will be e.g. /api/auth
. This filter will set the requestUri to /auth
and should hopefully fulfill your needs. Note: I didn't test it extensively.
import java.io.IOException;
import java.net.URI;
import javax.annotation.Priority;
import javax.inject.Inject;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import com.jrestless.aws.gateway.io.GatewayRequest;
@PreMatching
@Priority(0)
public class DynamicApplicationPathFilter implements ContainerRequestFilter {
private static final String PROXY_SUFFIX = "/{proxy+}";
private static final URI ROOT_URI = URI.create("/");
private final GatewayRequest gatwayRequest; // this is a proxy and it is request-scoped
@Inject
DynamicApplicationPathFilter(GatewayRequest gatwayRequest) {
this.gatwayRequest = gatwayRequest;
}
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
if (isProxyResourceWithApplicationPath()) {
URI requestUri = requestContext.getUriInfo().getRequestUri();
URI newUri = getDynamicApplicationPath().relativize(requestUri);
newUri = ROOT_URI.resolve(newUri);
requestContext.setRequestUri(newUri);
}
}
private boolean isProxyResourceWithApplicationPath() {
String resource = gatwayRequest.getResource();
if (resource == null) {
return false;
} else if (PROXY_SUFFIX.equals(resource)) {
return false;
} else {
return resource.endsWith(PROXY_SUFFIX);
}
}
private URI getDynamicApplicationPath() {
String resource = gatwayRequest.getResource();
return URI.create(resource.substring(0, resource.length() - PROXY_SUFFIX.length()));
}
}
Let me know if this works for you.
I can add this to the framework if you need it but I don't want to enable it by default since in my opinion there are too many cases where this behavior is not accurate/desired. You would be able to enable it by registering it in your ResourceConfig.
Hi @bbilger
Do you have Skype or some way of real-time coms? I think there's either a misunderstanding or a bug. For example, if I checkout and deploy the usage-example project it does not work (the endpoint isn're reachable at {stage}/health/sample) which I suspect is a side effect of the issue/concern I'm talking about ( serverless.yml - http: ANY / does not seem to result in a reachable endpoint regardless of Jersey/@path config)
Hi @remonvv,
You can reach me via skype after 20:00 CET (this week Tuesday, Wednesday and Sunday, only)
Hi @remonvv ,
it's been a while (have been quite busy; sorry) but in case you are still interested, JRestless should support what you need, now, either by using DynamicProxyBasePathFilter (which must be registered explicitly) or by using a custom domain configured accordingly in APIGW.
I updated the way JRestless handles the base- and request URI in v0.5.0 and it's described here: https://github.com/bbilger/jrestless/blob/master/docs/src/docs/asciidoc/uri_handling.adoc