Ezt fejezzük be, de fejezzük be nagyszerűen.
bevezetés
Az elmúlt körülbelül három hónapban valóban mindent kidolgoztam egy webfejlesztési keretrendszeren, amelyet Julia számára hoztam létre, toollips néven, a hozzá tartozó ökoszisztémával együtt. Az ökoszisztéma jelenleg elég nagy, mondhatnám, hat elkészült csomagot tartalmaz, és még több készülőben van.
Bár ezek egy kicsit félre lettek tolva, mivel egyszerűen rengeteget kell fejlesztenem, nem látom, hogy olyan sok idő telik el, amíg mindkét első sor elkészül és indulásra kész lesz. Az egyik csomag, amely nagyon közel áll a kiadáshoz, a ToolipsRemote.
Csak néhány frissítést szerettem volna végrehajtani ezen a csomagon, mielőtt közzétenném,
- Távoli kapcsolatok
- Felhasználói csoportok
Ezzel a koncepcióval azt vettem észre, hogy sokat beszéltem a toollips szerverbővítmények készítéséről, sőt a blogomon is készítettem néhányat. Az eszközsorok kiterjesztésének azonban több módja van, mint a szerverbővítmények és a reprodukálható komponensek létrehozása! Az egyik ilyen mód a Connection. Ma megmutatom, hogyan bővíthetjük ki a AbstractConnection
típust, hogy megváltoztassuk a toollips működését különböző körülmények között a csomag becsomagolásával!
felhasználói csoportok
Gyorsan ismerjük meg a felhasználói csoportok létrehozásának módját. Íme a távoli bővítmény jelenlegi mezői:
type::Vector{Symbol} remotefunction::Function f::Function logins::Dict{String, Vector{UInt8}} users::Dict{Vector{UInt8}, String} motd::String
Ezt egy kicsit módosítani kell, hogy különböző felhasználói csoportokat hozzunk létre, mert a különböző felhasználói csoportoknak eltérő távoli funkciókkal kell rendelkezniük. Ennek ellenére kissé módosítom az adataimat, hogy megkönnyítsem az ilyesmit. Vessünk egy pillantást a belső konstruktor bemeneteire:
function Remote(remotefunction::Function = controller(), users::Vector{Pair{String, String}} = ["root" => "1234"]; motd::String = """### login to toolips remote session""", serving_f::Function = serve_remote)
- A
remotefunction
az a függvény, amely mindegyiket meghívja, miután valaki bejelentkezett a megadott karakterlánccal a REPL-ből. - A
users
are olyan párok vektora, amelyek karakterláncként tartalmazzák a felhasználóneveket és jelszavakat. Ez csak a toollips remote indítására vonatkozik, a bejelentkezéseket vagy aktívan módosítani és tárolni kell, vagy környezeti változók biztosítják. - A
motd
a nap üzenete, amely akkor jelenik meg, amikor a felhasználó először csatlakozik. - Az
serving_f
az a funkció, amellyel a felhasználók bejelentkeznek. Valószínűleg nyugodtan kijelenthetjük, hogy a legtöbb esetben nem fogunk ezen változtatni.
Mindezeket figyelembe véve az itt végrehajtandó módosítás a users
vektor, amelyet Vector{Pair{String, String}}
-ről Vector{Pair{String, Pair}}
-ra fogok változtatni. Az új pár tartalmazza a jelszót és a felhasználói csoportot is. Meg kell változtatnunk a remotefunction
értéket is, ezt inkább egy különböző funkciók szótárára cserélem, kulcsként a felhasználói csoportokkal. Így mindkét funkcióval csak indexelhetünk a távoli kiszolgálás érdekében. Menjünk előre, és kezdjük el megváltoztatni ezeket a mezőket.
type::Vector{Symbol} remotefunction::Dict{Int64, Function} f::Function logins::Dict{String, Vector{UInt8}} users::Dict{Vector{UInt8}, Pair{String, Int64}} motd::String
Most megváltoztatom a függvényt, hogy megfelelővé tegyem ezeket a mezőket. Kezdjük az érvekkel:
remotefunction::Dict{Int64, Function} = Dict(1 => controller()) users::Vector{Pair{String, Pair}} = ["root" => "1234" => 1]
Ezután ezt bevisszük az egész függvénybe:
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_f end new([:routing, :connection], remotefunction, f, logins, users, motd)::Remote end
Végül frissítenünk kell a serve_remote
-t, hogy illeszkedjen ezekhez az új változásokhoz. Lekérjük a felhasználónk felhasználói csoportját, majd a remotefunction
mezőt ezzel a csoporttal indexeljük a funkció biztosítása érdekében.
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)] # cut out the session key if provided. 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
Azt is észreveheti, hogy a távoli függvényhívás három különböző argumentumot vesz fel. Mondanom sem kell, ez nem ideális. Ezen fogunk változtatni az új távoli kapcsolattal. Ennek ellenére valószínűleg ehhez a felhasználói adatainkat szeretnénk kérni, ezért íme egy pillantás:
if sha256(key) in keys(c[:Remote].users) userinfo = c[:Remote].users[sha256(key)] c[:Remote].remotefunction[userinfo[2]](newc) else write!(c, "Key invalid.") end
Észreveheti, hogy most ezt a függvényt a newc
hívja meg, amely még nem létezik. Ez lesz a mi RemoteConnection
, úgyhogy folytassuk és készítsük el.
távoli kapcsolatok
Mielőtt megírnánk ezt az új kapcsolatot, hivatkozzunk a Toolips.AbstractConnection
dokumentációjára, hogy egy kicsit többet megtudjunk a kapcsolatok egészéről.
help?> Toolips.AbstractConnection
Három különböző mezőt kell megadnunk a kapcsolatunkon, a kiszolgáló útvonalain, a kiszolgáló kiterjesztésein és a http-n. Készítsünk egy ilyen szerkezetet:
mutable struct RemoteConnection <: Toolips.AbstractConnection routes::Vector{Toolips.AbstractRoute} http::Any extensions::Vector{Toolips.ServerExtension} end
A HTTP mezőnknek ebben az esetben tetszőlegesnek kell lennie, különben el kell érnünk a HTTP-t. Minden egyes felvenni kívánt felhasználóra vonatkozóan is rendelkezünk adatokkal. Ez természetesen csak a csoport és a felhasználónév lesz:
mutable struct RemoteConnection <: Toolips.AbstractConnection routes::Vector{Toolips.AbstractRoute} http::Any extensions::Vector{Toolips.ServerExtension} group::Int64 name::String end
Most készítünk egy belső konstruktort, amely a korábban meglévő adatokból, a Connection és a felhasználói adatokból létrehozza ezt.
mutable struct RemoteConnection <: Toolips.AbstractConnection routes::Vector{Toolips.AbstractRoute} http::Any 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
Végül ezt a konstruktort visszahívjuk a serve_remote
függvényünkben.
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
Most létre kell hoznunk néhány küldeményt. Ebben az a fantasztikus, hogy megváltoztathatjuk a különböző összetevők írásmódját. Mivel a bejövő szöveg leértékelésként jelenik meg, egyszerűen write!
konvertálhatjuk a komponenseket leértékeléssé! Először egy összefoglalót készítünk az összes komponensről, így ha olyan Összetevőt adunk meg, amely nem jeleníthető meg távolról, akkor csak az Összetevő szövegét kapjuk:
function write!(c::RemoteConnection, s::Component{<:Any}) write!(c, s[:text]) end
Ezután írjunk egy másik függvényt egy div
-hez. Ez a funkció ---
szintaxisú elválasztókat biztosít a jelölésen belül.
function write!(c::RemoteConnection, s::Component{:div}) write!(c, "---") [write!(c, child) for child in s[:children]] write!(c, "---") end
Folytatjuk ezt a címsorokkal és félkövérrel szedve:
write!(c::RemoteConnection, s::Component{:h1}) = write!(c, "# $(s[:text])\n") write!(c::RemoteConnection, s::Component{:h2}) = write!(c, "## $(s[:text])\n") write!(c::RemoteConnection, s::Component{:h3}) = write!(c, "### $(s[:text])\n") write!(c::RemoteConnection, s::Component{:h4}) = write!(c, "#### $(s[:text])\n") write!(c::RemoteConnection, s::Component{:b}) = write!(c, "**$(s[:text])**")
Ugyanezt biztosítsuk a hiperhivatkozásoknál is, ehhez a a
használatával.
write!(c::RemoteConnection, s::Component{:a}) = write!(c, "[$(s[:text])]($(s[:href]))")
Végül megváltoztatom a controller
által létrehozott alapértelmezett függvényünket. Ez a függvény egy függvényt ad vissza súgómenüvel és dolgokkal.
function controller(commands::Dict{String, Function} = Dict("?" => helpme, "logit" => logit)) f(c::Connection, m::String) = begin args = [string(arg) for arg in split(m, ";")] cmd = args[1] if length(args) != 1 args = args[2:length(args)] else args = Vector{String}() end write!(c, commands[cmd](args, c)) end f end
Ennek a funkciónak most egy Connection-t és a bemeneti karakterláncot is fel kell vennie – aminek most tudomásom szerint a távoli kapcsolaton belül kell lennie.
mutable struct RemoteConnection <: Toolips.AbstractConnection routes::Vector{Toolips.AbstractRoute} http::Any extensions::Vector{Toolips.ServerExtension} group::Int64 name::String message::String function RemoteConnection(c::Connection, userdata::Pair{String, Int64}, message::AbstractString = "") new(c.routes, c.http, c.extensions, userdata[2], userdata[1], string(message))::RemoteConnection end end newc = RemoteConnection(c, userinfo, message) c[:Remote].remotefunction[userinfo[2]](newc)
Most változtassuk meg ezt a funkciót.
function controller(commands::Dict{String, Function} = Dict("?" => helpme, "logit" => logit)) f(c::RemoteConnection) = begin m = c.message args = [string(arg) for arg in split(m, ";")] cmd = args[1] if length(args) != 1 args = args[2:length(args)] else args = Vector{String}() end write!(c, commands[cmd](args, c)) end f end
Elég egyszerű, most próbáljuk ki mindezt, és reméljük, hogy nem lesz hiba.
következtetés
Hozzunk létre egy új webalkalmazást.
julia> using Toolips julia> Toolips.new_app("RemoteTest")
Ezt valójában kétszer kellett megcsinálnom, mert az aktivált környezet a ToolipsRemote környezet volt, amelyen még mindig 0.1.3-as toollips volt. A kód egy dolog kivételével szinte kompatibilis, de nekem kicsit jobban tetszik a projekt felépítése azzal, ahogy az újabb verzió csinálja.
hopp
module RemoteTest using Toolips using ToolipsRemote # welcome to your new toolips project! """ home(c::Connection) -> _ -------------------- The home function is served as a route inside of your server by default. To change this, view the start method below. """ function home(c::Connection) write!(c, p("helloworld", text = "hello world!")) end fourofour = route("404") do c write!(c, p("404message", text = "404, not found!")) end routes = [route("/", home), fourofour] extensions = Vector{ServerExtension}([Logger(), Remote()]) """ start(IP::String, PORT::Integer, ) -> ::ToolipsServer -------------------- The start function starts the WebServer. """ function start(IP::String = "127.0.0.1", PORT::Integer = 8000) ws = WebServer(IP, PORT, routes = routes, extensions = extensions) ws.start(); ws end end # - module
Próbáljuk ki ezt!
include("dev.jl") ERROR: LoadError: MethodError: no method matching ToolipsRemote.Remote(::Dict{Int64, ToolipsRemote.var"#f#10"{Dict{String, Function}}}) Closest candidates are: ToolipsRemote.Remote() at ~/.julia/packages/ToolipsRemote/ktpKK/src/ToolipsRemote.jl:56 ToolipsRemote.Remote(::Dict{Int64, Function}) at ~/.julia/packages/ToolipsRemote/ktpKK/src/ToolipsRemote.jl:56 ToolipsRemote.Remote(::Dict{Int64, Function}, ::Vector{Pair{String, Pair}}; motd, serving_f) at ~/.julia/packages/ToolipsRemote/ktpKK/src/ToolipsRemote.jl:56
Egyszerűen hozzá kell adnom a paramétereket ehhez a konstruktorhoz:
remotefunction::Dict{Int64, Function} = Dict{Int64, Function}(1 => controller()), users::Vector{Pair{String, Pair{String, Int64}}} = ["root" => "1234" => 1];
Most megint,
include("dev.jl") RROR: LoadError: MethodError: no method matching sha256(::Pair{String, Int64}) Closest candidates are: sha256(::Union{Tuple{Vararg{UInt8, N}} where N, AbstractVector{UInt8}}) at /opt/julia-1.6.3/julia-1.6.3/share/julia/stdlib/v1.7/SHA/src/SHA.jl:87 sha256(::IO) at /opt/julia-1.6.3/julia-1.6.3/share/julia/stdlib/v1.7/SHA/src/SHA.jl:116 sha256(::IO, ::Any) at /opt/julia-1.6.3/julia-1.6.3/share/julia/stdlib/v1.7/SHA/src/SHA.jl:116
Ez az a típusú hiba, amelyre számítottam, mert a felhasználók megváltoztak.
logins::Dict{String, Vector{UInt8}} = Dict( [n[1] => sha256(n[2][1]) for n in users])
Szerencsére a legtöbbször bejelentkezést használnak, és ez a hiba ismét a szerkezetünkön belül volt.
julia> include("dev.jl") Activating project at `~/dev/toolips/RemoteTest` WebServer hosted at: http://127.0.0.1:8000 status: active routes / 404 /remote/connect extensions Logger ToolipsRemote.Remote
Most próbáljunk meg csatlakozni hozzá!
julia> connect("http://127.0.0.1:8000") ┌ Error: error handling request │ exception = │ MethodError: no method matching write!(::Connection, ::String) │ Stacktrace:
Nagyon nagy hoppá. Elfelejtettem importálni a write!
-et a toollips-ből, ezért felülírtuk.
import Toolips: write!
Szerencsére, ha most hibát kapunk, az nincs a távoli konstruktorunkban. Valószínűnek tartom, hogy minden alkalommal dobni fog, amikor megpróbáljuk használni a serve_remote
-t, de meglátjuk, hogy ez így van-e.
julia> using ToolipsRemote julia> connect("http://127.0.0.1:8000") login to toolips remote session ––––––––––––––––––––––––––––––––– user: root password for root: [2022:09:06:21:53]: 🌷 toolips> 123 [2022:09:06:21:53]: 🌷 toolips> �e�Y B/�A~Hg��O��J?��~������z� [2022:09:06:21:53]: 🌷 toolips> �gB��\v��U�g�6#ȳ��E��x��F� "Your password was not found." julia>
Lehet, hogy elfelejtettem az alapértelmezett jelszót. Valószínűleg jó ötlet lenne eltávolítani a jelszó naplózását, amikor csak lehetséges.
user: root password for root: ┌ Error: error handling request │ exception = │ KeyError: key UInt8[0x15, 0x61, 0x9c, 0x0c, 0x15, 0x79, 0x7c, 0xb3, 0x9d, 0x0f … 0xbf, 0xf9, 0x07, 0xeb, 0x9c, 0x16, 0x1f, 0x23, 0x48, 0x27] not found │ Stacktrace: [1] getindex(h::Dict{Vector{UInt8}, Pair{String, Int64}}, key::Vector{UInt8}) │ @ Base ./dict.jl:481 │ [2] serve_remote(c::Connection)
Tudtam, hogy a serve_remote
problémákat fog okozni nekünk.
if sha256(usrpwd[2]) == c[:Remote].logins[string(usrpwd[1])] key = randstring(16) c[:Remote].users[sha256(key)][1] = usrpwd[1] write!(c, "$(usrpwd[1]):$key") else
Amit ezúttal csináltam, az indexelésünket véletlenül getindexre cseréltem, nem pedig beállított indexre, mert csak megkerestem a felhasználók minden példányát, és indexeltem, nem sok figyelmet fordítva a kontextusra. Ennek kijavítása feltárja,
julia> include("dev.jl") Activating project at `~/dev/toolips/RemoteTest` WebServer hosted at: http://127.0.0.1:8000 status: active routes / 404 /remote/connect extensions Logger ToolipsRemote.Remote julia> using ToolipsRemote julia> connect("http://127.0.0.1:8000") login to toolips remote session ––––––––––––––––––––––––––––––––– user: root password for root: connection successful! ------------------------ REPL mode remote initialized. Press [ to enter and backspace to exit. "Prompt(\"🔗 root> \",...)"
Végül.
Az utolsó dolog, amit meg akartam tenni, az a tesztírás a RemoteConnection
oldalunkra. Ehhez egyszerűen lecserélem az 1. szintű funkciónkat az otthoni funkciónkra:
extensions = Vector{ServerExtension}([Logger(), Remote(Dict(1 => home))])
Most pedig térjünk haza, és írjunk néhány dolgot. Az argumentumhoz tartozó ::Connection annotációt AbstractConnection-vé is fogjuk tenni.
function home(c::AbstractConnection) mydivider = div("mydivider") push!(mydivider, h("helloworld", 1, text = "hello world!")) push!(mydivider, a("toolips-link", text = "toolips :)", href = "https://toolips.app")) write!(c, mydivider) end
Most kapcsolódjunk:
julia> connect("http://127.0.0.1:8000") login to toolips remote session ––––––––––––––––––––––––––––––––– user: root password for root: connection successful! ------------------------ REPL mode remote initialized. Press [ to enter and backspace to exit. "Prompt(\"🔗 root> \",...)" 🔗 root> f –-# hello world! toolips :) (https://toolips.app)–-
Szóval ez jól működött, de ehhez hozzá kell adnunk \ns
-et. Szükségünk van arra is, hogy a \ns
szó szerinti legyen a szerver oldalán, mivel a kliens leértékelési elemzője valószínűleg nem szó szerint értelmezi őket.
function write!(c::RemoteConnection, s::Component{:div}) write!(c, "---" * R"\n") [write!(c, child) for child in s[:children]] write!(c, "---" * R"\n") end write!(c::RemoteConnection, s::Component{:h1}) = write!(c, "# $(s[:text])" * R"\n") write!(c::RemoteConnection, s::Component{:h2}) = write!(c, "## $(s[:text]))" * R"\n") write!(c::RemoteConnection, s::Component{:h3}) = write!(c, "### $(s[:text])" * R"\n") write!(c::RemoteConnection, s::Component{:h4}) = write!(c, "#### $(s[:text])" * R"\n")
Próbáljuk ki ezt.
julia> connect("http://127.0.0.1:8000") login to toolips remote session ––––––––––––––––––––––––––––––––– user: root password for root: connection successful! ------------------------ REPL mode remote initialized. Press [ to enter and backspace to exit. "Prompt(\"🔗 root> \",...)" 🔗 root> f –-\n# hello world!\ntoolips :) (https://toolips.app)–-\n
Ó nem, ez nem működött. De ne aggódjunk, ezeket a \nket csak a csatlakoztatott repl függvényünkön belül cseréljük le!
function connected_repl(name::AbstractString, url::String, key::String) send_up(s::String) = begin r = post("$url/remote/connect", s * ":SESSIONKEY:$key") display(Markdown.parse(replace(r, R"\n" => "\n"))) end initrepl(send_up, prompt_text="🔗 $name> ", prompt_color = :cyan, start_key='[', mode_name="remote") end
Most…
🔗 root> a ──────────────────────────────────────────────────────────────────── hello world! ≡≡≡≡≡≡≡≡≡≡≡≡≡≡ toolips :) (https://toolips.app)–-
Igen, szóval a legtöbb dolog működik – nem biztos benne, hogy most mi a baj az a-val, vagy hogy a REPL így jeleníti-e meg ezt a fajta leértékelést. Ettől függetlenül elkalandozom, mivel itt nem az apró csípések a fő jellemzők! Mondanom sem kell, hogy ez az új frissítés nagyon félelmetes, és a toollips távirányítóval a befejezéshez közeledve örülök. Őrültség elképzelni, hogy ez a bővítmény hogyan teszi lehetővé a webhelyek ÉS a távoli példányok ugyanazon funkcióval történő kiszolgálását. Ezzel a példával továbbra is ellátogathatunk weboldalunkra…
… és ugyanazt a tartalmat kapja! Remélhetőleg mások is vonzónak találták ezt, és örülök, hogy végre létrehoztam azt, ami egyben az ÉN első kapcsolat-kiterjesztésem. Az eredmények nagyon jók! Ezt a csomagot a következő héten belül közzéteszik a Julia General Registry-ben. Köszönöm hogy elolvastad!