Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RESTEASY-3266] RestEasy JSAPI Servlet does not work with root "/" or empty @ApplicationPath("") #3520

Merged
merged 1 commit into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ public void writeJavaScript(String base, HttpServletRequest req, HttpServletResp

for (Map.Entry<String, ServiceRegistry> entry : serviceRegistries.entrySet()) {
String uri = base;
if (entry.getKey() != null)
uri += entry.getKey();

if (entry.getKey() != null) {
uri = resolveDoubleSlash(uri, entry.getKey(), true);
}
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(new BufferedWriter(stringWriter));
writeJavaScript(uri, writer, entry.getValue());
Expand Down Expand Up @@ -98,7 +98,7 @@ public void writeJavaScript(String uri, PrintWriter writer,
LogMessages.LOGGER.debug(Messages.MESSAGES.restApiUrl(uri));
writer.println("REST.apiURL = '" + uri + "';");
Set<String> declaredPrefixes = new HashSet<String>();
printService(writer, serviceRegistry, declaredPrefixes);
printService(writer, serviceRegistry, declaredPrefixes, uri);

}

Expand All @@ -124,7 +124,7 @@ private void generateEtag(ServiceRegistry serviceRegistry, StringBuilder etagBui
}

private void printService(PrintWriter writer,
ServiceRegistry serviceRegistry, Set<String> declaredPrefixes) {
ServiceRegistry serviceRegistry, Set<String> declaredPrefixes, String uri) {

for (MethodMetaData methodMetaData : serviceRegistry.getMethodMetaData()) {
LogMessages.LOGGER.debug(Messages.MESSAGES.path(methodMetaData.getUri()));
Expand All @@ -133,11 +133,11 @@ private void printService(PrintWriter writer,
declarePrefix(writer, declaringPrefix, declaredPrefixes);

for (String httpMethod : methodMetaData.getHttpMethods()) {
print(writer, httpMethod, methodMetaData);
print(writer, httpMethod, methodMetaData, uri);
}
}
for (ServiceRegistry subService : serviceRegistry.getLocators())
printService(writer, subService, declaredPrefixes);
printService(writer, subService, declaredPrefixes, uri);
}

private void declarePrefix(PrintWriter writer, String declaringPrefix, Set<String> declaredPrefixes) {
Expand Down Expand Up @@ -166,20 +166,21 @@ private void copyResource(String name, PrintWriter writer)
}

private void print(PrintWriter writer, String httpMethod,
MethodMetaData methodMetaData) {
String uri = methodMetaData.getUri();
writer.println("// " + httpMethod + " " + uri);
MethodMetaData methodMetaData, String uri) {
String methodUri = methodMetaData.getUri();
writer.println("// " + httpMethod + " " + methodUri);
writer
.println(methodMetaData.getFunctionName() + " = function(_params){");
writer.println(" var params = _params ? _params : {};");
writer.println(" var request = new REST.Request();");
writer.println(" request.setMethod('" + httpMethod + "');");
writer
.println(" var uri = params.$apiURL ? params.$apiURL : REST.apiURL;");
if (uri.contains("{")) {
printURIParams(uri, writer);
if (methodUri.contains("{")) {
printURIParams(methodUri, writer);
} else {
writer.println(" uri += '" + uri + "';");
String resolvedMethodUri = resolveDoubleSlash(uri, methodUri, false);
writer.println(" uri += '" + resolvedMethodUri + "';");
}
printOtherParams(methodMetaData, writer);
writer.println(" request.setURI(uri);");
Expand Down Expand Up @@ -284,4 +285,47 @@ private void printURIParams(String uri, PrintWriter writer) {
if (i < replacedCurlyURI.length())
writer.println(" uri += '" + replacedCurlyURI.substring(i) + "';");
}

/**
* Resolves the case of {@code uri} ending with "/" and {@code uriToAppend} starting with "/" so that the combination
* of both ends up with only one "/" instead of "//".
*
* <br>
* <br>
* Example:
* http://localhost:8080/Uri//UriToAppend -> http://localhost:8080/Uri/UriToAppend
*
* @param uri the base URI with the possible extra "/" at the end.
* @param uriToAppend the URI being appended to the base one, with possible extra "/" at the beginning.
* @param appendToUri true if the {@code uriToAppend} should be appended to the {@code uri}, false if the
* {@code uriToAppend} should have the "/" removed without appending it to the {@code uri}.
* @return The {@code uriToAppend} appended to the {@code uri} if the {@code appendToUri} is true, the {@code uriToAppend}
* without the extra "/" at the beginning if the {@code appendToUri} is false. In both cases there are no two "//"
* between the {@code uri} and the {@code uriToAppend}.
*/
private String resolveDoubleSlash(String uri, String uriToAppend, boolean appendToUri) {
if (uri.endsWith("/") && uriToAppend.startsWith("/")) {
// Remove the extra "/" at the beginning of uriToAppend
if (uriToAppend.length() > 1) {
if (appendToUri) {
return uri.concat(uriToAppend.substring(1));
} else {
return uriToAppend.substring(1);
}
}
// Edge case if the uriToAppend is just "/"
else {
if (appendToUri) {
return uri;
}
return "";
}
}

// If the
if (appendToUri) {
return uri.concat(uriToAppend);
}
return uriToAppend;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
import jakarta.ws.rs.core.Response;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.logging.Logger;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.spi.HttpResponseCodes;
import org.jboss.resteasy.test.jsapi.resource.CustomResource;
import org.jboss.resteasy.test.jsapi.resource.RootApplication;
import org.jboss.resteasy.utils.PortProviderUtil;
import org.jboss.resteasy.utils.TestUtil;
import org.jboss.shrinkwrap.api.Archive;
Expand All @@ -29,16 +32,27 @@
@RunAsClient
public class JSAPIGetBasicJsapiHandlingScriptTest {

private static final String JSAPI = "jsapi";
private static final String DOUBLESLASH = "doubleSlash";

static ResteasyClient client;
protected static final Logger logger = Logger.getLogger(JSAPIGetBasicJsapiHandlingScriptTest.class.getName());

@Deployment
public static Archive<?> deploy() {
WebArchive war = TestUtil.prepareArchive(JSAPIGetBasicJsapiHandlingScriptTest.class.getSimpleName());
@Deployment(name = JSAPI)
public static Archive<?> deployJSAPI() {
WebArchive war = TestUtil.prepareArchive(JSAPI);
war.addAsWebInfResource(JSAPIGetBasicJsapiHandlingScriptTest.class.getPackage(), "web.xml", "web.xml");
return TestUtil.finishContainerPrepare(war, null, (Class<?>[]) null);
}

@Deployment(name = DOUBLESLASH)
public static Archive<?> deployDoubleSlash() {
WebArchive war = TestUtil.prepareArchive(DOUBLESLASH);
war.addClasses(CustomResource.class, RootApplication.class);
war.addAsWebInfResource(JSAPIGetBasicJsapiHandlingScriptTest.class.getPackage(), "web_double_slash.xml", "web.xml");
return TestUtil.finishContainerPrepare(war, null, (Class<?>[]) null);
}

@Before
public void init() {
client = (ResteasyClient) ClientBuilder.newClient();
Expand All @@ -49,8 +63,8 @@ public void after() throws Exception {
client.close();
}

private String generateURL(String path) {
return PortProviderUtil.generateURL(path, JSAPIGetBasicJsapiHandlingScriptTest.class.getSimpleName());
private String generateURL(String path, String deploymentName) {
return PortProviderUtil.generateURL(path, deploymentName);
}

/**
Expand All @@ -59,13 +73,33 @@ private String generateURL(String path) {
* @tpSince RESTEasy 3.0.16
*/
@Test
@OperateOnDeployment(JSAPI)
public void testGetJsapiHeaderScript() throws Exception {
WebTarget target = client.target(generateURL("/rest-js"));
WebTarget target = client.target(generateURL("/rest-js", JSAPI));
Response response = target.request().get();
Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus());
String responseString = response.readEntity(String.class);
logger.info(responseString);
Assert.assertTrue("Basic javascript generated by jsapi doesn't contain expected code",
responseString.contains("REST.Request = function() {"));
}

/**
* @tpTestDetails Tests whether the {@code request.setURI(uri)} would contain "//" in case the combination of
* {@code Path} and {@code ApplicationPath} would produce "//" or if the extra "/" was removed
* @tpSince RESTEasy 6.3.0
*/
@Test
@OperateOnDeployment(DOUBLESLASH)
public void testDoubleSlashInURI() {
WebTarget target = client.target(generateURL("/rest-js", DOUBLESLASH));
Response response = target.request().get();
Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus());
String responseString = response.readEntity(String.class);
logger.info(responseString);
Assert.assertTrue("Basic javascript generated by jsapi doesn't contain expected line",
responseString.contains("8080/doubleSlash/rootApplication/"));
Assert.assertTrue("Basic javascript generated by jsapi doesn't contain expected line",
responseString.contains("uri += 'path';"));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a license header.

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* JBoss, Home of Professional Open Source.
*
* Copyright 2023 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jboss.resteasy.test.jsapi.resource;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;

@Path("/path/")
public class CustomResource {

@GET
@Produces("application/xml")
public String get() {
return "Hello world!";
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a license header.

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* JBoss, Home of Professional Open Source.
*
* Copyright 2023 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jboss.resteasy.test.jsapi.resource;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@ApplicationPath("/rootApplication/")
public class RootApplication extends Application {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
<display-name>Archetype Created Web Application</display-name>

<servlet>
<servlet-name>Resteasy JSAPI</servlet-name>
<servlet-class>org.jboss.resteasy.jsapi.JSAPIServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>Resteasy JSAPI</servlet-name>
<url-pattern>/rest-js</url-pattern>
</servlet-mapping>

</web-app>