HOWTO

This chapter contains various small tips. Each tip is too small to have its own chapter.

Basic authentication

You can protect the whole site or just certain actions with basic authentication.

Note that Xitrum does not support digest authentication because it provides a false sense of security. It is vulnerable to a man-in-the-middle attack. For better security, you should use HTTPS, which Xitrum has built-in support (no need for additional reverse proxy like Apache or Nginx just to add HTTPS support).

Config basic authentication for the whole site

In config/xitrum.conf:

"basicAuth": {
  "realm":    "xitrum",
  "username": "xitrum",
  "password": "xitrum"
}

Add basic authentication to an action

import xitrum.Action

class MyAction extends Action {
  beforeFilter {
    basicAuth("Realm") { (username, password) =>
      username == "username" && password == "password"
    }
  }
}

Load config files

JSON file

JSON is neat for config files that need nested structures.

Save your own config files in “config” directory. This directory is put into classpath in development mode by build.sbt and in production mode by script/runner (and script/runner.bat).

myconfig.json:

{
  "username": "God",
  "password": "Does God need a password?",
  "children": ["Adam", "Eva"]
}

Load it:

import xitrum.util.Loader

case class MyConfig(username: String, password: String, children: Seq[String])
val myConfig = Loader.jsonFromClasspath[MyConfig]("myconfig.json")

Notes:

  • Keys and strings must be quoted with double quotes
  • Currently, you cannot write comment in JSON file

Properties file

You can also use properties files, but you should use JSON whenever possible because it’s much better. Properties files are not typesafe, do not support UTF-8 and nested structures etc.

myconfig.properties:

username = God
password = Does God need a password?
children = Adam, Eva

Load it:

import xitrum.util.Loader

// Here you get an instance of java.util.Properties
val properties = Loader.propertiesFromClasspath("myconfig.properties")

Typesafe config file

Xitrum also includes Akka, which includes the config library created by the company called Typesafe. It may be a better way to load config files.

myconfig.conf:

username = God
password = Does God need a password?
children = ["Adam", "Eva"]

Load it:

import com.typesafe.config.{Config, ConfigFactory}

val config   = ConfigFactory.load("myconfig.conf")
val username = config.getString("username")
val password = config.getString("password")
val children = config.getStringList("children")

Serialize and deserialize

To serialize to Array[Byte]:

import xitrum.util.SeriDeseri
val bytes = SeriDeseri.toBytes("my serializable object")

To deserialize bytes back:

val option = SeriDeseri.fromBytes[MyType](bytes)  // Option[MyType]

If you want to save to file:

import xitrum.util.Loader
Loader.bytesToFile(bytes, "myObject.bin")

To load from the file:

val bytes = Loader.bytesFromFile("myObject.bin")

Encrypt data

To encrypt data that you don’t need to decrypt later (one way encryption), you can use MD5 or something like that.

If you want to decrypt later, you can use the utility Xitrum provides:

import xitrum.util.Secure

// Array[Byte]
val encrypted = Secure.encrypt("my data".getBytes)

// Option[Array[Byte]]
val decrypted = Secure.decrypt(encrypted)

You can use xitrum.util.UrlSafeBase64 to encode and decode the binary data to normal string (to embed to HTML for response etc.).

// String that can be included in URL, cookie etc.
val string = UrlSafeBase64.noPaddingEncode(encrypted)

// Option[Array[Byte]]
val encrypted2 = UrlSafeBase64.autoPaddingDecode(string)

If you can combine the above operations in one step:

import xitrum.util.SeriDeseri

val mySerializableObject = new MySerializableClass

// String
val encrypted = SeriDeseri.toSecureUrlSafeBase64(mySerializableObject)

// Option[MySerializableClass]
val decrypted = SeriDeseri.fromSecureUrlSafeBase64[MySerializableClass](encrypted)

SeriDeseri uses Twitter Chill to serialize and deserialize. Your data must be serializable.

You can specify a key for encryption.

val encrypted = Secure.encrypt("my data".getBytes, "my key")
val decrypted = Secure.decrypt(encrypted, "my key")
val encrypted = SeriDeseri.toSecureUrlSafeBase64(mySerializableObject, "my key")
val decrypted = SeriDeseri.fromSecureUrlSafeBase64[MySerializableClass](encrypted, "my key")

If no key is specified, secureKey in xitrum.conf file in config directory will be used.

Multiple sites at the same domain name

If you want to use a reverse proxy like Nginx to run multiple different sites at the same domain name:

http://example.com/site1/...
http://example.com/site2/...

You can config baseUrl in config/xitrum.conf.

In your JS code, to have the correct URLs for Ajax requests, use withBaseUrl in xitrum.js.

# If the current site's baseUrl is "site1", the result will be:
# /site1/path/to/my/action
xitrum.withBaseUrl('/path/to/my/action')

Convert Markdown text to HTML

If you have already configured your project to use Scalate template engine, you only have to do like this:

import org.fusesource.scalamd.Markdown
val html = Markdown("input")

Otherwise, you need to add this dependency to your project’s build.sbt:

libraryDependencies += "org.fusesource.scalamd" %% "scalamd" % "1.6"

Monitor file change

You can register callback(s) for StandardWatchEventKinds on files or directories.

import java.nio.file.Paths
import xitrum.util.FileMonitor

val target = Paths.get("absolute_path_or_path_relative_to_application_directory").toAbsolutePath
FileMonitor.monitor(FileMonitor.MODIFY, target, { path =>
  // Do some callback with path
  println(s"File modified: $path")

  // And stop monitoring if necessary
  FileMonitor.unmonitor(FileMonitor.MODIFY, target)
})

Under the hood, FileMonitor uses Schwatcher.

Temporary directory

Xitrum projects by default (see tmpDir in xitrum.conf) uses tmp directory in the current working directory to save Scalate generated .scala files, big upload files etc.

To get path to that directory:

xitrum.Config.xitrum.tmpDir.getAbsolutePath

To create a new file or directory in that directory:

val file = new java.io.File(xitrum.Config.xitrum.tmpDir, "myfile")

val dir = new java.io.File(xitrum.Config.xitrum.tmpDir, "mydir")
dir.mkdirs()

Stream videos

There are many ways to stream videos. One easy way:

  • Serve interleaved .mp4 video files, so that users can play the videos while downloading.
  • And use a HTTP server like Xitrum that supports range requests, so that users can skip to a movie position that has not been downloaded.

You can use MP4Box to interleave movie file data by chunks of 500 milliseconds:

MP4Box -inter 500 movie.mp4