Recons allows you to embed a Scala REPL server into your application, and connect to it from outside. This is mostly useful as an advanced troubleshooting utility, giving you almost unlimited access to all the guts and internals of your running application.
Table of Contents generated with DocToc
- Features
- Supported Scala versions
- Quickstart
- Using standard input and output
- Security
- Troubleshooting utilities
- Customization
- remote, fully-featured Scala console, i.e. with syntax highlighting, tab completion, etc.
- support for Scala 2 and 3
- troubleshooting utilities and customizations
- pre-bundled client packages
- secure communication with TLS
Recons supports Scala 2 and 3. However, it uses the Scala compiler API, which has no compatibility guarantees. As a result, Recons must be cross-built for every minor Scala version. The currently supported versions are 2.13.14+ and 3.4.2+ (unless a version is very fresh and Recons hasn't been built for it yet).
Because the implementation of Recons copies some code from the compiler, and uses some private APIs via runtime reflection, there's a risk that it may not work with future Scala versions or require more significant changes to keep up with the compiler. Hopefully, it may be possible to propose some refactors to the compiler itself to improve the situation.
Add the following dependency to your build.sbt
:
libraryDependencies +=
"com.github.ghik" % "recons-server" % VERSION cross CrossVersion.full
Recons runs the Scala REPL, which wraps a real Scala compiler, which needs an explicit classpath.
Typically, you can obtain the classpath of your JVM process from the java.class.path
system
property:
sys.props("java.class.path").split(File.pathSeparator).toSeq
Warning
Depending on the exact way your application is launched, you may not be able to rely on
the java.class.path
system property - it may be unset or set to an incorrect value (for the
REPL server needs). You may need another way of determining the classpath.
import java.io.File
import com.github.ghik.recons.server.*
val server = new ReplServer(
classpath = sys.props("java.class.path").split(File.pathSeparator).toSeq,
tlsConfig = None, // disabling TLS for the sake of brevity of this example
bindAddress = "localhost",
bindPort = 6666,
)
server.run()
In order to connect to the REPL server running inside your application, you need to use the client binary. You can download it from a Recons release on GitHub.
RECONS_VERSION=<desired recons version>
SCALA_VERSION=<desired scala version>
package=recons-client_$SCALA_VERSION-$RECONS_VERSION
wget https://github.com/ghik/recons/releases/download/v$RECONS_VERSION/$package.zip
unzip $package.zip && cd $package
Then, run the client to connect to the server:
./bin/recons-client -h localhost -p 6666 --no-tls
and you should be able to see a fully-featured Scala REPL, e.g.
Welcome to Recons, based on Scala 3.4.2 (Java 21.0.1, OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.
val $ext: com.github.ghik.recons.server.utils.ShellExtensions = com.github.ghik.recons.server.utils.ShellExtensions@46a00624
recons>
Note
For simplicity of the example, we have disabled TLS.
The REPL runs in the server JVM process, which is a different process than the client.
This means that the standard input and output of the REPL are not connected to the client,
like they would in a regular, local Scala REPL.
If you invoke scala.Predef.println
or similar functions in the remote REPL,
you will not see the output in your terminal.
In order to work around this to some extent, Recons shadows println
and print
methods
with ones that use a custom output stream, connected to the client. However, they work only
in code written and compiled directly in the REPL.
You can also use out
(a PrintStream
) to grab direct access to this custom output stream,
pass it to other functions, etc.
There is currently no way to read client's standard input inside the REPL.
Recons gives you full access to your application process from outside, and lets you run arbitrary code in it. This is an obvious security risk, so you must make sure to limit access to the remote REPL to power users, and be extremely careful about what you execute in it. Otherwise, you may crash your process, put a thread into an infinite loop, or corrupt the internal state of your application.
Recons binds on localhost address by default. It might make sense to leave it this way, and
allow access only from the local machine or container/pod (e.g. in Kubernetes you would need permissions
to execute kubectl exec
on your application's pods in order to gain access to the REPL).
You can also secure client-server communication with TLS:
ReplServer
accepts aTlsConfig
parameter, which allows you to configure a completeSSLContext
andSSLParameters
for the server. This effectively allows you to specify (among others):- the keystore and truststore
- enabled protocols and cipher suites
- client authentication
- The client accepts
--cacert
option, as well as--cert
and--key
options for client authentication. Invoke the client with--help
for more details.
Since Recons is designed for troubleshooting, it provides some utilities to make it easier.
Namely, every REPL session automatically
imports ShellExtensions
,
which give you the following tools:
out
,print
andprintln
- see Using standard input and output.- private field and method accessors, e.g.
someObject.d.privateField
,someObject.d.privateMethod(arg)
- private static field and method accessors,
e.g.
statics[SomeClass].privateStaticField
,statics[SomeClass].privateStaticMethod(arg)
- shorter syntax for
.asInstanceOf[T]
, e.g.someObject.as[T]
- to complement the private field and method accessors, which return untyped values
You can customize the REPL in several ways, by specifying:
- a custom welcome message
- a custom prompt
- initial set of bindings and imports
See ReplConfig
for more details.