Client operations
Client operations involve querying data from one or several XPA servers or sending data to one or several XPA servers.
Persistent client connection
To avoid reconnecting to the XPA server for each client request, XPA.jl
maintains a per-task persistent connection to the server. The end-user should therefore not have to worry about creating persistent XPA client connections by calling XPA.Client()
for its application. Persistent XPA client connections are automatically shutdown and related resources freed when tasks are garbage collected. The close()
method can be applied to a persistent XPA client connection (if this is done for one of the memorized per-task connection, the connection will be automatically re-open if necessary). If needed, XPA.connection()
yields the persistent XPA client of the calling task.
Identifying XPA servers
The utility XPA.list
can be called to get a list of running XPA servers:
julia> XPA.list()
2-element Vector{XPA.AccessPoint}:
XPA.AccessPoint(class="DS9", name="ds9", address="/tmp/.xpa/DS9_ds9-8.7b1.17760", user="eric", access="gs")
XPA.AccessPoint(class="DS9", name="ds9", address="7f000001:43881", user="eric", access="gs")
indicates that two XPA servers are available and that both are SAOImage-DS9, an astronomical tool to display images, the first one is using a Unix socket connection, the 2nd one an internet socket. The identities of XPA servers is the string $class:$name
which can be matched by a template like $class:*
. In this case, both servers are identified by "DS9:ds9"
and matched by "DS9:*"
, to distinguish them, their address (which is unique) must be used. Using the address is thus the recommended way to identify a unique XPA server.
XPA.list
may be called with a predicate function to filter the list of servers. This function is called with each XPA.AccessPoint
of the running XPA servers and shall return a Boolean to indicate whether the server is to be selected. For example, using the do
-block syntax:
julia> apts = XPA.list() do apt
apt.class == "DS9" && startswith(apt.address, "/")
end
1-element Vector{XPA.AccessPoint}:
XPA.AccessPoint(class="DS9", name="ds9-8.7b1", address="/tmp/.xpa/DS9_ds9-8.7b1.17760", user="eric", access="gs")
lists the SAOImage/DS9 servers with a Unix socket connection while:
julia> apts = XPA.list() do apt
apt.class == "DS9" && startswith(apt.address, r"[0-9a-fA-F]")
end
1-element Vector{XPA.AccessPoint}:
XPA.AccessPoint(class="DS9", name="ds9-8.7b1", address="7f000001:43881", user="eric", access="gs")
lists the SAOImage/DS9 servers with an internet socket connection. The method
keyword may also be used to choose a specific connection type. See XPA.list
documentation for more details and for other keywords.
In order to get the address of a unique XPA server, you may call XPA.find
with a predicate function to filter the matching servers and a selection method to keep a single one among all matching servers. For example:
julia> apt = XPA.find(; select=first) do apt
apt.class == "DS9" && startswith(apt.name, "ds9")
end
XPA.AccessPoint(class="DS9", name="ds9-8.7b1", address="/tmp/.xpa/DS9_ds9-8.7b1.17760", user="eric", access="gs")
The select
keyword may be a function (as above) or a symbol such as :interact
to have an interactive menu for the user to choose one of the servers if there are more than one matching servers:
julia> apt = XPA.find(; select=:interact) do apt
apt.class == "DS9"
end
Please select one of:
> (none)
DS9:ds9-8.7b1 [address="/tmp/.xpa/DS9_ds9-8.7b1.17760", user="eric"]
DS9:ds9-8.7b1 [address="7f000001:43881", user="eric"]
If there are no matching servers, XPA.find
returns nothing
unless the throwerrors
keyword is true
to throw an exception if no match is found. If there are more than one matching servers and no select
method is specified or if it is not :interact
, XPA.find
throws an error.
The address of an XPA.AccessPoint
instance apt
is given by apt.address
. See the documentation of XPA.AccessPoint
for other properties of apt
that can be used in the filter and select functions.
Getting data from one or more servers
Available methods
To query something from one or more XPA servers, call the XPA.get
method:
XPA.get([conn,] apt, args...) -> rep
which uses the persistent client connection conn
to retrieve data from one or more XPA access-points identified by apt
as a result of the command build from arguments args...
. Argument conn
is optional, if it is not specified, a per-task persistent connection is used. The XPA access-point apt
is an instance of XPA.AccessPoint
or a string which can be a template name, a host:port
string or the path to a Unix socket file. The arguments args...
are converted into a single command string where the elements of args...
are separated by a single space.
For example, to query the version number of up to 5 running SAOImage-DS9 servers:
julia> rep = XPA.get("DS9:*", "version"; nmax=5)
XPA.Reply (2 answers):
1: server = "DS9:ds9-8.7b1 7f000001:43881", message = "", data = "ds9-8.7b1 8.7b1\n"
2: server = "DS9:ds9-8.7b1 7f000001:36785", message = "", data = "ds9-8.7b1 8.7b1\n"
For best performances or to make sure to receive answers from a single server, a unique server address shall be used, not a template as above.
The answer, bound to variable rep
in the above example, to the XPA.get
request is an instance of XPA.Reply
which is an abstract vector of answer(s). To access the different parts of the i
-th answer, use its properties. Property rep[i].server
yields the identifier and address of the server who sent the answer. Properties rep[i].has_message
and rep[i].has_error
indicate whether rep[i]
has an associated message, respectively a normal one or an error one, which is given by rep[i].message
(see the Messages section below). For example:
julia> rep[1].server
"DS9:ds9-8.7b1 7f000001:43881"
julia> rep[1].has_message
false
julia> rep[1].has_error
false
julia> rep[1].message
""
Usually the most interesting part of a particular answer is its data part and property rep[i].data
is a callable object to access such data with the following syntax:
rep[i].data() # a vector of bytes
rep[i].data(String) # an ASCII string
rep[i].data(T) # a value of type `T`
rep[i].data(Vector{T}) # the largest possible vector with elements of type `T`
rep[i].data(Array{T}, dims...) # an array of element type `T` and size `dims...`
For example:
julia> rep[1].data(String)
"ds9-8.7b1 8.7b1\n"
See the documentation of XPA.Reply
for more details.
To avoid checking for errors for every answer to an XPA request, XPA.has_errors(rep)
yields whether any of the answers in rep
has an error. Otherwise, the XPA.get
method has a throwerrors
keyword that can be set true
in order to automatically throw an exception if there are any errors in the answers.
The syntax rep[]
can be used to index the unique answer in rep
throwing an error if length(rep) != 1
. If you are only interested in the data associated to a single answer, you may thus do:
XPA.get(apt, args...)[].data(T, dims...)
This is so common that the same result is obtained by directly specifying T
and, optionally, dims
as the leading arguments of a XPA.get
call:
XPA.get(T, apt, args...)
XPA.get(T, dims, apt, args...)
In this context, exactly one answer and no errors are expected from the request (as if nmax=1
and throwerrors=true
were specified) and dims
, if specified, must be a single integer or a tuple of integers.
Examples
The following examples assume that apt
is the access-point or the unique address of a SAOImage-DS9 server. For instance:
using XPA
apt = XPA.find(apt -> apt.class == "DS9"; select=:interact)
To retrieve the version as a string:
julia> XPA.get(String, apt, "version")
"ds9-8.7b1 8.7b1\n"
To retrieve the about answer as (non-empty) lines:
julia> split(XPA.get(String, apt, "about"), r"\n|\r\n?"; keepempty=false)
10-element Vector{SubString{String}}:
"SAOImageDS9"
"Version 8.7b1"
"Authors"
"William Joye (Smithsonian Astrophysical Observatory)"
"Eric Mandel (Smithsonian Astrophysical Observatory)"
"Steve Murray (Smithsonian Astrophysical Observatory)"
"Development funding"
"NASA's Applied Information Systems Research Program (NASA/ETSO)"
"Chandra X-ray Science Center (CXC)"
"High Energy Astrophysics Science Archive Center (NASA/HEASARC)"
To retrieve the bits-per-pixel and the dimensions of the current image:
bitpix = parse(Int, XPA.get(String, apt, "fits bitpix"))
dims = map(s -> parse(Int, s), split(XPA.get(String, apt, "fits size"); keepempty=false))
Sending data to one or more servers
The XPA.set
method is called to send a command and some optional data to a server. The general syntax is:
XPA.set([conn,] apt, args...; data=nothing) -> rep
which sends data
to one or more XPA access-points identified by apt
with arguments args...
. As with XPA.get
, arguments args...
are converted into a string with a single space to separate them and the result rep
is an abstract vector of answer(s) stored by an instance of XPA.Reply
. The XPA.set
method accepts the same keywords as XPA.get
plus the data
keyword used to specify the data to send to the server(s). The value of data
may be nothing
if there is no data to send (this is the default). Otherwise, the value of data
may be an array, or an ASCII string. Arrays are sent as binary data, if the array data
does not have contiguous elements (that is not a dense array), it is converted to an Array
.
As an example, here is how to make SAOImage-DS9 server to quit:
XPA.set(apt, "quit");
Messages
If not empty, message strings associated with XPA answers are of the form:
XPA$ERROR message (class:name ip:port)
or
XPA$MESSAGE message (class:name ip:port)
depending whether an error or an informative message has been set. When a message indicates an error, the corresponding data buffers may or may not be empty, depending on the particularities of the server.