Final Enhancements for My Julia Web Package: Toolips Remote
Written on
Introduction to Toolips Remote
In the last three months, I have dedicated considerable effort to developing a web framework in Julia known as Toolips, which has since expanded into a broader ecosystem. Currently, this ecosystem consists of six completed packages, with several additional ones in development. Although progress on these packages has slowed, I anticipate that it won't be long before the initial features are fully operational. One of the packages nearing completion is ToolipsRemote, which requires a few final enhancements before its official launch.
Enhancing Remote Connections and User Groups
I've often discussed the potential for creating server extensions for Toolips and have even developed a few examples on my blog. However, extending Toolips can take various forms beyond just server extensions and reusable components. One effective method is through the use of connections. In this section, I will demonstrate how we can modify the AbstractConnection type to enhance Toolips's functionality by encapsulating this package.
To understand user groups, let's examine the current fields of the Remote extension:
Copytype::Vector{Symbol}
remotefunction::Function
f::Function
logins::Dict{String, Vector{UInt8}}
users::Dict{Vector{UInt8}, String}
motd::String
These fields require some adjustments to support distinct user groups, as each group should have its own set of remote functions. To facilitate this, I will make slight modifications to the data structure.
We’ll look at the parameters for the inner constructor:
function Remote(remotefunction::Function = controller(),
users::Vector{Pair{String, String}} = ["root" => "1234"];
motd::String = """### login to toolips remote session""",
serving_f::Function = serve_remote)
The remotefunction is triggered after a user logs in with the specified String from their REPL. The users field is a vector of username-password pairs, necessary for initializing Toolips remote. The motd is the message displayed when a user connects, and serving_f is the login function—this is typically not altered.
The primary modification involves changing the users vector from a Vector{Pair{String, String}} to a Vector{Pair{String, Pair}}, incorporating both the password and the user group. Additionally, the remotefunction will become a dictionary indexed by user groups, allowing for more flexible remote serving.
Copytype::Vector{Symbol}
remotefunction::Dict{Int64, Function}
f::Function
logins::Dict{String, Vector{UInt8}}
users::Dict{Vector{UInt8}, Pair{String, Int64}}
motd::String
Next, let's update the constructor to align with these changes:
remotefunction::Dict{Int64, Function} = Dict(1 => controller())
users::Vector{Pair{String, Pair}} = ["root" => "1234" => 1]
We’ll integrate this into the function:
function Remote(remotefunction::Dict{Int64, Function} = Dict(1 => controller()),
users::Vector{Pair{String, Pair}} = ["root" => "1234" => 1];
motd::String = """### login to toolips remote session""",
serving_f::Function = serve_remote)
logins::Dict{String, Vector{UInt8}} = Dict(
[n[1] => sha256(n[2]) for n in users])
users = Dict{Vector{UInt8}, Pair{String, Int64}}()
f(r::Vector{AbstractRoute}, e::Vector{ServerExtension}) = begin
r["/remote/connect"] = serving_fend
new([:routing, :connection], remotefunction, f, logins, users,
motd)::Remote
end
In conclusion, we will adjust the serve_remote function to accommodate the new user group structure. This function now retrieves the user group of the connecting user, indexing the remotefunction field accordingly.
function serve_remote(c::Connection)
message = getpost(c)
keybeg = findall(":SESSIONKEY:", message)
if length(keybeg) == 1
keystart = keybeg[1][2] + 11
key = message[keystart:length(message)]
message = message[1:keybeg[1][1] - 1]
print(message)
if sha256(key) in keys(c[:Remote].users)
group = c[:Remote].users[sha256(key)][2]
c[:Remote].remotefunction[group](c, message, c[:Remote].users[sha256(key)])
else
write!(c, "Key invalid.")end
end
end
Creating New Remote Connections
Before we proceed with writing this new connection, let’s refer to the Toolips.AbstractConnection documentation to learn more about connection structures.
mutable struct RemoteConnection <: Toolips.AbstractConnection
routes::Vector{Toolips.AbstractRoute}
extensions::Vector{Toolips.ServerExtension}
end
The HTTP field can remain as Any for now. Additionally, we will add user data, which includes both the group and username:
mutable struct RemoteConnection <: Toolips.AbstractConnection
routes::Vector{Toolips.AbstractRoute}
extensions::Vector{Toolips.ServerExtension}
group::Int64
name::String
end
An inner constructor will be created to instantiate this structure from the previous user data:
mutable struct RemoteConnection <: Toolips.AbstractConnection
routes::Vector{Toolips.AbstractRoute}
extensions::Vector{Toolips.ServerExtension}
group::Int64
name::String
function RemoteConnection(c::Connection, userdata::Pair{String, Int64})
new(c.routes, c.http, c.extensions, userdata[2], userdata[1])end
end
This constructor will be invoked in the serve_remote function to create new connections.
if sha256(key) in keys(c[:Remote].users)
userinfo = c[:Remote].users[sha256(key)]
newc = RemoteConnection(c, userinfo)
c[:Remote].remotefunction[userinfo[2]](newc)
else
Let's proceed with dispatching functions for various components, ensuring that they render correctly as markdown.
function write!(c::RemoteConnection, s::Component{<:Any})
write!(c, s[:text])
end
We will also define specific functions for other elements such as dividers and headings.
Testing the Final Implementation
Now it's time to create a new web application:
using Toolips
Toolips.new_app("RemoteTest")
After addressing some minor errors and importing necessary functions, I tested the new remote connection.
connect("http://127.0.0.1:8000")
The connection was successful, and I was able to interact with the remote session, indicating that the enhancements to the ToolipsRemote package are on track for release.
Video Demonstrations
To further illustrate the progress made, here are two relevant videos:
Putting Final Touches on Our Bathrooms | Building Our Own Home
This video showcases the final enhancements in a practical project setting, demonstrating the importance of detail in development.
Music Production Live Episode 95 - Putting the Finishing Touch on a New Electronic Track
This episode dives into the creative process of completing a music track, paralleling the iterative improvements made in software development.
Conclusion
The progress on the ToolipsRemote package is exciting, as it allows for both website and remote instance serving through a unified function. This development not only enhances functionality but also lays the groundwork for future extensions. I am thrilled to share my first Connection extension and look forward to publishing this package to the Julia General Registry soon. Thank you for following along!