Skip to content

QubitProducts/topNET

Repository files navigation

topNET

Introduction

topNET is a very lightweight and fast asynchronous HTTP server. It's highly customizable and has been designed with ease of usability in mind. Performance is on pair with servers like NGINX, Fasthttp or Netty and even higher - depending on configuration. topNET is as large as few tens of kilobytes.

Design Key Points

  • Performance
  • API Simplicity
  • Flexibility
  • Configurability

Performance

topNET is designed to behave and look similar to typical J2EE specification but only on a basic level. Structurally one may see some similarities with technologies like Servlet.

There has been implemented two server types, one is based on a typical event only based R/W operations wakups and a second version where all R/W operations are handled in full isolation by handling workers, the main difference between two is that main accept loop in event based version case wakes threads to process incoming R/W operations where second version does only accepting connections and let threads to decide on their own how to handle R/W operations (typically they wait for such). Both versions have similar performance in average use case, however under heavy load - AcceptOnlyEventsTypeServer server may be slightly faster and have better micro-latency.

Both versions however have same functionality with exception of on or two extra options for AcceptOnlyEventsTypeServer. Both server types also share same handling API for requests and response.

To use event only based server simply use:

com.qubit.topnet.PureEventsTypeServer

to use "wait" based server just choose:

com.qubit.topnet.AcceptOnlyEventsTypeServer

Because both servers share same functionality, they wont be referred seperately anymore unless its required in this documentation and as a single logical entity we will use a "server" word in following content.

Server uses 3 types of handling threads where POOL type is default:

  • POOL - this type uses atomic reference array with constant size created upon server start. It is safest and lightest way that stores thread jobs.
  • QUEUE - this type is similar to "pool" but uses concurrent list instead of statically sized array like in "pool" case. While "pool" type thread scans the array to find jobs to do, this thread peeks jobs from head and add to tail in typical FILO manner. Each thread of "queue" type maintain its own local queue instance. QUEUE type has an option of unlimited queue size and such option may be useful in some advanced cases.
  • QUEUE_SHARED - this is same as QUEUE type with the difference that all threads share same concurrent queue. This type of jobs may work better for servers that perform slow asynchronous operations and threads can stuck for long time in one place.

In most of the cases, "pool" will be absolutely fine and recommended choice. If you are not sure, just leave the defaults. Default configuration does also check how many cpu-s is in the system in order to estimate amount of used threads (if there is many cpus/core there will be more threads assigned). By default, server auto-scales when it runs out of jobs allocation space - if server will deal with short processing - it is fine to keep threads amount no larger than cores amount in the system. Autoscaling process by default re-uses threads, if server was downscaling, upscaling will reuse threads that were burried during downscaling.

To observe jobs and threads, add one of examples to the handlers set:

server.registerHandlerByPath(
    "/listjobsandthreads",
    new com.qubit.qurricane.examples.JobsNumHandler(server));

Server processing is limited to almost absolute minimum. While request URL and headers will be parsed and ready for a handler - extended functionality like parsing url parameters or POST body parameters is not provided (yet). There is a wide choice of API that can be used for parsing parameters and will be added to topNET in near future. topNET focuses solely on performance and flexibility.

Server supports protocol up to HTTP/1.1 (including keep-alive).

Usage Simplicity

topNET only requires a Server instance to be created to start processing requests. Service handlers can be added to server and can be chained, there is many way how your application can chain processing and choose which url/method is bounded to which handler in what order. There is plenty of choice in terms of customization. To create typical server application all is needed is a server instance and handler classes that will implement how to handle request. See examples section for more details. Handlers are added to server using:

server.registerHandlerByPath(String path, Handler handler)
// this will register handler by path name. if request path equals,
 handler will process request and pass it forward to next handler in chain.

server.registerPathMatchingHandler(Handler handler)
// this will register handler by handler.match(...) function use, if it returns
true, handler will process request and pass it forward to next handler in chain.

Flexibility

Handling threads amounts (pools), jobs queues size, processing delays and many more options are adjustable to system needs. By default system checks available cpus and will create adequate handling threads amount for each cpu to handle requests.

topNET is extremely small but can serve same heavy loads and application complexity as good or better as fully featured "official" servers such as tomcat, jboss or glassfish. Being very small and highly customizable it's an excellent solution as for mobile devices as for powerful desktop stations.

##Examples

To create server instance:

import com.qubit.topnet.AcceptOnlyEventsTypeServer;
import com.qubit.topnet.examples.EchoHandler;

public static void main(String[] args) {
	AcceptOnlyEventsTypeServer server = 
        new AcceptOnlyEventsTypeServer("localhost", 3456);

	server.setMaxGrowningBufferChunkSize(64 * 1024); 
        // this is a growing buffer size limit.
        // Growing buffer, is a class that is used to store data for R/W operations. 
        // topNET uses dedicated buffer chain for R/W operations, 
        // each chain chunk will have maximum size of specified by this setter.

	server.setJobsPerThread(256);
        // how many jobs each thread can have (max)
  	
    server.setThreadsAmount(16);
        // minimum amount of threads running

  	server.setDefaultIdleTime(25 * 1000);
        // This is how many miliseconds server will wait for R/W 
        // before closing connection.
  		// This property is also configurable on a handler level.

  	server.setDelayForNoIOReadsInSuite(30);
        // (only for accept type server)
        // This property indicates how many nanoseconds thread will sleep (if
        // value is larger than 1000000 then it corresponds to value miliseconds after dividing by 1000000).
  		// Wait occurs when if there is no I/O occuring in entire threads queue 
        // (queue having jobs but none has anything to read or write!.
  	
  	server.registerHandlerByPath("/echo", new EchoHandler());
        //register demo handler (echoing data back)

  	server.start();
        // start the server
}

To see more usage examples, check out the examples 'com.qubit.topnet.examples' package.

##Author Peter Fronc peter.fronc@qubit.com

##Benchmarks Coming.

About

Lightweight Java NIO HTTP Server

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages