ToolipsUDP
provides high-level toolips-style functionality to UDP networking projects.
- Follows
Toolips
0.3
conventions. - Servers are modules.
- Extensible server framework.
- Streamlined UDP API.
using Pkg; Pkg.add("ToolipsUDP")
# create a new project from template:
using ToolipsUDP; ToolipsUDP.new_app(UDP, "MyApp")
module MyUDPExample
main = handler() do c::AbstractUDPConnection
if contains(c.packet, "emmy")
respond!(c, "you are indeed me!")
return
end
respond!(c, "you aren't me... ? How are you on my network? Who are you ?!")
end
# routes and extensions are loaded by exporting them. We will need `start!` and `UDP` to start the server.
export start!, UDP, main
end
# start your server
using MyUDPExample; start!(UDP, MyUDPExample)
# start with threads. Any minimum below `1` will recurring select the main-thread, allowing for more requests to be distributed to the main thread than the accompanying threads.
# (*similar to the router_threads argument from `Toolips`*)
# serves once on main thread, then 4 times on other threads before returning.
using MyUDPExample; start!(UDP, MyUDPExample, threads = 1:5)
# serves on the main thread 7 times, -5-1,then serves 7 times on threads before returning to -5.
using MyUDPExample; start!(UDP, MyUDPExample, threads = -5:8)
Note that for multi-threading you will want to alias your functions as an AbstractConnection
-- this also will not work with certain forms of send
.
# client
using ToolipsUDP
# send with no response.
send("127.0.0.1":7009, "hello, my name is emmy")
# send with response.
sock = send("127.0.0.1":7009, "hello, my name is emmy", keep_open = true)
close(sock)
The intention with ToolipsUDP
is to replicate the typical Toolips
web-development format in UDP
. The server system closely mirrors that of Toolips
itself:
# hello world in toolips (TCP HTTP Server)
module HelloWorld
using Toolips
home = route("/") do c::Connection
write!(c, "hello world!")
end
export start!, home
end # module
using HelloWorld: start!(HelloWorld)
# hello world in toolipsUDP (UDP Server)
module HelloUDP
using ToolipsUDP
home = handler() do c::UDPConnection
respond!(c, "hello world!")
end
end # module
using HelloUDP; start!(UDP, HelloUDP)
# toolips 0.3.4 +:
using HelloUDP; start!(UDP, HelloUDP)
note that we will provide UDP
to both start!
and new_app
The Toolips.Route
is replaced with the ToolipsUDP.handler
. To write a handler
, we use the handler
function as seen above. We can also provide a String
to this function to make a NamedHandler
, which is used by some extensions. The handler
(s) are stored in the AbstractUDPConnection.handlers
field. There is also the packet
and an ip
field.
?UDPConnection
The AbstractUDPConnection
fills the same role as the Toolips.AbstractConnection
--
being a mutable type that is passed into a response handler.
- See also:
UDPConnection
,UDPIOConnection
,start!
ip
::Stringport
::Int64packet
::Stringdata
::Dict{Symbol, Any}
ToolipsUDP
is also a bit more simplified; there is no 'default header' to process, and the only data we can receive is the packet itself and the IP of the client, so things are a bit more simplified.
The following functions are used to retrieve data from an AstractUDPConnection
.
Toolips.get_ip(c::UDPConnection)
ToolipsUDP.get_ip4(c::UDPConnection)
For accessing the data in the incoming packet, we simply utilize the c.packet
field.
We are able to respond, as well as send data, using the send
and respond
functions.
respond!(c::UDPConnection, data::String)
is the most essential, as it instantly sendsdata
back to the client.send(data::String, to::IP4 = "127.0.0.1":2000; from::Int64 = to.port - 5)
For sending a packet through a 'cursor', useful to test an initial response via a temporary server on the port infrom
, but note that it will not get a response as the server is immediately stopped after sending.send(c::UDPConnection, data::String, to::IP4 = "127.0.0.1":2000)
allows us to send data to any server, regardless as to whether or not it has sent to us or not or it is the current client, from ahandler
. A use-case for this would be multi-user chat, for example, where we want to call aFunction
on a certain client and not another. We store the IP of all clients alongside their names, when a client elects to send a user to the name we retrieve the associated IP and send the data.send(c::Module, data::String, to::IP4 = "127.0.0.1":2000)
is similar to sending data from ahandler
, but it allows us to send data from the REPL using the server.
UDP extensions are handled nearly identically to regular Toolips
extensions. First, we create a new type that is a <:
of AbstractUDPExtension
.
pages = Dict("page1" => "once upon a time", "page2" => "there was a person")
mutable struct MySampleExtension <: ToolipsUDP.UDPExtension
client_page::Dict{IP4, String}
config_path::String
MySampleExtension(uri::String) = new(Dict{IP4, String}(), uri)::MySampleExtension
end
Next, this extension may be bound to route!
and on_start
:
import ToolipsUDP: on_start, route!
function on_start(data::Dict{Symbol, Any}, ext::MySampleExtension)
raw_config = read(ext.config_path, String)
for client in split(raw_config, ";")
value_splits = split(client, "|")
ip4_splits = split(ip4_str[1], ":")
ip4 = string(ip4_splits[1]):parse(Int64, ip4_splits[2])
push!(ext.client_page, ip4 => string(value_splits[2]))
end
end
# use `false` to stop routing
function route!(c::UDPConnection, ext::MySampleExtension)
if get_ip4(c) in keys(ext.client_page)
respond!(c, "you're loaded on page " * ext.client_page[get_ip4(c)])
return(false)
end
end
Multi-threading is done by simply adding a range of threads to utilize. In this range, 1
represents your base thread -- anything about 1
will be served on an additional thread. Anything below 1
provided as the minimum
will perform an extra response on the base thread. In other words, 0:3
would serve twice on the base thread, 0
and 1
, before serving 2
and 3
on workers and returning to 0
and the base thread. Note that both a UDPIOConnection
and a UDPConnection
will be sent through a multi-threaded server's handlers, so this must be annotated as an AbstractUDPConnection
.
module MultiThreadedServer
using ToolipsUDP
count = 0
main = handler() do c::AbstractUDPConnection
global count += 1
end
export main, UDP, start!
end # module MultiThreadedServer
using MultiThreadedServer
start!(UDP, MultiThreadedServer, threads = -1:5)
The ProcessManager
is also, like Toolips
, the return of start!
. Considering this, we could feasibly add workers and distribute our tasks -- though threads
and router_threads
aren't both available as they are in Toolips
.
While this aspect of multi-threading is relatively straightforward, not all servers will be compatible with this form of multi-threading. For starters, your handlers will need to be annotated as AbstractUDPConnection
, rather than a regular UDPConnection
-- as a different Connection
type is used when not on the base thread.
Downsides to multi-threading with ToolipsUDP
:
- You cannot use
send
-- in the future, there might be a place to store sent data inside of aUDPIOConnection
, for now this is not a reality and theUDPIOConnection
is relegated exclusively torespond!
for sending data. In other words, we can send data back to the client but not really anywhere else. - A multi-threaded project MUST BE an established project with its own environment, modules created under
Main
or in the REPL will not be able to load on additional threads. - Inevitably, loading your server across several threads leads to a higher memory cost. The
threads
argument allows you to balance the performance of the threads with the loss in performance that occurs from translating data into each thread. - Every
Handler
function argument must be annotated as anAbstractUDPConnection
, or have no annotation at all.
You can help out with this project by...
- using
ToolipsUDP
in your own project 🌷 - creating extensions for the toolips ecosystem 💐
- forking this project contributing guidelines
- submitting issues
- contributing to other chifi projects
- supporting chifi creators
I thank you for all of your help with our project, or just for considering contributing! I want to stress further that we are not picky -- allowing us all to express ourselves in different ways is part of the key methodology behind the entire chifi ecosystem. Feel free to contribute, we would love to see your art! Issues marked with good first issue
might be a great place to start!
We are not super strict, but making sure of these few things will be helpful for maintainers!
- You have replicated the issue on Unstable
- The issue does not currently exist... or does not have a planned implementation different to your own. In these cases, please collaborate on the issue, express your idea and we will select the best choice.
- Pull Request TO UNSTABLE
- Be specific about your issue -- if you are experiencing multiple issues, open multiple issues. It is better to have a high quantity of issues that specifically describe things than a low quantity of issues that describe multiple things.
- If you have a new issue, open a new issue. It is not best to comment your issue under an unrelated issue; even a case where you are experiencing that issue, if you want to mention another issue, open a new issue.
- Questions are fine, but preferably not questions answered inside of this
README
.