Spring request mapping with regex like in javax.ws.rs - java

I'm trying rewrite this Google App Engine maven server repository to Spring.
I have problem with URL mapping.
Maven repo server standard looks like this:
URL with slash at the end, points to a folder, example:
http://127.0.0.1/testDir/
http://127.0.0.1/testDir/testDir2/
all others (without slash at the end) point to files, example:
http://127.0.0.1/testFile.jar
http://127.0.0.1/testFile.jar.sha1
http://127.0.0.1/testDir/testFile2.pom
http://127.0.0.1/testDir/testFile2.pom.md5
Original app mapping for directories and for files.
There were used annotations #javax.ws.rs.Path which supports regexy differently than Spring.
I tried bunch of combinations, for example something like this:
#ResponseBody
#GetMapping("/{file: .*}")
public String test1(#PathVariable String file) {
return "test1 " + file;
}
#ResponseBody
#GetMapping("{dir: .*[/]{1}$}")
public String test2(#PathVariable String dir) {
return "test2 " + dir;
}
But I can't figure out how to do this in right way in Spring application.
I'd like to avoid writing a custom servlet dispatcher.

I had a similar problem once, also regarding a Spring implementation of a maven endpoint.
For the file endpoints, you could do something like this
/**
* An example Maven endpoint for Jar files
*/
#GetMapping("/**/{artifactId}/{version}/{artifactId}-{version}.jar")
public ResponseEntity<String> getJar(#PathVariable("artifactId") String artifactId, #PathVariable("version") String version) {
...
}
This gives you the artifactId and the version, but for the groupId you would need to do some string parsing. You can get the current requestUri with the help of the ServletUriComponentsBuilder
String requestUri = ServletUriComponentsBuilder.fromCurrentRequestUri().build().toUri().toString();
// requestUri = /api/v1/com/my/groupId/an/artifact/v1/an-artifact-v1.jar
For the folder endpoints, I'm not sure if this will work, but you can give it a try
#GetMapping("/**/{artifactId}/{version}")
public ResponseEntity<String> getJar(#PathVariable("artifactId") String artifactId, #PathVariable("version") String version) {
// groupId extracted as before from the requestUri
...
}

Don't know about your java code, but if you are verifying one path at a time, you can just check if the string ends in "/" for a folder and the ones that don't are files
\/{1}$
this regular expression just checks that the string ends with "/" if there is a match, you have a folder, if there is not, you have a file

Well there is no other specific standard in Spring then the way you have used it. However if you can customize URL then I have a special way to differentiate directory and files. That will increase the scalibility and readability of application and will reduce lot of code for you.
Your Code as of now
#ResponseBody
#GetMapping("/{file: .*}")
public String test1(#PathVariable String file) {
return "test1 " + file;
}
#ResponseBody
#GetMapping("{dir: .*[/]{1}$}")
public String test2(#PathVariable String dir) {
return "test2 " + dir;
}
Change above code to as below in your controller class
private final Map<String, String> managedEntities=ImmutableMap.of(
"file","Type_Of_Operation_You_want_For_File",
"directory","Type_Of_Operation_You_want_For_Directory"
);
#GetMapping(path = "/{type:file|directory}")
public String myFileOperationControl(#PathVariable String type){
return "Test"+managedEntities.get(type));
}
And proceed further the way you want to per your business logic. Let me know if you have any questions.
Note: Please simply enhance endpoint per your need.

Spring doesn't allow matching to span multiple path segments. Path segments are delimited values of path on path separator (/). So no regex combination will get you there. Spring 5 although allows the span multiple path segments only at the end of path using ** or {*foobar} to capture in foobar uri template variable for reactive stack but I don't think that will be useful for you.
Your options are limited. I think the best option if possible is to use different delimiter than / and you can use regex.
Other option ( which is messy ) to have catch all (**) endpoint and read the path from the request and determine if it is file or directory path and perform actions.

Try this solution:
#GetMapping("**/{file:.+?\\..+}")
public String processFile(#PathVariable String file, HttpServletRequest request) {
return "test1 " + file;
}
#GetMapping("**/{dirName:\\w+}")
public String processDirectory(#PathVariable String dirName, HttpServletRequest request) {
String dirPath = request.getRequestURI();
return "test2 " + dirPath;
}
Results for URIs from the question:
test2 /testDir/
test2 /testDir/testDir2/
test1 testFile.jar
test1 testFile.jar.sha1
test1 testFile2.pom
test1 testFile2.pom.md5

Related

How to parse dynamic nested paths in spring controller

Hi all I am writing a spring controller to printout the contents of a directory so my controller signature is
#RequestMapping(value = "/listdir/{dirname}")
public ResponseEntity<List<DirectoryItem>> listDirectory(#PathVariable String dirname){
List<DirectoryItem> directories = util.getDirectoryItems(rootFolder + dirname);
I call it with a get:
/listdir/dirname
and it returns a json of the contents of the directory. eg
file1,
file2,
dir1,
dir2,
and so on.
So next what i want is that in the view if the user clicks a directory name, the same method is called.
This time i imagine the call would be
listdir/dirname/dir1
and so on for the next would be
listdir/dirname/dir1/childdir
Does anyone know what's the correct way of accomplishing this?
Thanks
You can use a wildcard in a mapping like this
#GetMapping("listdir/**")
public String listDirectory(HttpServletRequest request) {
String dirname = request.getRequestURI().split(request.getContextPath() + "/listdir/")[1];
// ...
}
but basically for such request you should not use path parameters at all. Instead of this just send the parameter through the request parameter
#GetMapping("all")
public String listDirectory(#RequestParam("dirname") String dirname) {
// ...
}

How to Pass URL as Path Variable

I am trying to pass a URL as a path variable, but when I input the URL as a parameter and access the route, it returns an error. I want to be able to see the address after it passes into the route as a parameter.
#RequestMapping("/addAddress/{address}")
public String addAddress(#PathVariable("address") String address) {
System.out.println("Address: "+address)
return address;
}
For example, if I put into the URL:
localhost:8080/addAddress/http://samplewebsite.com
I should see
http://samplewebsite.com
printed out in the back end.
The forward slashes are your issue.
You have a few choices.
Use 2 path variables. This still works with a double forward slash. This works with URL: "localhost:8080/addAddress/http://samplewebsite.com"
#GetMapping("/addAddress/{schema}/{address}")
public String addAddress(#PathVariable("schema") String schema, #PathVariable("address") String address) {
System.out.println("Address: "+ schema + "//" + address);
return schema + "//" + address;
}
Use a query (request) param, your url would then be URL: "localhost:8080/addAddress/?address=http://samplewebsite.com"
#GetMapping("/addAddress2")
public String addAddress2(#RequestParam("address") String address) {
System.out.println("Address: "+address);
return address;
}
Encode the slashes in URL:
localhost:8080/addAddress/http:%2F%2Fsamplewebsite.com"
and configure Tomcat or Jetty, whatever you use to allow encoded slashes. Here is an example in Tomcat
You can do the following;
#RequestMapping("/addAddress/**")
public String addAddress(HttpServletRequest request) {
String fullUrl = request.getRequestURL().toString();
String url = fullUrl.split("/addAddress/")[1];
System.out.println(url);
return url;
}
with #PathVariable that is not doable due to the / char breaking the behaviour you are looking for, unless you encode/decode, but I feel like this is a simpler way to go for both user of the endpoint, and for the backend.
Also this will not fetch the request query part, e.g. ?input=user,
to do that you can add this logic
You can use SafeUrl to parse your URL in your path.
Some documentation:
https://www.urlencoder.io/java/
It let you pass parameters safe by the url and you can decode where you need.

Any way to use #RequestMapping and #PathVariable in Spring (Boot) to capture a variable path?

I'm looking for a way to catch any URL, such as:
mydomain.com/first/second/third
mydomain.com/first/second/third/fourth
mydomain.com/first/
and be able to catch it in a single method in my controller and build a string with the path, like:
for (String pathVar : pathVariableArray){
stringToBuild += pathVar + '.'
}
and get the following results:
stringToBuild = "first.second.third."
stringToBuild = "first.second.third.fourth."
stringToBuild = "first."
Is there any way? I don’t want to have to code various methods for a different length of the path.
#RequestMapping("/**")
public void method(HttpServletRequest request) {
String stringToBuild = request.getServletPath().replace("/", ".") + ".";
For alternate mapping methods, see Spring boot - Controller catching all URLs.
You may need to be more sophisticated to avoid an double . at the end if the path ends with a /.

Java URL Class getPath(), getQuery() and getFile() inconsistent with RFC3986 URI Syntax

I am writing a utility class that semi-wraps Java's URL class, and I have written a bunch of test cases to verify the methods I have wrapped with a customized implementation. I don't understand the output of some of Java's getters for certain URL strings.
According to the RFC 3986 specification, a path component is defined as follows:
The path is terminated by the first question mark ("?") or number sign
("#") character, or by the end of the URI.
A query component is defined as follows:
The query component is indicated by the first question
mark ("?") character and terminated by a number sign ("#") character
or by the end of the URI.
I have a couple test cases which are treated by Java as valid URLs, but getters for path, file and query don't return the values I had expected:
URL url = new URL("https://www.somesite.com/?param1=val1");
System.out.print(url.getPath());
System.out.println(url.getFile());
System.out.println(url.getQuery());
The above results in the following output:
//?param1=val1
param1=val1
<empty string>
My other test case:
URL url = new URL("https://www.somesite.com?param1=val1");
System.out.print(url.getPath());
System.out.println(url.getFile());
System.out.println(url.getQuery());
The above results in the following output:
?param1=val1
param1=val1
<empty string>
According to the documentation for Java URL:
public String getFile()
Gets the file name of this URL. The returned file portion will be the
same as getPath(), plus the concatenation of the value of getQuery(), if
any. If there is no query portion, this method and getPath() will return
identical results.
Returns:
the file name of this URL, or an empty string if one does not exist
So, my test cases result in empty string when getQuery() is invoked. In which case, I would expected getFile() to return the same value as getPath(). This is not the case.
I had expected the following output for both test cases:
<empty string>
?param1=val1
param1=val1
Maybe my interpretation of the RFC 3986 is not correct. But the output I have seen also does not line up with the documentation for the URL class either? Can anyone explain what I am seeing?
Here some executable code based on your fragments:
import java.net.MalformedURLException;
import java.net.URL;
public class URLExample {
public static void main(String[] args) throws MalformedURLException {
printURLInformation(new URL("https://www.somesite.com/?param1=val1"));
printURLInformation(new URL("https://www.somesite.com?param1=val1"));
}
private static void printURLInformation(URL url) {
System.out.println(url);
System.out.println("Path:\t" + url.getPath());
System.out.println("File:\t" + url.getFile());
System.out.println("Query:\t" + url.getQuery() + "\n");
}
}
Works fine, here is the result as you might have expected. The only difference is, that you used one System.out.print, followed by System.out.println that printed the result for path and file in the same line.
https://www.somesite.com/?param1=val1
Path: /
File: /?param1=val1
Query: param1=val1
https://www.somesite.com?param1=val1
Path:
File: ?param1=val1
Query: param1=val1

Get last part of url using a regex

How do I get the last part of the a URL using a regex, here is my URL, I want the segmeent between the last forward slash and the #
http://mycompany.com/test/id/1234#this
So I only want to get 1234.
I have the following but is not removing the '#this'
".*/(.*)(#|$)",
I need this while indexing data so don't want to use the URL class.
Just use URI:
final URI uri = URI.create(yourInput);
final String path = uri.getPath();
path.substring(path.lastIndexOf('/') + 1); // will return what you want
Will also take care of URIs with query strings etc. In any event, when having to extract any part from a URL (which is a URI), using a regex is not what you want: URI can handle it all for you, at a much lower cost -- since it has a dedicated parser.
Demo code using, in addition, Guava's Optional to detect the case where the URI has no path component:
public static void main(final String... args) {
final String url = "http://mycompany.com/test/id/1234#this";
final URI uri = URI.create(url);
final String path = Optional.fromNullable(uri.getPath()).or("/");
System.out.println(path.substring(path.lastIndexOf('/') + 1));
}
how about:
".*/([^/#]*)(#.*|$)"
Addition to what #jtahlborn answer to include query string:
".*/([^/#|?]*)(#.*|$)"

Categories