deployment

deployment

Learn how to build, package and deploy applications.

intro

Jooby applications are self-contained and they don’t run inside a container or Servlet environment.

Application resources (a.k.a conf files) are packaged within your app. Not only that, the server you pick is also packaged within your application.

Jooby lack of the concept of server/servlet container it just a raw/plain/normal Java application that can be launched via:

java -jar myapp.jar

Where myapp.jar is a fat jar. A fat jar is a jar with all the app dependencies, conf files and web server… all bundle as a single jar.

environments

The fat jar is nice when we need to deploy our application into one single environment, but what if we need to deploy to stage? or prod?

The way we deploy to a different environment is by creating a conf file (.conf or logback.xml) and adding the environment as suffix.

If you need to deploy to stage and prod then you probably need:

# dev environment
conf/application.conf
conf/logback.xml

conf/application.stage.conf
conf/logback.stage.xml

conf/application.prod.conf
conf/logback.prod.xml

The file jar contains everything your dev, stage and prod conf files.

Jooby is built around the environment concept, which means a Jooby app always known in which environment it runs.

The application.env property controls the environment, by default this property is set to dev (the unique and well known environment).

Suppose you need to deploy your awesome app into prod and you need to set/update a few properties. The application.conf file looks like:

...
aws.accessKey = AKIAIOSFODNN7EXAMPLE
aws.secretKey =  wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
...

For prod you have a new key pair… then all you have to do is to create a new file application.prod.conf and just add those new keys:

aws.accessKey = AKIAIOSFODNN7PROD
aws.secretKey =  wJalrXUtnFEMI/K7MDENG/bPxRfiCYPROD

TIP: You don’t have to add all the properties, just those that changes between environments.

run your app

In order to start your application in one specific environment, you need to set the application.env argument:

java myapp.jar application.env=prod

or using a shortcut, like:

java myapp.jar env=prod

or just:

java myapp.jar prod

Your application will run now in prod mode. It will find your application.conf and overrides any property defined there with those from application.prod.conf

It works for logback.xml too, if logback.[env].xml is present, then Jooby will use it, otherwise it fallbacks to logback.xml.

fat jar

This is the default deployment option and you (usually) don’t need to do anything if you created your application via maven archetype.

In order to create a fat jar, go to your project home, open a terminal and run:

mvn clean package

The jar will be available inside the target directory.

configuration

We use the maven-shade-plugin for creating the fat jar:

...
<build>
  <plugins>
    ...
    <!-- Build fat jar -->
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-shade-plugin</artifactId>
    </plugin>
    ...
  </plugins>
</build>

Or the gradle-shadow-plugin:

buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "com.github.jengelman.gradle.plugins:shadow:1.2.4"
  }
}

apply plugin: "com.github.johnrengelman.shadow"

If you created your application via maven archetype this setup is already present in your application.

run / start

Since everything was bundled into a single jar, all you have to do is:

java -jar myapp.jar [env]

Easy huh? No complex deployment, no heavy-weight servers, no classpath hell, nothing!

Your application is up and running!

stork

Build, package and distribute your application using Stork.

Stork is a collection of utilities for optimizing your after-build workflow by filling in the gap between your Java build system and eventual end-user app execution.

usage

Stork integration is provided via Maven Profiles.

  • Write your stork.yml launcher and save it in the src/etc directory

  • Open a console and type: mvn clean package

  • It builds a [app-name].zip file inside the target directory

profile activation

The stork Maven profile is activated by the presence of the src/etc/stork.yml file.

launcher configuration

You must name your launcher as stork.yml and save it inside the src/etc directory.

Maven properties defined here will be resolved and merged into the final output. Examples of these properties are: ${application.class}, ${project.groupId}, etc…

example

Here’s a simple example of the stork.yml launcher:

# Name of application (make sure it has no spaces)
name: "${project.artifactId}"

# Display name of application (can have spaces)
display_name: "${project.name}"

# Type of launcher (CONSOLE or DAEMON)
type: DAEMON

# Java class to run
main_class: "${application.class}"

domain: "${project.groupId}"

short_description: "${project.artifactId}"

# Platform launchers to generate (WINDOWS, LINUX, MAC_OSX)
# Linux launcher is suitable for Bourne shells (e.g. Linux/BSD)
platforms: [ LINUX ]

# Working directory for app
# RETAIN will not change the working directory
# APP_HOME will change the working directory to the home of the app
# (where it was intalled) before running the main class
working_dir_mode: RETAIN

# Minimum version of java required (system will be searched for acceptable jvm)
min_java_version: "1.8"

# Min/max fixed memory (measured in MB)
min_java_memory: 512
max_java_memory: 512

# Min/max memory by percentage of system
#min_java_memory_pct: 10
#max_java_memory_pct: 20

# Try to create a symbolic link to java executable in <app_home>/run with
# the name of "<app_name>-java" so that commands like "ps" will make it
# easier to find your app
symlink_java: true

capsule

Package and Deploy JVM Applications with Capsule.

A capsule is a single executable JAR that contains everything your application needs to run either in the form of embedded files or as declarative metadata. More at capsule.io

usage

Capsule integration is provided via Maven Profiles using the capsule-maven-plugin.

  • Write a capsule.activator file inside the src/etc directory

  • Open a console and type: mvn clean package

  • It builds a [app-name]-capsule-fat.jar file inside the target directory

It generates a fat capsule by default inside the target directory.

If you are a Gradle user, checkout the capsule gradle example.

profile activation

The capsule Maven profile is activated by the presence of the src/etc/capsule.activator file (file content doesn’t matter, just the file presence).

options

The integration provides the following defaults:

<properties>
  <capsule.resolve>false</capsule.resolve>
  <capsule.chmod>true</capsule.chmod>
  <capsule.trampoline>false</capsule.trampoline>
  <capsule.JVM-Args>-Xms512m -Xmx512m</capsule.JVM-Args>
  <capsule.types>fat</capsule.types>
  <capsule.caplets>Capsule</capsule.caplets>
</properties>

For example, if you need or prefer a thin capsule, follow these steps:

  • Open your pom.xml
  • Go to the <properties> section and add/set:
<properties>
  <capsule.resolve>true</capsule.resolve>
  <capsule.types>thin</capsule.types>
</properties>
  • Open a console and type: mvn clean package

You’ll find your thin capsule in the target directory.

docker

Docker is the world’s leading software containerization platform. You can easily run you Jooby app as a docker container. You need to have the docker engine installed.

usage

You need docker installed on the building machine.

Maven users have two options: one for a building a fat jar and one for building a stork distribution.

Gradle users might want to choose one of the available plugins.

fat jar

  • Write a src/etc/docker.activator file. File contents doesn’t matter, the file presence activates a Maven profile.

  • Open a terminal and run:

mvn clean package docker:build
  • Once finished, the docker image has been built and tagged as ${project.artifactId}.

  • You can now run the image with:

docker run -p 80:8080 ${project.artifactId}

The Maven profile triggers the spotify/docker-maven-plugin which generates a docker file. Please see the doc for more details.

stork

  • Write a src/etc/docker.stork.activator file. File contents doesn’t matter, the file presence activates a Maven profile.

  • Open a terminal and run:

mvn clean package docker:build
  • Once finished, the docker image has been built and tagged as ${project.artifactId}.

  • You can now run the image with:

docker run -it -p 80:8080 ${project.artifactId}

The Maven profile triggers the spotify/docker-maven-plugin which generates a docker file. Please see the doc for more details.

static resources

This isn’t a deployment option, just a way to collect static resources and deploy them somewhere else.

The static assembly let you collect and bundle all your static resources into a zip file.

This is nice if you want or prefer to serve static resources via nginx or apache.

usage

  • Write a assets.activator file inside the src/etc directory

  • Open a console and type: mvn clean package

  • Look for [app-name].static.zip inside the target directory

war?

This module exists for strict environments where the ONLY option is to deploy into a Servlet Container. It’s strongly recommended to avoid this and rather opt for using one of Netty, Jetty or Undertow.

usage

In order to deploy into a Servlet Container, you need to generate a *.war file. Here’s how:

  • Create a war.activator file in the src/etc directory.

  • Open a console and type: mvn clean package

  • Find the *.war file in the target directory.

limitations

  • web-sockets are not supported
  • some properties has no effect when deploying into a Servlet Container:
    • application.path / contextPath
    • appplication.port
    • max upload file sizes
    • any other server specific property: server., jetty., netty., undertow.

special note on contextPath

To avoid potential headaches, make sure to use the contextPath variable while loading static/dynamic resources (.css, .js, etc..).

For example:

<html>
<head>
  <link rel="stylesheet" text="text/css" href="{{contextPath}}/css/styles.css">
  <script src="{{contextPath}}/js/app.js"></script>
</head>
</html>

The expression: {{contextPath}} corresponds to the template engine (handlebars in that case) or ${contextPath} for Freemarker.

how does it work?

The presence of the src/etc/war.activator file triggers a Maven profile. The contents of the file doesn’t matter, it just needs to be present.

The Maven profile builds the *.war file using the maven-assembly-plugin. The assembly descriptor can be found here

web.xml

A default web.xml file is generated by the assembly plugin. This file looks like:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1">
  <context-param>
    <param-name>application.class</param-name>
    <param-value>${application.class}</param-value>
  </context-param>

  <listener>
    <listener-class>org.jooby.servlet.ServerInitializer</listener-class>
  </listener>

  <servlet>
    <servlet-name>jooby</servlet-name>
    <servlet-class>org.jooby.servlet.ServletHandler</servlet-class>
    <load-on-startup>0</load-on-startup>
    <!-- MultiPart setup -->
    <multipart-config>
      <file-size-threshold>0</file-size-threshold>
      <!-- Default 200k -->
      <max-request-size>${war.maxRequestSize}</max-request-size>
    </multipart-config>
  </servlet>

  <servlet-mapping>
    <servlet-name>jooby</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>
max upload size

The default max upload size is set to 204800b (200kb). If you need to increase this, add the war.maxRequestSize property to pom.xml:

<properties>
  <war.maxRequestSize>1048576</war.maxRequestSize> <!-- 1mb -->
</properties>
custom web.xml

When the generated file isn’t enough, follow these steps:

  1. create a dir: src/etc/war/WEB-INF
  2. save a web.xml file inside that dir
  3. run: mvn clean package

jooby.conf

################################################################################################### 
#! application 
################################################################################################### 
application {

  # environment default is: dev 
  env = dev

  # contains the simple name of package of your application bootstrap class. 
  # For example: com.foo.App -> foo 
  # name = App.class.getPackage().getName().lastSegment() 
  # application namespace, default to app package. set it at runtime 
  # ns = App.class.getPackage().getName() 
  # class = App.class.getName() 
  # tmpdir 
  tmpdir = ${java.io.tmpdir}/${application.name}

  # path (a.k.a. as contextPath) 
  path = /

  # localhost 
  host = localhost

  # HTTP ports 
  port = 8080

  # uncomment to enabled HTTPS 
  # securePort = 8443 
  # we do UTF-8 
  charset = UTF-8

  # date format 
  dateFormat = dd-MMM-yyyy

  # number format, system default. set it at runtime 
  # numberFormat = DecimalFormat.getInstance(${application.lang})).toPattern() 
  # comma separated list of locale using the language tag format. Default to: Locale.getDefault() 
  # lang = Locale.getDefault() 
  # timezone, system default. set it at runtime 
  # tz = ZoneId.systemDefault().getId() 
  # redirect to/force https 
  # example: https://my.domain.com/{0} 
  redirect_https = ""

}

ssl {

  # An X.509 certificate chain file in PEM format, provided certificate should NOT be used in prod. 
  keystore.cert = org/jooby/unsecure.crt 

  # A PKCS#8 private key file in PEM format, provided key should NOT be used in prod. 
  keystore.key = org/jooby/unsecure.key

  # password of the keystore.key (if any) 
  # keystore.password = 
  # Trusted certificates for verifying the remote endpoint's certificate. The file should 
  # contain an X.509 certificate chain in PEM format. Default uses the system default. 
  # trust.cert = 
  # Set the size of the cache used for storing SSL session objects. 0 to use the 
  # default value. 
  session.cacheSize = 0

  # Timeout for the cached SSL session objects, in seconds. 0 to use the default value. 
  session.timeout = 0

}

################################################################################################### 
#! session defaults 
################################################################################################### 
session {

  # we suggest a timeout, but usage and an implementation is specific to a Session.Store implementation 
  timeout = 30m

  # save interval, how frequently we must save a none-dirty session (in millis). 
  saveInterval = 60s

  cookie {

    # name of the cookie 
    name = jooby.sid

    # cookie path 
    path = /

    # expires when the user closes the web browser 
    maxAge = -1

    httpOnly = true

    secure = false

  }

}

################################################################################################### 
#! flash scope defaults 
################################################################################################### 
flash {

  cookie {

    name = jooby.flash

    path = ${application.path}

    httpOnly = true

    secure = false

  }

}

################################################################################################### 
#! server defaults 
################################################################################################### 
server {

  http {

    HeaderSize = 8k

    # Max response buffer size 
    ResponseBufferSize = 16k

    # Max request body size to keep in memory 
    RequestBufferSize = 1m

    # Max request size total (body + header) 
    MaxRequestSize = 200k

    IdleTimeout = 0

    Method = ""

  }

  threads {

    Min = ${runtime.processors}

    Max = ${runtime.processors-x8}

    IdleTimeout = 60s

  }

  routes {

    # Guava Cache Spec 
    Cache = "concurrencyLevel="${runtime.concurrencyLevel}",maximumSize="${server.threads.Max}

  }

  ws {

    # The maximum size of a text message. 
    MaxTextMessageSize = 16k

    # The maximum size of a binary message. 
    MaxBinaryMessageSize = 16k

    # The time in ms (milliseconds) that a websocket may be idle before closing. 
    IdleTimeout = 5minutes

  }

  http2 {

    cleartext = true

    enabled = false

  }

}

################################################################################################### 
#! assets 
################################################################################################### 
assets {

  #! If asset CDN is present, the asset router will do a redirect to CDN and wont serve the file locally 
  #! /assets/js/index.js -> redirectTo(cdn + assets/js/index.js) 
  cdn =  ""

  etag = true

  lastModified = true

  env = ${application.env}

  charset = ${application.charset}

  # -1 to disable or HOCON duration value 
  cache.maxAge = -1

}

################################################################################################### 
#! Cross origin resource sharing 
################################################################################################### 
cors {

  # Configures the Access-Control-Allow-Origin CORS header. Possibly values: *, domain, regex or a list of previous values. 
  # Example: 
  # "*" 
  # ["http://foo.com"] 
  # ["http://*.com"] 
  # ["http://foo.com", "http://bar.com"] 
  origin: "*"

  # If true, set the Access-Control-Allow-Credentials header 
  credentials: true

  # Allowed methods: Set the Access-Control-Allow-Methods header 
  allowedMethods: [GET, POST]

  # Allowed headers: set the Access-Control-Allow-Headers header. Possibly values: *, header name or a list of previous values. 
  # Examples 
  # "*" 
  # Custom-Header 
  # [Header-1, Header-2] 
  allowedHeaders: [X-Requested-With, Content-Type, Accept, Origin]

  # Preflight max age: number of seconds that preflight requests can be cached by the client 
  maxAge: 30m

  # Set the Access-Control-Expose-Headers header 
  # exposedHeaders: [] 
}

################################################################################################### 
#! runtime 
################################################################################################### 
#! number of available processors, set it at runtime 
#! runtime.processors = Runtime.getRuntime().availableProcessors() 
#! runtime.processors-plus1 = ${runtime.processors} + 1 
#! runtime.processors-plus2 = ${runtime.processors} + 2 
#! runtime.processors-x2 = ${runtime.processors} * 2 
################################################################################################### 
#! status codes 
################################################################################################### 
err.java.lang.IllegalArgumentException = 400

err.java.util.NoSuchElementException = 400

err.java.io.FileNotFoundException = 404

################################################################################################### 
#! alias 
################################################################################################### 
contextPath = ${application.path}

mime.properties

mime.ai=application/postscript

mime.aif=audio/x-aiff

mime.aifc=audio/x-aiff

mime.aiff=audio/x-aiff

mime.apk=application/vnd.android.package-archive

mime.asc=text/plain

mime.asf=video/x.ms.asf

mime.asx=video/x.ms.asx

mime.au=audio/basic

mime.avi=video/x-msvideo

mime.bcpio=application/x-bcpio

mime.bin=application/octet-stream

mime.bmp=image/bmp

mime.cab=application/x-cabinet

mime.cdf=application/x-netcdf

mime.class=application/java-vm

mime.cpio=application/x-cpio

mime.cpt=application/mac-compactpro

mime.crt=application/x-x509-ca-cert

mime.csh=application/x-csh

mime.css=text/css

mime.scss=text/css

mime.less=text/css

mime.csv=text/comma-separated-values

mime.dcr=application/x-director

mime.dir=application/x-director

mime.dll=application/x-msdownload

mime.dms=application/octet-stream

mime.doc=application/msword

mime.dtd=application/xml-dtd

mime.dvi=application/x-dvi

mime.dxr=application/x-director

mime.eps=application/postscript

mime.etx=text/x-setext

mime.exe=application/octet-stream

mime.ez=application/andrew-inset

mime.gif=image/gif

mime.gtar=application/x-gtar

mime.gz=application/gzip

mime.gzip=application/gzip

mime.hdf=application/x-hdf

mime.hqx=application/mac-binhex40

mime.htc=text/x-component

mime.htm=text/html

mime.html=text/html

mime.ice=x-conference/x-cooltalk

mime.ico=image/x-icon

mime.ief=image/ief

mime.iges=model/iges

mime.igs=model/iges

mime.jad=text/vnd.sun.j2me.app-descriptor

mime.jar=application/java-archive

mime.java=text/plain

mime.jnlp=application/x-java-jnlp-file

mime.jpe=image/jpeg

mime.jpeg=image/jpeg

mime.jpg=image/jpeg

mime.js=application/javascript

mime.ts=application/javascript

mime.coffee=application/javascript

mime.json=application/json

mime.yaml=application/yaml

mime.jsp=text/html

mime.kar=audio/midi

mime.latex=application/x-latex

mime.lha=application/octet-stream

mime.lzh=application/octet-stream

mime.man=application/x-troff-man

mime.mathml=application/mathml+xml

mime.me=application/x-troff-me

mime.mesh=model/mesh

mime.mid=audio/midi

mime.midi=audio/midi

mime.mif=application/vnd.mif

mime.mol=chemical/x-mdl-molfile

mime.mov=video/quicktime

mime.movie=video/x-sgi-movie

mime.mp2=audio/mpeg

mime.mp3=audio/mpeg

mime.mpe=video/mpeg

mime.mpeg=video/mpeg

mime.mpg=video/mpeg

mime.mpga=audio/mpeg

mime.ms=application/x-troff-ms

mime.msh=model/mesh

mime.msi=application/octet-stream

mime.nc=application/x-netcdf

mime.oda=application/oda

mime.odb=application/vnd.oasis.opendocument.database

mime.odc=application/vnd.oasis.opendocument.chart

mime.odf=application/vnd.oasis.opendocument.formula

mime.odg=application/vnd.oasis.opendocument.graphics

mime.odi=application/vnd.oasis.opendocument.image

mime.odm=application/vnd.oasis.opendocument.text-master

mime.odp=application/vnd.oasis.opendocument.presentation

mime.ods=application/vnd.oasis.opendocument.spreadsheet

mime.odt=application/vnd.oasis.opendocument.text

mime.ogg=application/ogg

mime.otc=application/vnd.oasis.opendocument.chart-template

mime.otf=application/vnd.oasis.opendocument.formula-template

mime.otg=application/vnd.oasis.opendocument.graphics-template

mime.oth=application/vnd.oasis.opendocument.text-web

mime.oti=application/vnd.oasis.opendocument.image-template

mime.otp=application/vnd.oasis.opendocument.presentation-template

mime.ots=application/vnd.oasis.opendocument.spreadsheet-template

mime.ott=application/vnd.oasis.opendocument.text-template

mime.pbm=image/x-portable-bitmap

mime.pdb=chemical/x-pdb

mime.pdf=application/pdf

mime.pgm=image/x-portable-graymap

mime.pgn=application/x-chess-pgn

mime.png=image/png

mime.pnm=image/x-portable-anymap

mime.ppm=image/x-portable-pixmap

mime.pps=application/vnd.ms-powerpoint

mime.ppt=application/vnd.ms-powerpoint

mime.ps=application/postscript

mime.qml=text/x-qml

mime.qt=video/quicktime

mime.ra=audio/x-pn-realaudio

mime.ram=audio/x-pn-realaudio

mime.ras=image/x-cmu-raster

mime.rdf=application/rdf+xml

mime.rgb=image/x-rgb

mime.rm=audio/x-pn-realaudio

mime.roff=application/x-troff

mime.rpm=application/x-rpm

mime.rtf=application/rtf

mime.rtx=text/richtext

mime.rv=video/vnd.rn-realvideo

mime.ser=application/java-serialized-object

mime.sgm=text/sgml

mime.sgml=text/sgml

mime.sh=application/x-sh

mime.shar=application/x-shar

mime.silo=model/mesh

mime.sit=application/x-stuffit

mime.skd=application/x-koan

mime.skm=application/x-koan

mime.skp=application/x-koan

mime.skt=application/x-koan

mime.smi=application/smil

mime.smil=application/smil

mime.snd=audio/basic

mime.spl=application/x-futuresplash

mime.src=application/x-wais-source

mime.sv4cpio=application/x-sv4cpio

mime.sv4crc=application/x-sv4crc

mime.svg=image/svg+xml

mime.swf=application/x-shockwave-flash

mime.t=application/x-troff

mime.tar=application/x-tar

mime.tar.gz=application/x-gtar

mime.tcl=application/x-tcl

mime.tex=application/x-tex

mime.texi=application/x-texinfo

mime.texinfo=application/x-texinfo

mime.tgz=application/x-gtar

mime.tif=image/tiff

mime.tiff=image/tiff

mime.tr=application/x-troff

mime.tsv=text/tab-separated-values

mime.txt=text/plain

mime.ustar=application/x-ustar

mime.vcd=application/x-cdlink

mime.vrml=model/vrml

mime.vxml=application/voicexml+xml

mime.wav=audio/x-wav

mime.wbmp=image/vnd.wap.wbmp

mime.wml=text/vnd.wap.wml

mime.wmlc=application/vnd.wap.wmlc

mime.wmls=text/vnd.wap.wmlscript

mime.wmlsc=application/vnd.wap.wmlscriptc

mime.wrl=model/vrml

mime.wtls-ca-certificate=application/vnd.wap.wtls-ca-certificate

mime.xbm=image/x-xbitmap

mime.xht=application/xhtml+xml

mime.xhtml=application/xhtml+xml

mime.xls=application/vnd.ms-excel

mime.xml=application/xml

mime.xpm=image/x-xpixmap

mime.xsd=application/xml

mime.xsl=application/xml

mime.xslt=application/xslt+xml

mime.xul=application/vnd.mozilla.xul+xml

mime.xwd=image/x-xwindowdump

mime.xyz=chemical/x-xyz

mime.z=application/compress

mime.zip=application/zip

mime.conf=application/hocon

#! fonts 
mime.ttf=font/truetype

mime.otf=font/opentype

mime.eot=application/vnd.ms-fontobject

mime.woff=application/x-font-woff

mime.woff2=application/font-woff2

#! source map 
mime.map=text/plain

mime.mp4=video/mp4

crash

CRaSH remote shell: connect and monitor JVM resources via HTTP, SSH or telnet.</a>

dependency

<dependency>
 <groupId>org.jooby</groupId>
 <artifactId>jooby-crash</artifactId>
 <version>1.1.3</version>
</dependency>

usage


import org.jooby.crash;

{
  use(new Crash());
}

Just drop your commands in the cmd folder and CRaSH will pick up all them.

Now let’s see how to connect and interact with the CRaSH shell.

connectors

HTTP connector

The HTTP connector is a simple yet powerful collection of HTTP endpoints where you can run a CRaSH command:

{
  use(new Crash()
     .plugin(HttpShellPlugin.class)
  );
}

Try it:

GET /api/shell/thread/ls

OR:

GET /api/shell/thread ls

The connector is listening at /api/shell. If you want to mount the connector some where else just set the property: crash.httpshell.path.

SSH connector

Just add the crash.connectors.ssh dependency to your project.

Try it:

ssh -p 2000 admin@localhost

Default user and password is: admin. See how to provide a custom authentication plugin.

telnet connector

Just add the crash.connectors.telnet dependency to your project.

Try it:

telnet localhost 5000

Checkout complete telnet connector configuration.

web connector

Just add the crash.connectors.web dependency to your project.

Try it:

GET /shell

A web shell console will be ready to go at /shell. If you want to mount the connector some where else just set the property: crash.webshell.path.

commands

You can write additional shell commands using Groovy or Java, see the CRaSH documentation for details. CRaSH search for commands in the cmd folder.

Here is a simple ‘hello’ command that could be loaded from cmd/hello.groovy folder:

package commands

import org.crsh.cli.Command
import org.crsh.cli.Usage
import org.crsh.command.InvocationContext

class hello {

  @Usage("Say Hello")
  @Command
  def main(InvocationContext context) {
    return "Hello"
  }
}

Jooby adds some additional attributes and commands to InvocationContext that you can access from your command:

  • registry: Access to Registry.
  • conf: Access to Config.

Example:

package commands

import org.crsh.cli.Command
import org.crsh.cli.Usage
import org.crsh.command.InvocationContext

class HelloMyService {

  @Usage("MySerivce.doSomething")
  @Command
  def execute(InvocationContext context) {
    def registry = context.registry

    return registry.require(MySerivce.class).doSomething()
  }
}

routes command

The routes print all the application routes.

dev> routes
order method pattern             consumes produces name       source
-------------------------------------------------------------------------------------------------
0     GET    /shell/css/**       [*/*]    [*/*]    /anonymous org.jooby.crash.WebShellPlugin:41
0     GET    /shell/js/**        [*/*]    [*/*]    /anonymous org.jooby.crash.WebShellPlugin:42
0     GET    /shell              [*/*]    [*/*]    /anonymous org.jooby.crash.WebShellPlugin:45
0     GET    /api/shell/{cmd:.*} [*/*]    [*/*]    /anonymous org.jooby.crash.HttpShellPlugin:43
0     *      {before}/path       [*/*]    [*/*]    /anonymous app.CrashApp:21
0     *      {after}/path        [*/*]    [*/*]    /anonymous app.CrashApp:24
0     *      {complete}/path     [*/*]    [*/*]    /anonymous app.CrashApp:28
0     GET    /                   [*/*]    [*/*]    /anonymous app.CrashApp:31


method pattern consumes         produces
-------------------------------------------------
WS     /shell  application/json application/json

conf command

The conf tree print the application configuration tree (configuration precedence):

dev> conf tree
 merge of system properties
  org/jooby/crash/crash.conf @ file:/jooby-crash/target/classes/org/jooby/crash/crash.conf
   org/jooby/spi/server.conf @ file:/jooby-netty/target/classes/org/jooby/spi/server.conf
    org/jooby/mime.properties @ file:/jooby/target/classes/org/jooby/mime.properties
     org/jooby/jooby.conf @ file:/jooby/target/classes/org/jooby/jooby.conf: 1

The conf props [path] print all the application properties, sub-tree or a single property if path argument is present.

dev> conf props
name                                   value
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
mime.pgn                               application/x-chess-pgn
os.version                             10.11.6
mime.ttf                               font/truetype
err.java.io.FileNotFoundException      404
sun.cpu.isalist
mime.wtls-ca-certificate               application/vnd.wap.wtls-ca-certificate
mime.texinfo                           application/x-texinfo
runtime.concurrencyLevel               8
.....
dev> conf props application
name                       value
--------------------------------------------------------------------------------
application.tmpdir         /var/folders/9l/fyb_j4l553z6ql4443yttbj40000gn/T/app
application.version        0.0.0
application.ns             app
application.port           8080
application.charset        UTF-8
application.redirect_https
application.class          app.CrashApp
application.tz             America/Argentina/Buenos_Aires
application.numberFormat   #,##0.###
application.dateFormat     dd-MMM-yyyy
application.env            dev
application.host           localhost
application.name           app
application.path           /
application.lang           en-US
dev> conf props application.port
name             value
-----------------------
application.port 8080

fancy banner

Just add the jooby-banner to your project and all the CRaSH shell will use it.

{
  use(new Banner("crash me!"));

  use(new Crash());
}
telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
_____                        _____
__________________ __________  /_       ______ ___ ____ __  /
  ___/_  ___/  __ `/_  ___/_  __ \      _  __ `__ \  _ \_  / 
 /__    /     /_/ / (__  )   / / /        / / / / /  __//_/  
___/  _/     __,_/  ____/  _/ /_/       _/ /_/ /_/ ___/ _) v0.0.0

dev>

Simple and easy!!

conclusion

  • jar/capsule deployment makes perfect sense for PaaS like Heroku, AppEngine, etc…

  • stork deployment give you more control and the sense of a traditional Java Server with start/stop scripts but also the power of control the application logs at runtime.