What you’ll build
Home page
Link Preview
API for fetching link preview info by URL
What you’ll need
- Spring Tool Suite 4
- JDK 11
Tech Stack
- Spring Boot 2.2
- JSP and Bootstrap 4.1.3
- JQuery 3.4.1
- Jsoup 1.12.1
What is Rich Link Preview ?
Rich Link Preview allows you to see a peek of the link location and usually generated realtime by visiting the website, and reading the meta tags present in the web page.
Bootstrap Your Application
You can create your spring boot application with the web & devtools dependencies and download it from here
Project Structure
+---pom.xml
|
|
\---src
\---main
+---java
| \---com
| \---javachinna
| \---linkpreview
| | SpringBootLinkPreviewApplication.java
| |
| +---controller
| | PagesController.java
| |
| \---model
| Link.java
|
+---resources
| application.properties
|
\---webapp
\---WEB-INF
\---views
\---pages
link.jsp
previewLink.jsp
Project Dependencies
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.javachinna</groupId>
<artifactId>spring-boot-link-preview</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-link-preview</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!-- To compile JSP files -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.12.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.1-jre</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!-- <scope>runtime</scope> -->
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Create Controller
PagesController.java
@RestController
public class PagesController {
private final Logger logger = LogManager.getLogger(getClass());
@GetMapping({ "/", "/home" })
public ModelAndView home(@RequestParam(value = "view", required = false) String view) {
logger.info("Entering home page");
ModelAndView model = new ModelAndView("previewLink");
model.addObject("title", "Home");
model.addObject("view", view);
return model;
}
/**
* Fetches rich link preview info for the given URL based on the meta tags
* present in the web page
*
*
* @param url
* @return
*/
@GetMapping("/api/link/preview")
public Link getLinkPreviewInfo(@RequestParam(value = "url", required = true) String url) {
Link link = null;
try {
link = extractLinkPreviewInfo(url);
} catch (IOException e) {
logger.error("Unable to connect to : {}", url);
}
return link;
}
/**
* Generates rich link preview for the given URL based on the meta tags present
* in the web page
*
* @param url
* @return
*/
@GetMapping("/link/preview")
public ModelAndView linkPreview(@RequestParam(value = "url", required = true) String url) {
ModelAndView model = new ModelAndView("link");
try {
model.addObject("link", extractLinkPreviewInfo(url));
} catch (IOException e) {
logger.error("Unable to connect to : {}", url);
model.addObject("css", "danger");
model.addObject("msg", "Unable to connect to '" + url + "': " + e.getMessage());
}
return model;
}
/**
* Parses the web page and extracts the info from meta tags required for preview
*
* @param url
* @return
* @throws IOException
*/
private Link extractLinkPreviewInfo(String url) throws IOException {
if (!url.startsWith("http")) {
url = "http://" + url;
}
Document document = Jsoup.connect(url).get();
String title = getMetaTagContent(document, "meta[name=title]");
String desc = getMetaTagContent(document, "meta[name=description]");
String ogUrl = StringUtils.defaultIfBlank(getMetaTagContent(document, "meta[property=og:url]"), url);
String ogTitle = getMetaTagContent(document, "meta[property=og:title]");
String ogDesc = getMetaTagContent(document, "meta[property=og:description]");
String ogImage = getMetaTagContent(document, "meta[property=og:image]");
String ogImageAlt = getMetaTagContent(document, "meta[property=og:image:alt]");
String domain = ogUrl;
try {
domain = InternetDomainName.from(new URL(ogUrl).getHost()).topPrivateDomain().toString();
} catch (Exception e) {
logger.warn("Unable to connect to extract domain name from : {}", url);
}
return new Link(domain, url, StringUtils.defaultIfBlank(ogTitle, title), StringUtils.defaultIfBlank(ogDesc, desc), ogImage, ogImageAlt);
}
/**
* Returns the given meta tag content
*
* @param document
* @return
*/
private String getMetaTagContent(Document document, String cssQuery) {
Element elm = document.select(cssQuery).first();
if (elm != null) {
return elm.attr("content");
}
return "";
}
}
For simplicity, I’ve added the link preview info fetching logic in the controller itself instead of putting them into the service layer.
@RestController
annotation is a convenience annotation that is itself annotated with @Controller
and @ResponseBody
. This annotation is applied to a class to mark it as a request handler. Spring RestController annotation is used to create RESTful web services using Spring MVC
@GetMapping
is a composed annotation that acts as a shortcut for @RequestMapping(method = RequestMethod. GET)
Note: The
getLinkPreviewInfo()
method is the REST service mapped with path/api/link/preview
. So If you are just looking for a REST service which will return the preview info for the given URL as a JSON response, then you can get it by htting http://localhost:8080/api/link/preview?url=<website url>
Create Model Class
Link.java
public class Link {
private static final long serialVersionUID = -706243242873257798L;
public Link() {
}
public Link(String domain, String url, String title, String desc, String image, String imageAlt) {
this.domain = domain;
this.url = url;
this.title = title;
this.desc = desc;
this.image = image;
this.imageAlt = imageAlt;
}
private String domain;
private String url;
private String title;
private String desc;
private String image;
private String imageAlt;
// Setters and Getters goes here
}
Create Spring Boot Application Class
SpringBootLinkPreviewApplication.java
@SpringBootApplication
Indicates a configuration
class that declares one or more @Bean
methods and also triggers auto-configuration
and component scanning
. This is a convenience annotation that is equivalent to declaring @Configuration
, @EnableAutoConfiguration
and @ComponentScan
@SpringBootApplication
public class SpringBootLinkPreviewApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootLinkPreviewApplication.class, args);
}
}
Create View Pages
linkPreview.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<title>Demo</title>
</head>
<body>
<div class="container-fluid mt-2">
<div class="row">
<div class="col-12 col-md-5">
<c:url value="/postLink" var="actionUrl" />
<div class="form-group">
<label for="title">Link </label> <input type="text" class="form-control" id="url" aria-describedby="url" placeholder="Enter URL here" />
</div>
<div class="mb-2">
<button class="btn btn-sm btn-dark" value="preview" onclick="preview()">Preview Link</button>
</div>
<div class="form-group" id="link-preview"></div>
</div>
<div class="col-md-7 d-none d-md-block"></div>
</div>
</div>
<script >
function preview() {
var url = $('#url').val();
if (url) {
$.get('/link/preview?url=' + url, function(data, status) {
$('#link-preview').html(data);
});
}
}
</script>
</body>
</html>
link.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:choose>
<c:when test="${empty msg}">
<div class="card shadow mb-2">
<div class="card-body">
<h6 class="text-muted pb-2 m-0">
<i class="fas fa-link"></i> Link Preview
</h6>
<a href="${link.url}" class="custom-card" target="_blank" data-image="${link.image}" data-image-alt="${link.imageAlt}" data-title="${link.title}"
data-desc="${link.desc}" data-domain="${link.domain}">
<div class="card">
<img src="${link.image}" class="card-img-top" alt="${link.imageAlt}" style="max-height: 300px; object-fit: fill;">
<div class="card-footer">
<h5 class="card-title">${link.title}</h5>
<p class="card-text">${link.desc}</p>
<p class="card-text">
<small class="text-muted">${link.domain}</small>
</p>
</div>
</div>
</a>
</div>
</div>
</c:when>
<c:otherwise>
<div class="alert alert-${css} alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<strong>${msg}</strong>
</div>
</c:otherwise>
</c:choose>
Create application.properties file
spring.mvc.view.prefix=/WEB-INF/views/pages/
spring.mvc.view.suffix=.jsp
Run with Maven
You can run the application using mvn clean spring-boot:run
and visit to http://localhost:8080
Source Code
https://github.com/Chinnamscit/spring-boot-link-preview
Conclusion
That’s all folks! In this tutorial, you’ve learned how to get link preview info for a given url and generate the preview using spring boot application.
I hope you enjoyed this tutorial. Thank you for reading.