简体   繁体   中英

React-SpringBoot implementing URL shortener redirection

I'm trying to learn a few things by implementing a URL Shortner using Spring-boot, React, SQLite3.

My Controller class is supposed to

  • render the index when someone goes to http://localhost:8080 . Where they get the option to input a long URL, click a button, to get a shortened URL say aBcD . This is index() at path / .
  • redirect to long URL when someone goes to http://localhost:8080/aBcD . This is redirectTinyurl() at path /{shortUrl} .
  • I've also added the REST API that creates these mappings (long <> short) in same Controller . This is createTinyurl() at path /tinyurl .

Problem (I think) is when index is rendered, and browser tries to fetch http://localhost:8080/main.css it matches up with URL pattern ( /{shortUrl} ) and Spring calls redirectTinyurl() in teh Controller .

Is there a way to make Spring serve such resources by itself and also call my redirectTinyurl() when appropriate? Or a way where in I can pass some calls to Spring, if I can figure out that this is a request for resource not someone trying to resolve a URL.

Interestingly it can differentiate between /tinyurl and /{shortUrl} .

My main.css is already in src/main/resources/static/main.css . Here is full folder structure.

Controller

@Controller
public class HomeController {
    private final TinyUrlRepository repository;

    @GetMapping(value = "/")
    public String index() {
        logger.info("rendering index");
        return "index";
    }

    @GetMapping(value = "/{shortUrl}")
    public ResponseEntity<String> redirectTinyurl(@PathVariable String shortUrl) {
        logger.info("redirectTinyurl() shortUrl: {}", shortUrl);
        UrlMapping resolvedUrl = repository.findById(Long.parseLong(shortUrl)).get();
        logger.info("redirecting to: resolvedUrl: {}", resolvedUrl);
        HttpHeaders headers = new HttpHeaders();
        headers.setLocation(URI.create(resolvedUrl.getLongUrl()));
        return new ResponseEntity<>(headers, HttpStatus.MOVED_PERMANENTLY);
    }

    @PostMapping(path = "/tinyurl")
    @ResponseBody
    public String createTinyurl(@RequestBody UrlMappingPojo body /*contains long URL*/) {
          // ... create a mapping, store in DB and return a shortened URL
    }
}

index.html

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head lang="en">
    <meta charset="UTF-8"/>
    <title>TinyURL experiment</title>
    <link rel="stylesheet" href="/main.css" />
</head>
<body>
    <div id="react"></div>
    <script src="built/bundle.js"></script>
</body>
</html>

Output

2021-10-02 15:01:44.656  INFO 40980 --- rendering index
2021-10-02 15:01:44.985  INFO 40980 --- redirectTinyurl() shortUrl: main.css
2021-10-02 15:01:45.011 ERROR 40980 --- Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NumberFormatException: For input string: "main.css"] with root cause

java.lang.NumberFormatException: For input string: "main.css"
    at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:68) ~[na:na]
    at java.base/java.lang.Long.parseLong(Long.java:707) ~[na:na]
    at java.base/java.lang.Long.parseLong(Long.java:832) ~[na:na]
    at com.greglturnquist.payroll.HomeController.redirectTinyurl(HomeController.java:61) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.30.jar:9.0.30]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) ~[na:na]
    .........
    at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]

According to your code it seems like you are using Themeleaf in your project.

I replicated your problem with following,

 @GetMapping(value = "/{shortUrl}")
public ResponseEntity<String> redirectTinyurl(@PathVariable String shortUrl, HttpServletResponse response)  throws IOException {
    if(shortUrl.equals("google"))
        response.sendRedirect("http://www.google.com");
    return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}

This will redirect to google if someone goes to http://localhost:8080/google .

According to WebMvcConfigurationSupport documentation , first it will consider the annotated controller methods. /{shortUrl} in your case. Since /main.css & /{shortUrl} has the similar url partern, both resolved to same method. However /tinyirl is specifically mentioned in the controller, it will not be confused with /{shortUrl} .

If you want to use the main.css and other in the same directory as your index, you will have to add wildcard patterns to exclude them using the controller method /{shortUrl} .

But there is a simple solution for solve this.(I think it is also a best practice) That is moving your main.css to folder similar to /static/css/ .Refer the image below.

在此处输入图像描述

You can change the stylesheet reference as follows and you are good to go.

<link rel="stylesheet" href="/css/main.css" />

What happened here is now. /css/main.css patern looks for a annotated controller method with /{something}/{something} . Since that is not there, it will go to resources and resolve the css file.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM