NAF (Network Application Framework) is a Java API that implements a reactive event-driven framework based on the Java JDK's NIO interface.
It is free and open-source, distributed under the terms of the GNU Affero General Public License, Version 3 (AGPLv3).
NAF is intended to be easy to use and does not provide gratuitous layers of abstraction (which force you to learn excessively large custom APIs) over the familiar JDK interfaces that already do what most people need.
What it does, is turn the raw NIO Selector and Channel interfaces into a functioning multiplexor based on the Reactor comms pattern, that performs the I/O ops for you, calls your code with the received data, and then gets out of the way to let your application handle the event.
There are no data transformation pipelines, NAF presents received data as raw bytes and you can transmit as either byte buffers or NIO ByteByffer objects. The intention is to provide a frictionless low-level interface, that simplifies your code without imposing any extra processing overhead.
NAF transparently handles non-blocking writes as you would expect, lets you schedule an unlimited number of arbitrary timed ops (and cancel or reschedule them before they trigger) as you would hope and also lets you specify whether reads should return after a fixed number of bytes, on receipt of a marker char (eg. a LineFeed) or just as soon as any data is available.
NAF also provides the possibly unique feature of a non-blocking DNS-resolver API, which lets you perform hostname (A and AAAA), reverse-IP (PTR), MX (mailserver), NS (nameserver), SOA, SRV and TXT lookups.
A more conventional blocking DNS-resolver API with all the same functionality is also provided as a convenience for non-NAF applications, ie. applications which have not been written for the NAF framework, but would appreciate having a utility API to perform low-level DNS queries.
In more detail, a NAF application (more precisely termed a "NAFlet", or sometimes a "NAF task") executes under the control of a single-threaded object called a Dispatcher, which acts as an NIO controller and invokes registered callbacks in your application, as triggered by I/O, Timer and DNS events.
As well as simplifying the code, the single-threaded reactor mode of operation permits many optimisations, not least of which is pre-allocating many of the temporary objects that your code might require, so as to reduce memory churn for the GC.
An overview of the main NAF components follows.
To find code-level documentation, you are urged to go to the example applications provided (see section §12 below), along with the API Reference (which is included in the NAF download), and of course the NAF source code itself, which is freely available.
<dependency> <groupId>com.github.greysoft.naf</groupId> <artifactId>greynaf</artifactId> <version>2.5.0</version> </dependency>
NAF applications are config-driven, and are specified in an XML-based configuration file which is generically referred to as the
file (though it is not necessarily named that).
This config file defines the application's Dispatchers and provides a mechanism for automatically wiring in and launching all your application code without you having to provide the main() method (and without the use of custom annotations).
The top-level structure of a naf.xml file is outlined below.
<naf> <dirpaths> <root>.</root> <config>%DIRTOP%/conf</config> <var>%DIRTOP%/var</var> <logs>%DIRVAR%/logs</logs> <tmp>%DIRVAR%/tmp</tmp> </dirpaths> <baseport>13000</baseport> <dependjars>app1.jar : app2.jar</dependjars> <nafman> ... </nafman> <dnsresolver> ... </dnsresolver> <dispatchers> ... </dispatchers> </naf>
The naf.xml header config elements are as follows:
This section defines directory paths that a typical NAF application might use. The entire section is optional, since all the paths have default values that are as explicitly defined above.
For each path, there is an associated system-property that is looked up if the naf.xml config element is absent, and the default value kicks in if the system property isn't defined either.
Thereafter, the value of the path is used to substitute token strings to generate a concrete path.
The use of some replacement tokens is illustrated above. These substitutions are automatically performed on all naf.xml paths, and applications can also dynamically perform them on arbitrary strings by calling com.grey.naf.Config.getPath(String path, Class> clss) (the clss param is typically null).
The matrix of configuration settings for these paths is specified in the table below:
|Config Element||System Property||Default Value||Replacement Token|
The paths are actually defined in terms of each other, so the full derivation is as shown in the naf.xml listing above, and the defaults in the above table are what would result if no config items or system properties are defined.
If the grey.paths.tmp system property is set, then NAF's temp path will default to the global value specified by com.grey.base.config.SysProps.TMPDIR, else it will diverge. It's up to the application which setting it wishes to abide by.
If the grey.logger.dir system property is not set, then NAF will set it to the resolved value of its dirpaths/logs config item, and this ought to propagate to the underlying GreyLog logger, so long as you loaded naf.xml before creating any loggers.
This optional setting specifies the base port number above which NAF applications allocate the range of TCP ports they may use - whether purely internally or advertised externally. The actual number of reserved TCP ports depends on the NAF application, but it will be a contiguous block starting this base port.
If this config item is absent, the setting is obtained from the greynaf.baseport system property, and if that is also absent, the ultimate default is 13000.
A colon-separated list of dependent JAR files to dynamically load.
Some of the list items may be directory paths rather than a JAR pathname, and if so, then:
- If directory spec ends in / (forward slash): NAF loads any .jar files found in that directory.
- Else, the directory itself is placed on the live classpath.
Note that the greynaf.cp system property can be used as an alternative to the dependjars config element.
Their syntax and treatment is identical, but the system property is evaluated a fraction earlier during application startup.
It is a matter of personal preference as to whether you would prefer to specify extra JARs in the naf.xml file or via system properties.
Dispatchers are the event multiplexers at the heart of the NAF framework, and each dispatcher resides in its own thread.
Multiple Dispatchers can exist within one JVM process, and they are completely independent of each other.
In a widely used terminology, each Dispatcher represents an event loop.
Users can simply regard a Dispatchers as a single-threaded execution context within which their applications reside. Dispatchers monitor all events (eg. I/O) in which their applications have registered an interest, and they also provide built-in services such as DNS resolution, NAFMAN and logging.
An illustrative dispatchers config block is shown below, and as can be seen, each individual dispatcher section consists of nothing more than one or more NAFlets, and a set of attributes controlling the dispatcher's own behaviour.
The top-level config outlined in section §2 above shows where this block fits into the overall naf.xml config file.
<dispatchers> <dispatcher name="dispatcher1"> <naflets> <naflet name="naflet1a" class="com.my.package.Task1a"> ... </naflet> <naflet name="naflet1b" class="com.my.package2.Task1b"> ... </naflet> </naflets> </dispatcher> <dispatcher name="dispatcher2"> <naflets> <naflet name="naflet2" class="com.my.package2.Task2"> <configfile root="xpath">%DIRCONF%naflet2.xml</configfile> </naflet> </naflets> </dispatcher> </dispatchers>
The config block above illustrates the Dispatcher
attribute, and a description of all the other Dispatcher attributes (specified in same manner) follows.
The com.grey.naf.DispatcherDef class is where these attributes are parsed, so refer to the source for the full details.
As explained in the Overview section (see EchoBot), Dispatchers can be either be specified in a naf.xml file, in which case the NAF launcher automatically creates and runs them, or they can be programmatically created and started.
The latter are termed dynamic (or programmatically created) Dispatchers, as opposed to the configured ones created via naf.xml. The distinction is in their startup mode, and once launched, there is no difference in the resulting Dispatchers.
Dispatchers are dynamically created by either of these static methods:
• com.grey.naf.Dispatcher.create(com.grey.naf.DispatcherDef, com.grey.naf.Config, com.grey.logging.Logger)
• com.grey.naf.Dispatcher.create(com.grey.naf.DispatcherDef, int baseport, com.grey.logging.Logger)
Once they have been created and set up, you simply call their start() method.
Each NAF dispatcher executes one or more application entities, known as
Whereas Dispatchers are event managers which NAF provides for you, NAFlets embody application-specific functionality which you have to code.
Naflets are independent processing tasks which are unaware of other Naflets executing within the same dispatcher, and can be freely moved between dispatchers, according to performance-tuning considerations.
When we say that NAF is a single-threaded framework, what we really mean is that individual NAFlets execute in a single-threaded context (ie. the Dispatcher). However, real-world applications may comprise multiple NAFlets, which in turn may or may not be distributed over multiple Dispatchers (ie. multiple threads). The important point is that the Dispatchers are independent of one another and co-exist without incurring any synchronisation overhead, it's ultimately up to the developer how closely coupled their own bespoke NAFlets are, and whether they require synchronisation across multiple threads.
The NAFlet config block is illustrated in the Dispatchers section above, and there are 3 top-level attributes.
The inner elements of a naflet block are application-specific and are parsed by the bespoke NAFlet code, but there is one generic setting which is understood and actioned by the NAF core:
The Dispatcher will call the NAFlet's
method from within its own thread (so it must not have been called already) and the NAFlet would then be live.
This method can be used to load new NAFlets whether the Dispatcher was originally launched from a naf.xml file or programmatically.
The Dispatcher argument indicates which Dispatcher the calling code is associated with, and would be set to Null if it's not running within a Dispatcher. If you are calling loadNaflet() on a Dispatcher which you have just created programmatically but not yet started, then you should pass in null as the Dispatcher reference, and it will load the NAFlet once it starts up.
There is also an unloadNaflet(String naflet_name, com.grey.naf.Dispatcher) method, which tells the Dispatcher to stop the NAFlet of that name, if it's running.
NAFMAN is the NAF management agent. It is web-enabled, and enables you to issue commands to a running NAF application from your browser or the command line.
A NAFMAN agent is embedded in every dispatcher by default, and the first NAFMAN-enabled Dispatcher becomes the Primary NAFMAN agent, all the others (if any) becoming its Secondaries.
The nafman config block is typically absent from the naf.xml config file, but that does not mean NAFMAN is disabled, merely that it's operating with its default settings. NAFMAN can only be disabled on a per-Dispatcher basis, via the Dispatcher's nafman attribute.
The structure of the config block is illustrated below, but you should rarely need to modify it as they are more a case of low-level tuning than anything else.
See the com.grey.naf.nafman.Server source code for all the options - plus the usual Listener attributes.
<nafman> <listener> <server> ... see NAFMAN source ... </server> </listener> </nafman>
The most likely option you would want to change is the TCP port on which the NAFMAN server listens, which by default is the same as the
baseport (see §2 above), ie. 13000.
However, if you do want to change this port, it should be done via the baseport config setting, NOT the Listener's port attribute. That way, NAF continues to reserve a contiguous block of ports that is specified in one place.
The upshot of the above is that (notwithstanding any changes to the baseport) if you point your browser at port 13000, you will see the NAFMAN home page, where you can issue various NAFMAN commands and navigate to other screens.
NAF contains a built-in NAFMAN home page called the NAF Dashboard, but this can be overridden by NAF-based applications (eg. Mailismus installs its own bespoke home page).
NAFMAN's capabilities are best understood by viewing it on your browser, but the available control points include stopping NAF as a whole, or stopping individual Dispatchers or NAFlets, as well as monitoring the state of all the Dispatchers (you can view all current I/O channels/connections and timers - and even kill TCP connections).
Assorted other NAF and application commands are also available, as well as the ability to inspect NAFMAN's own internal state, eg. which components have registered to handle which commands.
Since NAFMAN command handlers are dynamically registered, the exact set of commands available will depend on your Dispatcher config and active NAFlets.
NAFMAN commands can also issued from the command line, by entering the Path and Query-String part of the URL (ie. the URL mimnus the host-port part) as the argument to the NAF Launcher's -cmd option
For example, the following command-line invocations list all the available NAFMAN commands. The second form illustrates how to connect to an arbitrary instance of NAFMAN, rather than the one associated with a particular naf.xml file.
As a web-based agent, NAFMAN returns a standard HTTP response even when invoked from the command line (in fact internally, this issues a HTTP request and forwards the response) and the body of the response is in general an XML document (although even that depends on the particular handler for that command).
Some of the more common NAFMAN options are:
Putting it all together, the following command-line invocation can be used to terminate a named NAFlet within a named Dispatcher.
(NB: On a Unix platform, you will need to escape the ampersand using the usual Shell syntax)
See section §13 below for more general info on command-line usage.
The DNS Resolver is a NAF library component that offers a non-blocking (aka asychronous) API to NAF applications and other NAF components.
It supports A, AAAA, PTR, NS, SOA, MX, TXT and SRV lookups, and can work in recursive or non-recursive mode.
The Resolver caches the results so that future lookups on the same domain can be satisfied internally within NAF, without recourse to any more external queries. It also caches negative answers (ie. domain name not found).
The API is provided by the com.grey.naf.dns.Resolver class. Any application wishing to use the DNS resolver must implement the com.grey.naf.dns.Resolver.Client interface, and the DNS resolver calls back to its dnsResolved() method with the final result.
The final result is represented by an instance of the com.grey.naf.dns.Answer class, within which individual Resource Records are represented by the com.grey.naf.dns.ResourceData class.
If the answer was already cached, then it is returned by the Resolver API call, and there is no subsequent callback.
NAF offers two alternative implementations of the DNS Resolver, namely the Embedded Resolver and the Distributed Resolver.
So the trade-off between the embedded resolver and the distributed clients is one of nominal speed versus memory consumption.
However the overhead of the inter-thread communication is expected to have a negligible effect on overall throughput, and would be more than offset by the potential for less cache misses on the larger shared cache, so we recommend the distributed resolver.
Despite the terminology, both resolvers are obviously embedded in your application, but the so-called "Embedded" one has an independent instance embedded in every Dispatcher thread.
Note that the difference between the various embedded and distributed resolvers is internal to the DNS resolver, and the application code within each Dispatcher accesses the resolver via a single API, represented by the Dispatcher.dnsresolv field, which is an instance of the com.grey.naf.dns.Resolver class.
Application code is unaware what underlying type of DNS resolver it is calling, and it has no effect on the final answer.
Although each Dispatcher thread contains its own asynchronous (non-blocking) DNS resolver, they're all controlled by one dnsresolver config block at the top level of the naf.xml file.
This config block consists entirely of optional parameters, and may be absent from the config file if you don't want to modify its defaults, but its absence simply means that those defaults will be in effect, not that DNS lookup is disabled. A resolver will still be created in all dispatchers that have their dns attribute set to Yes.
The DNS config attributes are shown below.
<dnsresolver class="com.grey.naf.dns.embedded.EmbeddedResolver" recursive="N" validate_response_ip="N" nonbailiwick_glue="Y" partialprune="N" exitdump="Y" minttl_initial="5m" minttl_lookup="1m" negttl="1h"> <localservers>192.168.1.12 | 192.168.1.13</localservers> <udpsockets>1</udpsockets> <rootservers auto="Y">/path/to/file</rootservers> <retry timeout="10s" backoff="3s" max="3"/> <cache_a hiwater="2000" lowater="1000"/> <cache_ptr hiwater="1000" lowater="500"/> <cache_soa hiwater="1000" lowater="500"/> <cache_ns hiwater="2000" lowater="1000" maxrr="5"/> <cache_mx hiwater="1000" lowater="500" maxrr="5"/> </dnsresolver>
wget --user=ftp --password=ftp ftp://ftp.rs.internic.net/domain/db.cache
curl -u ftp:ftp ftp://ftp.rs.internic.net/domain/db.cache
The Resolver-API described above is an asynchronous one based on callbacks, and is intended to support NAF applications, ie. applications that are embedded within the NAF framework. The open-source Batch-Resolver described in section §11 below provides an example.
However, NAF also offers a synchronous Resolver API as a convenience for non-NAF applications, which consists of blocking calls to retrieve all the same info as returned by the asynchronous API.
The ability to make simple blocking method calls means the synchronous Resolver API is a toolkit API available to applications of any stripe, rather than a framework API (like the asynchronous one) which requires your application to be structured in a particular manner.
This API is provided by the com.grey.naf.dns.synchronous.SynchronousResolver class, and it returns its results in the form of the same com.grey.naf.dns.Answer class as the asynchronous API.
SynchronousResolver class launches a Dispatcher thread in the background and uses the asynchronous API behind the scenes.
It offers constructors which let you either instantiate the asynchronous Resolver with its default settings, or allow you to supply a
to customise any settings you require.
SynchronousResolver class is MT-safe (thread-safe) so the recommended usage is to create a single instance which is shared amongst all the caller threads in your application, to avoid launching excess Dispatcher threads.
The Listener is a generic NAF component that crops up in many scenarios.
It is a functional building block that allows you to listen on a specified TCP port and serve incoming connections with a specified server class.
It can be created either programmatically or by a naf.xml config file. While the programmatic mode allows you to create concurrent or iterative servers, the config mode only supports concurrent.
The Listener config block can be specified in arbitrary locations within the NAFlet config, and takes the form shown below:
<listener name="webserver" port="80" interface="127.0.0.1" maxservers="1000" backlog="5000"> <ssl ... /> <server factory="com.grey.http.Server$Factory"> ... </server> </listener>
The Listener's attributes are as follows:
Note that setting a high Listener backlog (or at least expecting it to be honoured!) and supporting high TCP/IP concurrency levels in general will probably require tuning of the underlying OS as well.
It is beyond the scope of this guide to make such sysadmin recommendations, but on Linux, the key settings include net.core.somaxconn, net.core.netdev_max_backlog, net.ipv4.tcp_max_syn_backlog and fs.file-max.
You can display the current settings with sysctl -A, add them to /etc/sysctl.conf for permanent effect and reload with sysctl -p or set them temporarily (till next reboot) with commands like. sysctl -w net.core.somaxconn=8192
The contents of the server config block depend on the particular server, but it's top-level attributes are as follows:
Note that the Listener does not instantiate a Server for every incoming connection, as it stores spare server objects on an ObjectWell
(see com.grey.base.utils.ObjectWell) for reuse, and will soon reach a steady state where it always has spare servers in reserve.
example app is a good example of how this works, including the use of the prototype factory.
There is one further variation to consider:
Listener config blocks may exist as a sequence, and you may want Listeners on different interfaces or even ports to share the same Server config.
This can be achieved by the use of an extra Listener attribute called configlink, which points at the config block of another named listener, as illustrated below.
<listeners> <listener name="webserver" port="80" interface="127.0.0.1" iterative="N" backlog="100"> <server factory="com.grey.http.Server$Factory"> ... </server> </listener> <listener name="webserver_alt" port="8080" interface="127.0.0.1" configlink="webserver"/> </listeners>
The above XML block configures Listener webserver_alt on port 8080 to use the same server config as the first Listener on port 80.
NAF provides full fledged support for SSL connections in non-blocking mode. This includes client and server mode, client certificates, and the ability to switch to SSL after starting a connection (having exchanged unencrypted data).
The com.grey.naf.reactor.SSLConnection and com.grey.naf.SSLConfig classes provide the core of the SSL functionality with the latter providing the means to configure the SSL settings. This can be done programatically via the SSLConfig.Def class, or via an XML config block which gets parsed into an SSLConfig.def instance - see SSLConfig.create().
A NAF class which engages in a connection is always a subclass of com.grey.naf.reactor.ChannelMonitor and it tells ChannelMomitor whether it requires an SSL connection by means of the abstract ChannelMonitor.getSSLConfig() method.
If you override this method to return a non-null value, then ChannelMonitor will act accordingly and make an SSL connection when you call the ChannelMonitor.connect() method.
NAF servers inherit their SSL settings from NAF's built-in Listener, and it's SSL capabilities are also controlled by the same SSLConfig class.
The available settings are probably best described by going through the XML config node. It need not necessarily be called "ssl", as it is the attributes that are read by SSLConfig.
<ssl cert="my-cert-id" kspath="keystore-path" certpass="mypass" peercert="peer-cert-name" tspath="truststore-path" latent="N" mandatory="N" clientauth="1"/>
See the source code for more details, and the SSLConnectionTest unit test provides an example of how to create SSL clients and servers based on the above settings.
NAF logging is based on the
logging framework, which is included in the NAF downloads (both source and binary).
See the GreyLog Guide for how to use it.
Each Dispatcher has its own logger, which is made available to its call-out code via the public Dispatcher.logger field.
As a developer of NAF applications, your main concern is to provide GreyLog's logging.xml runtime config file, and NAF will do the rest.
is a term that crops up frequently in the
code and comments.
It is a sub-project within NAF and constitutes one of the JAR files in the NAF distribution.
Broadly speaking however, NAF is considered to consist of all these JARs (including the logging ones - see §9), not just the one that happens to be called greynaf.jar, so GreyBase is simply a physical subdivision within NAF.
The dividing line between the GreyBase and NAF sub-projects is that NAF is the framework library and can only be used by NAF applications (indeed, using it is what defines a NAF application). GreyBase on the other hand is a utility API, which contains classes that are not intrinsically tied to NAF and can be used by any application in any context.
Notwithstanding that, GreyBase has evolved to serve the needs of NAF applications and will remain focussed on NAF's particular concerns and optimisations, such as object reuse (see ObjectWell), interpreting byte streams as 8-bit text (a valid assumption for most Internet protocols, including HTTP, FTP & SMTP headers - see ByteChars) and avoiding incidental garbage generation (see HashedList and friends).
GreyBase includes the following:
• SASL: Server and client mode support for Plain, CRAM-MD5 and External
• Base64 and Hex encoding and decoding
• Some basic utilities for symmetric and assymetric encryption
• Hashed maps that support primitives Ints as key or data, without autoboxing to the Integer class
• Utilities for parsing XML config files
DNS Batch Resolver
is a built-in NAF application (technically a standalone NAFlet) that takes an input file containing a list of domain names and issues DNS queries to resolve them, writing out the answers. It is capable of rapidly resolving very large datasets.
Although it also serves as an illustration of NAF's DNS API (see the com.grey.naf.dns.batchresolver package) it is not intended as merely a sample app like the ones in section §12, but is a fully functional application and is contained within the NAF JAR itself.
Assuming you have downloaded and extracted the binary NAF distribution (or built your own and extracted the resulting ZIP/Tar file), you would run it as follows:
config file as shipped specifies an input file called
and an output file called
The above command will also pick up the logger definition labelled dnsbatch in the provided conf/logging.xml logging config file (the logging.xml file might be under the samples sub-directory initially, in which case just copy it to conf).
The input file should contain one record per line, but the type of the records depends on the dnstype setting in the batchresolver.xml config file.
• A: Hostname in FQDN (Fully Qualified Domain Name) form - gets resolved to IPv4 address
• AAAA: Hostname in FQDN (Fully Qualified Domain Name) form - gets resolved to IPv6 address
• PTR: Dotted IPv4 address - resolved to hostname
• NS: DNS domain name in FQDN form - resolved to the hostnames and addresses of the domain's name servers
• SOA: DNS domain name in FQDN form - resolved to the domain's SOA data
• MX: Email domain (mailbox part will be stripped if present) in FQDN form - resolved to hostnames and addresses of the domain's mail relays
• SRV: A service specification in the standard SRV
_service._proto.name. notation - resolved to hostnames and addresses of its servers.
• TXT: DNS domain name in FQDN form - resolved to the domain's SRV data string
The remaining config items are all set to appropriate defaults, and the source code can be viewed in the constructor of the BatchResolver class, but the most interesting ones are:
A Y/N field determining whether we merely report the status (OK, NODOMAIN, etc) of each query in the output file, or the full set of Answer RRs (DNS Resource Records)
The max number of simultaneous DNS requests that can be outstanding, before the batch resolver pauses.
It defaults to zero meaning no limit, but if a limit is specified, the batch resolver pauses until the pending requests fall to maxpending_lowater.
If maxpending is specified, this defaults to the greater of half its value, or maxpending minus 20.
In terms of the standard dnsresolver attributes (see section §6), you will note that the batchresolver.xml comes with recursive set to No, which is recommended for large batches as it could well swamp a slow local DNS server.
The exitdump attribute is also set, meaning that at the end, the batch resolver will dump the final state of its cache to var/DNSdump-dnsbatch.txt.
In addition to the source code and Javadoc-generated
NAF also provides 3 sample apps (both in pre-built form in the binary download and in source form in the published NAF source tree) that demonstrate its main modes of operation.
They also demonstrate how little Java code you need to write, to build relatively complex NAF apps.
When NAF starts up, it loads system properties from a
file which it looks for it on the following search path:
• If the grey.properties system property is set, then it specifies the pathname.
• ./grey.properties (ie. look in current directory)
If no grey.properties file is found or the grey.properties system property is set to "-", then it is simply not loaded.
The file itself is a standard Java properties file, consisting of name=value lines.
See the source code of the com.grey.naf.Launcher class, for more detail on the startup processing and options.
The portfwd demo app provides an example of how an application that is based on a naf.xml config file might pass its main() method directly onto NAF.
It's com.grey.portfwd.Task.main() method does no more than load its own bespoke NAFMAN command, and while this demo app is running, its NAFMAN commands can be accessed either by pointing your browser at the NAFMAN home page (see section §5 above), where the commands are auto-published, or via the command-line as follows:
demo app illustrates the behaviour of an application that doesn't use a naf.xml file, but it still leverages NAF's command-line parsing facilities.
As you can see, com.grey.echobot.App subclasses NAF's com.grey.naf.Launcher class, and contains the EchoBot's main() which instantiates the Launcher subclass and then passes control to it.
The EchoBot's App class defines a subclass of com.grey.base.utils.CommandParser.OptionsHandler to define its own command-line options (on top of those understood by NAF), which it loads in its constructor.
Because the App class overrides the Launcher's appExec() method, NAF passes control back to it after parsing the command-line, and the EchoBot takes over from there.
Run NAF directly with the -h option to see its built-in options.
NAF would normally be loaded by your own application rather than executed directly, not least of the reasons being that the application-specific classpath you require will be set in your application's JAR (or enclosing framework), rather than NAF.
As noted in section §2 above though, you could specify all the JARs your application requires in the dependjars config item or the greynaf.cp system property, and if so, you could launch your application by directly executing greynaf.jar