Edit on GitHub

Lisp TCP REPL Server

In addition to launching a Lisp REPL in the web and app dotnet tools you can also open a Lisp REPL into any ServiceStack App configured with the LispReplTcpServer ServiceStack plugin. This effectively opens a “programmable gateway” into any ServiceStack App where it’s able to perform live queries, access IOC dependencies, invoke internal Server functions and query the state of a running Server which like the Debug Inspector can provide invaluable insight when diagnosing issues on a remote server.

To see it in action we’ll enable it one of our production Apps techstacks.io which as it’s a Vuetify SPA App is only configured with an empty SharpPagesFeature as it doesn’t use any server-side scripting features.

We’ll enable it in DebugMode where we can enable by setting DebugMode in our App’s appsettings.Production.json which will launch a TCP Socket Server which by default is configured to listen to the loopback IP on port 5005.

if (Config.DebugMode)
{
    Plugins.Add(new LispReplTcpServer {
        ScriptMethods = {
            new DbScripts()
        },
        ScriptNamespaces = {
            nameof(TechStacks),
            $"{nameof(TechStacks)}.{nameof(ServiceInterface)}",
            $"{nameof(TechStacks)}.{nameof(ServiceModel)}",
        },
    });
}

ScriptNamespaces behaves like C#’s using Namespace; statement letting you reference Types by Name instead of its fully-qualified Namespace.

Whilst you can now connect to it with basic telnet, it’s a much nicer experience to use it with the rlwrap readline wrap utility which provides an enhanced experience with line editing, persistent history and completion.

$ sudo apt-get install rlwrap

Then you can open a TCP Connection to connect to a new Lisp REPL with:

$ rlwrap telnet localhost 5005

Where you now have full scriptability of the running server as allowed by #Script Pages SharpPagesFeature which allows scripting of all .NET Types by default.

TechStacks TCP Lisp REPL Demo

In this demo we’ll explore some of the possibilities of scripting the live techstacks.io Server where we can resolve IOC dependencies to send out tweets using its registered ITwitterUpdates dependency, view the source and load a remote parse-rss lisp function into the new Lisp interpreter attached to the TCP connection, use it to parse Hacker News RSS Feed into a .NET Collection where it can be more easily queried using its built-in functions which is used to construct an email body with HN’s current Top 5 links.

It then uses DB Scripts to explore its configured AWS RDS PostgreSQL RDBMS, listing its DB tables and viewing its column names and definitions before retrieving the Email addresses of all Admin users, sending them each an email with HN’s Top 5 Links by publishing 5x SendEmail Request DTOs using the publishMessage ServiceStack Script to where they’re processed in the background by its configured MQ Server that uses it to execute the SendEmail ServiceStack Service where it uses its configured AWS SES SMTP Server to finally send out the Emails:

YouTube: youtu.be/HO523cFkDfk

Password Protection

Since TCP Server effectively opens your remote Server up to being scripted you’ll want to ensure the TCP Server is only accessible within a trusted network, effectively treating it the same as Redis Security Model.

A secure approach would be to leave the default of only binding to IPAddress.Loopback so only trusted users with SSH access will be able to access it, which they’ll still be able to access remotely via Local PC > ssh > telnet 127.0.0.1 5005.

Just like Redis AUTH you can also add password protection for an additional layer of Security:

Plugins.Add(new LispReplTcpServer {
    RequireAuthSecret = true,
    ...
});

Which will only allow access to users with the configured AuthSecret:

SetConfig(new HostConfig { 
    AdminAuthSecret = "secretz" 
});

Annotated Lisp TCP REPL Transcript

; resolve `ITwitterUpdates` IOC dependency and assign it to `twitter`
(def twitter (resolve "ITwitterUpdates"))

; view its concrete Type Name
(typeName twitter)

; view its method names 
(joinln (methods twitter))

; view its method signatures 
(joinln (methodTypes twitter))

; use it to send tweet from its @webstacks account
(.Tweet twitter "Who's using #Script Lisp? https://sharpscript.net/lisp")

; view all available scripts in #Script Lisp Library Index gist.github.com/3624b0373904cfb2fc7bb3c2cb9dc1a3
(gistindex)

; view the source code of the `parse-rss` library
(load-src "index:parse-rss")

; assign the XML contents of HN's RSS feed to `xml`
(def xml (urlContents "https://news.ycombinator.com/rss"))

; preview its first 1000 chars
(subString xml 0 1000)

; use `parse-rss` to parse the RSS feed into a .NET Collection and assign it to `rss`
(def rss (parse-rss xml))

; view the `title`, `description` and the first `item` in the RSS feed:
(:title rss)
(:description rss)
(:0 (:items rss))

; view the links of all RSS feed items
(joinln (map :link (:items rss)))

; view the links and titles of the top 5 news items
(joinln (map :link (take 5 (:items rss))))
(joinln (map :title (take 5 (:items rss))))

; construct a plain-text numbered list of the top 5 HN Links and assign it to `body`
(joinln (map-index #(str %2 (:title %1)) (take 5 (:items rss))))
(joinln (map-index #(str (padLeft (1+ %2) 2) ". " (:title %1)) (take 5 (:items rss))))
(def body (joinln 
    (map-index #(str (padLeft (1+ %2) 2) ". " (:title %1) "\n" (:link %1) "\n") (take 5 (:items rss)))))

; view all TechStacks PostgreSQL AWS RDS tables
(dbTableNames)
(joinln dbTableNames)

; view the column names and definitions of the `technology` table
(joinln (dbColumnNames "technology"))
(joinln (dbColumns "technology"))

; search for all `user` tables
(globln "*user*" (dbTableNames))

; view how many Admin Users with Emails there are
(dbScalar "select count(email) from custom_user_auth where roles like '%Admin%'")

; assign the Admin Users email to the `emails` list
(def emails (map :email (dbSelect "select email from custom_user_auth where roles like '%Admin%'")))

; search for all `operation` script methods
(globln "*operation*" scriptMethods)

; search for all `email` Request DTOs
(globln "*email*" metaAllOperationNames)

; view the properties available on the `SendEmail` Request DTO
(props (SendEmail.))

; search for all `publish` script methods that can publish messages
(globln "publish*" scriptMethods)

; create and publish 5x `SendEmail` Request DTOs for processing by TechStacks configured MQ Server
(doseq (to emails) (publishMessage "SendEmail" { :To to :Subject "Top 5 HN Links" :Body body }))