Skip Navigation
Show nav
Heroku Dev Center
  • Get Started
  • Documentation
  • Changelog
  • Search
  • Get Started
    • Node.js
    • Ruby on Rails
    • Ruby
    • Python
    • Java
    • PHP
    • Go
    • Scala
    • Clojure
  • Documentation
  • Changelog
  • More
    Additional Resources
    • Home
    • Elements
    • Products
    • Pricing
    • Careers
    • Help
    • Status
    • Events
    • Podcasts
    • Compliance Center
    Heroku Blog

    Heroku Blog

    Find out what's new with Heroku on our blog.

    Visit Blog
  • Log inorSign up
View categories

Categories

  • Heroku Architecture
    • Dynos (app containers)
    • Stacks (operating system images)
    • Networking & DNS
    • Platform Policies
    • Platform Principles
  • Command Line
  • Deployment
    • Deploying with Git
    • Deploying with Docker
    • Deployment Integrations
  • Continuous Delivery
    • Continuous Integration
  • Language Support
    • Node.js
    • Ruby
      • Working with Bundler
      • Rails Support
    • Python
      • Working with Django
      • Background Jobs in Python
    • Java
      • Working with Maven
      • Java Database Operations
      • Working with the Play Framework
      • Working with Spring Boot
      • Java Advanced Topics
    • PHP
    • Go
      • Go Dependency Management
    • Scala
    • Clojure
  • Databases & Data Management
    • Heroku Postgres
      • Postgres Basics
      • Postgres Performance
      • Postgres Data Transfer & Preservation
      • Postgres Availability
      • Postgres Special Topics
    • Heroku Redis
    • Apache Kafka on Heroku
    • Other Data Stores
  • Monitoring & Metrics
    • Logging
  • App Performance
  • Add-ons
    • All Add-ons
  • Collaboration
  • Security
    • App Security
    • Identities & Authentication
    • Compliance
  • Heroku Enterprise
    • Private Spaces
      • Infrastructure Networking
    • Enterprise Accounts
    • Enterprise Teams
    • Heroku Connect (Salesforce sync)
    • Single Sign-on (SSO)
  • Patterns & Best Practices
  • Extending Heroku
    • Platform API
    • App Webhooks
    • Heroku Labs
    • Building Add-ons
      • Add-on Development Tasks
      • Add-on APIs
      • Add-on Guidelines & Requirements
    • Building CLI Plugins
    • Developing Buildpacks
    • Dev Center
  • Accounts & Billing
  • Troubleshooting & Support
  • Language Support
  • Clojure
  • Using WebSockets on Heroku with Clojure and Immutant

Using WebSockets on Heroku with Clojure and Immutant

English — 日本語に切り替える

Last updated December 16, 2019

Table of Contents

  • Prerequisites
  • Create the WebSocket app
  • Functionality
  • Running the app locally
  • Deploying the app to Heroku

In this tutorial you’ll learn how to build a Clojure application with Immutant that uses a WebSocket. Then you’ll learn how to deploy that application to Heroku.

Sample code for the demo application is available on GitHub. Edits and enhancements are welcome. Just fork the repository, make your changes and send us a pull request.

Prerequisites

  • Java, Leiningen, and the Heroku CLI (as described in the Heroku CLI setup article)
  • A Heroku user account. Signup is free and instant.

Create the WebSocket app

Begin by generating the project scaffolding with Leiningen like so:

$ lein new demo
Generating a project called demo based on the 'default' template.
The default template is intended for library projects, not applications.
To see other templates (app, plugin, etc), try `lein help new`.

Then add the Immutant dependencies to the project.clj file, and prepare it for Heroku by adding these settings:

(defproject demo "0.1.0-SNAPSHOT"
  :dependencies [[org.clojure/clojure "1.6.0"]
                  [org.immutant/web "2.0.0-beta2"]
                  [compojure "1.1.8"]
                  [ring/ring-core "1.3.0"]
                  [environ "1.0.0"]]
  :main demo.core
  :uberjar-name "demo-standalone.jar"
  :profiles {:uberjar {:aot [demo.core]}}
  :min-lein-version "2.4.0")

Now you’re ready to add some behavior to the application.

Functionality

The sample application renders a simple web page that opens a WebSocket to the backend. The client sends a payload containing a string over the WebSocket. The server reverses the string and sends the result back to the client.

There are two important pieces to the interaction: the server-side callback functions and a JavaScript file that opens the WebSocket.

Server

In the project’s src/demo/core.clj file, add the following dependencies to the top of the file (replacing the existing namespace statement):

(ns demo.core
  (:require
    [immutant.web             :as web]
    [immutant.web.async       :as async]
    [immutant.web.middleware  :as web-middleware]
    [compojure.route          :as route]
    [environ.core             :refer (env)]
    [compojure.core           :refer (ANY GET defroutes)]
    [ring.util.response       :refer (response redirect content-type)])
  (:gen-class))

Below that, define the WebSocket callback functions:

(def websocket-callbacks
  "WebSocket callback functions"
  {:on-open   (fn [channel]
    (async/send! channel "Ready to reverse your messages!"))
  :on-close   (fn [channel {:keys [code reason]}]
    (println "close code:" code "reason:" reason))
  :on-message (fn [ch m]
    (async/send! ch (apply str (reverse m))))})

The websocket-callbacks function defines a map of three functions. These functions will be executed for opening a socket, closing a socket and receiving a message respectively.

Next, define the web routes for the application:

(defroutes routes
  (GET "/" {c :context} (redirect (str c "/index.html")))
  (route/resources "/"))

Finally, create a main function as the entry point to the application:

(defn -main [& {:as args}]
  (web/run
    (-> routes
      (web-middleware/wrap-session {:timeout 20})
      ;; wrap the handler with websocket support
      ;; websocket requests will go to the callbacks, ring requests to the handler
      (web-middleware/wrap-websocket websocket-callbacks))
      (merge {"host" (env :demo-web-host), "port" (env :demo-web-port)}
      args)))

The server side is ready to handle messages from the client.

Client

The client side will use a simple HTML page to control the demo. Create a resources/public/index.html file and put the following code in it:

<html>
<head>
  <meta charset="utf-8">
  <title>WebSocket Demo</title>
</head>
<body>
  <h1>WebSocket Demo</h1>
  <div>
    <input type="text" id="input" value="Enter text to reverse!" />
  </div>
  <div>
    <button type="button" id="open">Open</button>
    <button type="button" id="send">Send</button>
    <button type="button" id="close">Close</button>
  </div>
  <div id="messages"></div>
  <script src="js/app.js"></script>
</body>
</html>

This page has a text box, an open button, a send button, a close button and an area for some messages. But they don’t do anything yet. To add behavior to them, you’ll need some Javascript. Create a resources/public/js/app.js file, and put the following code in it:

window.onload = function() {
  var input = document.getElementById('input');
  var openBtn = document.getElementById('open');
  var sendBtn = document.getElementById('send');
  var closeBtn = document.getElementById('close');
  var messages = document.getElementById('messages');

  var socket;
};

This defines the window.onload function and a few variables that represent the textbox contents, the buttons, the message area, and the socket you’ll use to communicate.

Now add a function to the window.onload body (that is, a function inside of the function) to update the messages variable:

function output(style, text){
  messages.innerHTML += "<br/><span class='" + style + "'>" + text + "</span>";
}

This will be used as a callback when communicating with the socket. Below this function (but still inside of the window.onload function) add the follow code to define the behavior of the open button:

openBtn.onclick = function(e) {
  e.preventDefault();
  if (socket !== undefined) {
    output("error", "Already connected");
    return;
  }

  var uri = "ws://" + location.host + location.pathname;
  uri = uri.substring(0, uri.lastIndexOf('/'));
  socket = new WebSocket(uri);

  socket.onerror = function(error) {
    output("error", error);
  };

  socket.onopen = function(event) {
    output("opened", "Connected to " + event.currentTarget.url);
  };

  socket.onmessage = function(event) {
    var message = event.data;
    output("received", "<<< " + message);
  };

  socket.onclose = function(event) {
    output("closed", "Disconnected: " + event.code + " " + event.reason);
    socket = undefined;
  };
};

When the open button is clicked, this function will execute. It creates a new websocket, and then defines the callback functions for each of the socket operations. In all cases, it posts a message to the messages text area.

Next, add the following code (still inside of the window.onload function) to define the behavior of the send button:

sendBtn.onclick = function(e) {
  if (socket == undefined) {
    output("error", 'Not connected');
    return;
  }
  var text = document.getElementById("input").value;
  socket.send(text);
  output("sent", ">>> " + text);
};

In a similar fashion as opening, this function invokes the send method on the socket and invokes the output function with the value that was sent.

Finally, add the following code before the end of the window.onload function to define the behavior of the close button:

closeBtn.onclick = function(e) {
  if (socket == undefined) {
    output('error', 'Not connected');
    return;
  }
  socket.close(1000, "Close button clicked");
};

This will close the socket when the close button is clicked.

Now the application is ready to run.

Running the app locally

To run the app locally, you’ll first need to compile it by running this command:

$ lein uberjar

This will produce an executable JAR file that can be launched with this command:

$ java -jar target/demo-standalone.jar host 0.0.0.0 port 5000

Run the command shown above, and then open a browser to http://localhost:5000. You will see the WebSockets page with the open, send and close buttons. Enter some text and test it out. Now you’re ready to deploy to it to the cloud.

Deploying the app to Heroku

Create a Procfile in the project root with the following contents:

web: java $JVM_OPTS -jar target/demo-standalone.jar host 0.0.0.0 port $PORT

Heroku needs this file to know what command to run to launch your application. As you’ll notice, it’s very similar to the command you ran locally.

Now, add all of your code to a Git repository:

$ git init
$ git add .
$ git commit -m "first commit"

Create the Heroku app to which you will deploy:

$ heroku create

Then deploy your code:

$ git push heroku master
Counting objects: 20, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (13/13), done.
Writing objects: 100% (20/20), 7.03 KiB | 0 bytes/s, done.
Total 20 (delta 0), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Clojure (Leiningen 2) app detected
remote: -----> Installing OpenJDK 1.8...done
remote: -----> Installing Leiningen
remote:        Downloading: leiningen-2.5.0-standalone.jar
remote:        Writing: lein script
remote: -----> Building with Leiningen
remote:        Running: lein uberjar
remote:        Retrieving org/clojure/clojure/1.6.0/clojure-1.6.0.pom from central
...
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote:
remote: -----> Compressing... done, 58.2MB
remote: -----> Launching... done, v3
remote:        https://still-hamlet-4310.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/immutant-feature-demo.git
 * [new branch]      master -> master

Congratulations! Your web app should now be up and running on Heroku. Visit the application to see it in action:

$ heroku open

Be aware that the heroku open command will open an HTTPS URL. If you are using Firefox, you will get a security error in your browser when you attempt to open the Websocket. You must either change the URL to an HTTP URL or use another browser.

For more information on using WebSockets with Immutant, see the Immutant API documentation. But be aware that not all Immutant features can be used on Heroku. Any feature that requires cluster, such as singleton services and session replication, will not behave correctly. Instead, it is recommend that you use other solutions such as the Heroku Scheduler add-on and the Memcached add-on for caching .

Keep reading

  • Clojure

Feedback

Log in to submit feedback.

Information & Support

  • Getting Started
  • Documentation
  • Changelog
  • Compliance Center
  • Training & Education
  • Blog
  • Podcasts
  • Support Channels
  • Status

Language Reference

  • Node.js
  • Ruby
  • Java
  • PHP
  • Python
  • Go
  • Scala
  • Clojure

Other Resources

  • Careers
  • Elements
  • Products
  • Pricing

Subscribe to our monthly newsletter

Your email address:

  • RSS
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku Blog
    • Heroku News Blog
    • Heroku Engineering Blog
  • Heroku Podcasts
  • Twitter
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku
    • Heroku Status
  • Facebook
  • Instagram
  • Github
  • LinkedIn
  • YouTube
Heroku is acompany

 © Salesforce.com

  • heroku.com
  • Terms of Service
  • Privacy
  • Cookies