Sunday, May 11, 2014

how to build your web application on spring boot and deploy it on heroku

Why would you need a tutorial for building your web application on spring boot and deploying it on heroku?
That's because our web application will serve JSP files and as Spring Boot's JSP support is limited we will have to use war files for deployment and it can get a bit tricky to deploy it on heroku. Furthermore, as the web application we will deploy is super concise you can use it on your own project by just editing it.

Our controller is quite basic. It will take the name of the user from url and print it on the next page. So calling "/hello/sezin" will print on "hello sezin" on the page.


package main.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author sezin karli (skarligmail.com)
 * @since 3/19/14 3:42 PM
 *        User: Sezin Karli
 */
@Controller
@RequestMapping(value = "/hello")
public class HelloController {

    @RequestMapping(value = "/{user}")
    public String handleOne(@PathVariable String user, ModelMap modelMap){
        String helloToken = "Hello " + user;
        modelMap.put("token", helloToken);
        return "welcome-page";
    }
}

Our main Spring class is below. PORT attribute is taken from environment variables if it can't be found 8080 is used. This part is a requirement for Heroku.

package main;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @author sezin karli (skarligmail.com)
 * @since 3/19/14 9:26 AM
 *        User: Sezin Karli
 */
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class ToyProjectLauncher {

    public static void main(String[] args) throws Exception {
        String webPort = System.getenv("PORT");
        if (webPort == null || webPort.isEmpty()) {
            webPort = "8080";
        }
        System.setProperty("server.port", webPort);

        SpringApplication.run(ToyProjectLauncher.class, args);
    }
}

Our application properties. We need to show a folder for jsp files and if we don't want to print ".jsp" suffix for every return value in the controller we will also need the suffix parameter below.

server.port: ${port:8080}
spring.view.prefix: /WEB-INF/jsp/
spring.view.suffix: .jsp
Our jsp is as follows. We just print the token we get from the controller.
<%@ page language="java" contentType="text/html; charset=US-ASCII"
         pageEncoding="US-ASCII"%>



    
    Hello Page
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    






If we analyze the pom below you can see that we have a spring boot parent which's needed to inherit defaults from spring boot. Then we add starter-web dependency because it will add web dependency template. Jasper and jstl dependencies are needed for JSPs to work. We would need spring-boot-maven-plugin if we want a fat jar which makes it easier to deploy.

    4.0.0

    nr.co.caught
    boot-toy-project
    1.0-SNAPSHOT
    war


    
    
        org.springframework.boot
        spring-boot-starter-parent
        1.0.0.BUILD-SNAPSHOT
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
        
            org.apache.tomcat.embed
            tomcat-embed-jasper
        
        
            javax.servlet
            jstl
        

    

    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

    
        
            spring-snapshots
            Spring Snapshots
            http://repo.spring.io/snapshot
            
                true
            
        
    

    
        
            spring-snapshots
            http://repo.spring.io/snapshot
        
    

You can check GitHub if you need further details with the project. If everyting's fine you should be able to build it with "mvn package".
You must see your war file under target folder. Why a war file? Because Spring Boot supports JSPs only if the project package is war.
 Lets run our code by going under "target" and doing "java -jar boot-toy-project-1.0-SNAPSHOT.war". You should see your hello page if you type "localhost/hello/sezin".

Now we will continue by deploying it on heroku. I assume you already have a heroku user and heroku toolbelt already installed.

Open a command prompt and login to heroku by typing "heroku login". Type your heroku email and password. It says "could not find an existing public key. We try to generate one but get a "could not generate key: 'ssh-keygen' is not recognized" error (under windows 7).


There seems to be a problem.
Open a git bash and lets create an ssh key with 'ssh-keygen -t rsa -C "yourEmail"' command.

Lets login again and here we get "authentication successful".



After that we will try to push and deploy our Spring Boot Project.
Create a file named "Procfile" with the information below under our project folder

 web: java $JAVA_OPTS -jar target/boot-toy-project-1.0-SNAPSHOT.war

There's another way to deploy war files to heroku (heroku deploy:war), but I didn't manage to run my application with it so I'm teaching you what worked for me. :)

Now, create a project with "heroku create" command. We will get "Git remote heroku added".


Open git bash and enter "git push heroku master" under your project directory. Now we will push our code into heroku git and heroku will deploy it to our server.



The last line will show you on which address your site is available. Mine was "http://pure-shelf-8719.herokuapp.com/". I add few strings to test my hello page and go to "http://pure-shelf-8719.herokuapp.com/hello/world". You can see the awesome result below.