javav2/usecases/create_spring_stream_app/README.md
| Heading | Description |
|---|---|
| Description | Discusses how to a dynamic web application that streams Amazon Simple Storage Service (Amazon S3) video content over HTTP. |
| Audience | Developer (beginner / intermediate) |
| Required skills | Java, Maven |
You can create a dynamic web application that streams Amazon Simple Storage Service (Amazon S3) video content over HTTP. The video is displayed in the application along with a menu that displays the videos that you can view.
In this AWS tutorial, you create a Spring Boot web application. After the application is created, this tutorial shows you how to deploy the application to AWS Elastic Beanstalk.
To complete the tutorial, you need the following:
Create an Amazon S3 bucket that contains 3-5 MP4 files. For information, see Creating a bucket.
Tag each MP4 file with these two tags.
The application you create uses Spring Boot APIs to build a model, different views, and a controller. For more information about Spring Boot APIs, see Spring Boot.
In the previous illustration, notice the video menu that displays video titles and descriptions and used to let users know which videos are available. This web application reads the object tags to dynamically build the video menu. To read the object tags, you use the Amazon S3 Java API (V2). To view a specific video, the user can click the video title. A GET Request is made to a Spring Controller, the application reads the specific video in an Amazon S3 bucket, encodes the byte array, and then steams the data where the video is displayed in an HTML5 Video tag.
This web application also supports uploading MP4 videos to an Amazon S3 bucket. For example, the following illustration shows a video named Rabbit.mp4 along with a description.
Once a video is uploaded into the Amazon S3 bucket, it is displayed in the video menu.
Create an IntelliJ project that is used to create the web application that streams Amazon S3 video content.
In the IntelliJ IDE, choose File, New, Project.
In the New Project dialog box, choose Maven.
Choose Next.
In GroupId, enter spring-aws.
In ArtifactId, enter SpringVideoApp.
Choose Next.
Choose Finish.
Make sure that your project's pom.xml file looks like the POM file in this Github repository.
Create a Java package in the main/java folder named com.example. This Java classes go into this package.
Create these Java classes:
The following Java code represents the Application class.
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class })
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
The following Java code represents the Tags class.
package com.example;
public class Tags {
private String name;
private String description;
public String getDesc() {
return this.description ;
}
public void setDesc(String description){
this.description = description;
}
public String getName() {
return this.name ;
}
public void setName(String name){
this.name = name;
}
}
The following Java code represents the VideoStreamController class.
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import reactor.core.publisher.Mono;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Controller
public class VideoStreamController {
private final VideoStreamService vid;
@Autowired
VideoStreamController(
VideoStreamService vid
) {
this.vid = vid;
}
private final String bucket = "<Enter your S3 bucket>";
@RequestMapping(value = "/")
public String root() {
return "index";
}
@GetMapping("/watch")
public String designer() {
return "video";
}
@GetMapping("/upload")
public String upload() {
return "upload";
}
// Upload a MP4 to an Amazon S3 bucket
@RequestMapping(value = "/fileupload", method = RequestMethod.POST)
@ResponseBody
public ModelAndView singleFileUpload(@RequestParam("file") MultipartFile file, @RequestParam String description) {
try {
byte[] bytes = file.getBytes();
String name = file.getOriginalFilename() ;
// Put the MP4 file into an Amazon S3 bucket.
vid.putVideo(bytes, bucket, name, description);
} catch (IOException e) {
e.printStackTrace();
}
return new ModelAndView(new RedirectView("upload"));
}
// Returns items to populate the Video menu.
@RequestMapping(value = "/items", method = RequestMethod.GET)
@ResponseBody
public String getItems(HttpServletRequest request, HttpServletResponse response) {
String xml = vid.getTags(bucket);
return xml;
}
// Returns the video in the bucket specified by the ID value.
@GetMapping("/{id}/stream")
public Mono<ResponseEntity<StreamingResponseBody>> streamVideo(@PathVariable String id) {
String fileName = id;
return Mono.just(vid.getObjectBytes(bucket, fileName));
}
}
Note: Make sure that you assign an Amazon S3 bucket name to the bucket variable. Otherwise, your code does not work.
The following Java code represents the VideoStreamService class. This class uses the Amazon S3 Java API (V2) to interact with content located in an Amazon S3 bucket. For example, the getTags method returns a collection of tags that are used to create the video menu. Likewise, the getObjectBytes reads bytes from a MP4 video. The byte array is used to create a ResponseEntity object. This object sets HTTP header information and the HTTP status code required to stream the video.
package com.example;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.model.ListObjectsRequest;
import software.amazon.awssdk.services.s3.model.ListObjectsResponse;
import software.amazon.awssdk.services.s3.model.S3Object;
import software.amazon.awssdk.services.s3.model.GetObjectTaggingRequest;
import software.amazon.awssdk.services.s3.model.GetObjectTaggingResponse;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.Tag;
import org.w3c.dom.Document;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Element;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.BufferedOutputStream;
import java.io.StringWriter;
import java.time.Duration;
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.springframework.http.HttpHeaders;
@Service
public class VideoStreamService {
public static final String VIDEO_CONTENT = "video/";
private S3Client getClient() {
return S3Client.builder()
.credentialsProvider(EnvironmentVariableCredentialsProvider.create())
.region(Region.US_WEST_2)
.build();
}
// Places a new video into an Amazon S3 bucket.
public void putVideo(byte[] bytes, String bucketName, String fileName, String description) {
S3Client s3 = getClient();
try {
// Set the tags to apply to the object.
String theTags = "name="+fileName+"&description="+description;
PutObjectRequest putOb = PutObjectRequest.builder()
.bucket(bucketName)
.key(fileName)
.tagging(theTags)
.build();
s3.putObject(putOb, RequestBody.fromBytes(bytes));
} catch (S3Exception e) {
System.err.println(e.awsErrorDetails().errorMessage());
System.exit(1);
}
}
// Returns a schema that describes all tags for all videos in the given bucket.
public String getTags(String bucketName){
S3Client s3 = getClient();
try {
ListObjectsRequest listObjects = ListObjectsRequest.builder()
.bucket(bucketName)
.build();
ListObjectsResponse res = s3.listObjects(listObjects);
List<S3Object> objects = res.contents();
List<String> keys = new ArrayList<>();
for (S3Object myValue: objects) {
String key = myValue.key(); // We need the key to get the tags.
GetObjectTaggingRequest getTaggingRequest = GetObjectTaggingRequest.builder()
.key(key)
.bucket(bucketName)
.build();
GetObjectTaggingResponse tags = s3.getObjectTagging(getTaggingRequest);
List<Tag> tagSet= tags.tagSet();
for (Tag tag : tagSet) {
keys.add(tag.value());
}
}
List<Tags> tagList = modList(keys);
return convertToString(toXml(tagList));
} catch (S3Exception e) {
System.err.println(e.awsErrorDetails().errorMessage());
System.exit(1);
}
return "";
}
// Return a List where each element is a Tags object.
private List<Tags> modList(List<String> myList) {
int count = myList.size();
return IntStream.range(0, count / 2)
.mapToObj(index -> {
Tags myTag = new Tags();
myTag.setName(myList.get(index * 2));
myTag.setDesc(myList.get(index * 2 + 1));
return myTag;
})
.collect(Collectors.toList());
}
// Reads a video from a bucket and returns a ResponseEntity.
public ResponseEntity<StreamingResponseBody> getObjectBytes(String bucketName, String keyName) {
S3Client s3 = getClient();
try {
// Create an S3 object request.
GetObjectRequest objectRequest = GetObjectRequest.builder()
.bucket(bucketName)
.key(keyName)
.build();
// Get the S3 object stream.
ResponseInputStream<GetObjectResponse> objectStream = s3.getObject(objectRequest);
// Set content type and length headers.
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.valueOf(VIDEO_CONTENT + "mp4"));
// Set content length if available.
Long contentLength = objectStream.response().contentLength();
if (contentLength != null) {
headers.setContentLength(contentLength);
}
// Set disposition as inline to display content in the browser.
headers.setContentDispositionFormData("inline", keyName);
// Create a StreamingResponseBody to stream the content.
StreamingResponseBody responseBody = outputStream -> {
try (BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
byte[] buffer = new byte[1024 * 1024];
int bytesRead;
while ((bytesRead = objectStream.read(buffer)) != -1) {
bufferedOutputStream.write(buffer, 0, bytesRead);
}
bufferedOutputStream.flush();
} finally {
objectStream.close();
}
};
return new ResponseEntity<>(responseBody, headers, HttpStatus.OK);
} catch (Exception e) {
// Handle exceptions and return an appropriate ResponseEntity.
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
// Convert a LIST to XML data.
private Document toXml(List<Tags> itemList) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.newDocument();
// Start building the XML
Element root = doc.createElement( "Tags" );
doc.appendChild( root );
// Iterate through the list.
for (Tags myItem: itemList) {
Element item = doc.createElement( "Tag" );
root.appendChild( item );
// Set Name
Element id = doc.createElement( "Name" );
id.appendChild( doc.createTextNode(myItem.getName() ) );
item.appendChild( id );
// Set Description
Element name = doc.createElement( "Description" );
name.appendChild( doc.createTextNode(myItem.getDesc() ) );
item.appendChild( name );
}
return doc;
} catch(ParserConfigurationException e) {
e.printStackTrace();
}
return null;
}
private String convertToString(Document xml) {
try {
TransformerFactory transformerFactory = getSecureTransformerFactory();
transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Transformer transformer = transformerFactory.newTransformer();
StreamResult result = new StreamResult(new StringWriter());
DOMSource source = new DOMSource(xml);
transformer.transform(source, result);
return result.getWriter().toString();
} catch (TransformerException ex) {
ex.printStackTrace();
}
return null;
}
private static TransformerFactory getSecureTransformerFactory() {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
try {
transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
} catch (TransformerConfigurationException e) {
e.printStackTrace();
}
return transformerFactory;
}
}
At this point, you have created all of the Java files required for this example Spring Boot application. Now you create HTML files that are required for the application's view. Under the resource folder, create a templates folder, and then create the following HTML files:
Note: The CSS file for this application is located in this Github repository.
The index.html file is the application's home view. The following HTML represents the index.html file.
<!DOCTYPE HTML>
<html xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" href="../public/css/styles.css" th:href="@{/css/styles.css}" />
<link rel="icon" href="../public/img/favicon.ico" th:href="@{/img/favicon.ico}" />
<title>AWS Item Tracker</title>
</head>
<body>
<header th:replace="layout :: site-header"></header>
<div class="container">
<h2>Video Stream over HTTP App</h2>
<p>This sample application streams S3 video content over HTTP using the Amazon S3 Java V2 API.<p>
<ol>
<li>Upload MP4 videos to an Amazon S3 bucket by choosing the <i>Upload Video</i>.
<li>Fill in the form and then choose <i>Submit</i>.</li>
<li>The application stores the video in an Amazon S3 bucket.</li>
<li>You can view a video by choosing <i>Watch Video</i>.
<li>Next, choose the video to watch by click in the video title.</li>
</ol>
</div>
</div>
</body>
</html>
The following code represents the layout.html file that represents the application's menu.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="site-head">
<meta charset="UTF-8" />
<link rel="icon" href="../public/images/favicon.ico" th:href="@{/images/favicon.ico}" />
<script th:src="|https://code.jquery.com/jquery-1.12.4.min.js|"></script>
<meta th:include="this :: head" th:remove="tag"/>
</head>
<body>
<!-- th:hef calls a controller method - which returns the view -->
<header th:fragment="site-header">
<a href="#" style="color: white" th:href="@{/}">Home</a>
<a href="#" style="color: white" th:href="@{/upload}">Upload Videos</a>
<a href="#" style="color: white" th:href="@{/watch}">Watch Videos</a>
</header>
<h1>Welcome</h1>
</body>
</html>
The upload.html file is the application's view that lets users upload a MP4 file.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<script th:src="|https://code.jquery.com/jquery-1.12.4.min.js|"></script>
<link rel="stylesheet" href="../public/css/styles.css" th:href="@{/css/styles.css}" />
<link rel="icon" href="../public/images/favicon.ico" th:href="@{/images/favicon.ico}" />
<title>Spring Framework</title>
</head>
<body>
<header th:replace="layout :: site-header"></header>
<div class="container">
<h2>Video Stream over HTTP App</h2>
<p>Upload a MP4 video to an Amazon S3 bucket</p>
<form method="POST" onsubmit="myFunction()" action="/fileupload" enctype="multipart/form-data">
Video Description:<input type="text" name="description" required>
<input type="file" name="file" />
<input type="submit" value="Submit" />
</form>
</div>
</body>
</html>
The video.html file is the application's view that displays both the video menu and the video content.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<script th:src="|https://code.jquery.com/jquery-1.12.4.min.js|"></script>
<link rel="stylesheet" href="../public/css/styles.css" th:href="@{/css/styles.css}" />
<link rel="icon" href="../public/images/favicon.ico" th:href="@{/images/favicon.ico}" />
<title>Spring Framework</title>
<script>
$(function() {
getItems();
} );
// Gets the MP4 tags to set in the scroll list.
function getItems() {
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", loadTags, false);
xhr.open("GET", "../items", true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");//necessary
xhr.send();
}
function loadTags(event) {
var xml = event.target.responseText;
$(xml).find('Tag').each(function () {
var $field = $(this);
var name = $field.find('Name').text();
var description = $field.find('Description').text();
var vv = "Prime video"
// Append this data to the main list.
$('.list-group').append("<className='list-group-item list-group-item-action flex-column align-items-start'>");
$('.list-group').append("<h5 onMouseOver=\"this.style.cursor='pointer'\" onclick=\"addVideo('" +name+"')\" class='mb-1'>"+name+"</li>");
$('.list-group').append("<p class='mb-1'>"+description+"</p>");
$('.list-group').append("<small class='text-muted'>"+vv+"</small>");
$('.list-group').append("<br class='row'>");
});
}
function addVideo(myVid) {
var myVideo = document.getElementById("video1");
myVideo.src = "/"+myVid+"/stream";
}
</script>
</head>
<body>
<header th:replace="layout :: site-header"></header>
<div class="container">
<h3>Video Stream over HTTP App</h3>
<p>This example reads a MP4 video located in an Amazon S3 bucket and streams over HTTP</p>
<div class="row">
<div class="col">
<video id="video1" width="750" height="440" controls>
<source type="video/mp4">
Your browser does not support HTML video.
</video>
</div>
<div class="col">
<div class="list-group">
</div>
</div>
</div>
</div>
</body>
</html>
Package up the project into a .jar (JAR) file that you can deploy to Elastic Beanstalk by using the following Maven command.
mvn package
The JAR file is located in the target folder.
The POM file contains the spring-boot-maven-plugin that builds an executable JAR file that includes the dependencies. Without the dependencies, the application does not run on Elastic Beanstalk. For more information, see Spring Boot Maven Plugin.
Sign in to the AWS Management Console, and then open the Elastic Beanstalk console. An application is the top-level container in Elastic Beanstalk that contains one or more application environments (for example prod, qa, and dev, or prod-web, prod-worker, qa-web, qa-worker).
If this is your first time accessing this service, you will see a Welcome to AWS Elastic Beanstalk page. Otherwise, you’ll see the Elastic Beanstalk Dashboard, which lists all of your applications.
When you’re done, you will see the application state the Health is Ok .
Note: If you don't know how to set variables, see Environment properties and other software settings.
To access the application, open your browser and enter the URL for your application. You will see the Home page for your application.
Congratulations! You have created a Spring Boot application that uses Amazon Lex to create an interactive user experience. As stated at the beginning of this tutorial, be sure to terminate all of the resources you create while going through this tutorial to ensure that you’re not charged.
For more AWS multiservice examples, see usecases.