Skip to content

braginxv/netgym

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

46 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Netgym is a network client for JVM

'netgym' is a compact high performance asynchronous network client for using various socket-based (TCP and UDP) connections. Among TCP features it supports secure socket connections (TLS), HTTP/1.1, including:

  1. Rest methods GET, POST (with arbitrary content, url-encoded, form-data), PUT, DELETE, OPTIONS;
  2. HTTP/1.1 connections: Single/Closable (per request), Keep-alive and Pipelining;
  3. HTTPS (TLS) connections.

The crucial idea of this library is that the only one instance of client is used to serve all socket-based connections and is built on limited thread pool. Furthermore, different stages or operations of one connection can perform in parallel on the same fork-join thread pool.

Thread model

Thus, the following operations are executed in parallel, increasing performance:

  1. TLS handshake/re-handshake in a dedicated managed Thread Pool used for blocking operations
  2. Data encryption/decryption within a TLS connection
  3. Gzip/deflate data compressing/uncompressing

Requirements

Runtime builds of this library don't depend on other packages and require only JDK 1.7 or later. Assembling depends on JUnit 4 and Mockito testing frameworks.

Add netgym to a project

If you use Maven then insert the following

<dependency>
    <groupId>com.github.braginxv</groupId>
    <artifactId>netgym</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

In case of using Gradle

implementation 'com.github.braginxv:netgym:1.0.0-SNAPSHOT'

To add to a scala project use the following

libraryDependencies += "com.github.braginxv" % "netgym" % "1.0.0-SNAPSHOT"

Typical usage

Asynchronous requests

Suppose we need to download images listed in images variable from a remote server. These images can be downloaded asynchronously to prevent blocking userspace thread (such as UI thread).

  final SimpleHttpClient client = new SimpleHttpClient(baseUrl)
    .configureConnection(SimpleHttpClient.ConnectionType.Single)
    .addHeader("User-Agent", "Netgym network library (https://github.com/braginxv/netgym)");

  // TODO: for modeling synchronous flow in console app, remove the latch in production app
  final CountDownLatch latch = new CountDownLatch(urls.length);
  
  final Pattern fileNamePattern = Pattern.compile(".*/(?=[^/]+$)");
  
  Arrays.stream(urls)
    .forEachOrdered(url -> client.asyncRawGET(url, serverResponse -> {
        serverResponse.left().apply(System.err::println);
        
        serverResponse.right().apply( response -> {
           if (response.getCode() != HttpURLConnection.HTTP_OK) {
              System.err.printf("Downloading failed with HTTP code: %d\n", response.getCode());
              latch.countDown();
              return;
           }
           try {
               Files.write(Paths.get(imageDirectory, fileNamePattern.matcher(url).replaceFirst("")), response.getContent());
            } catch (IOException e) {
               e.printStackTrace();
            } finally { latch.countDown(); }
        });
     }));

  // TODO: await all asynchronous requests to be done, remove the latch in production app
  latch.await();

  // shutdown Network Client before the application terminates
  ClientSystem.client().shutdown();
  ClientSystem.client().awaitTerminating();   

Synchronous flow

Sometimes the control thread may be blocked until a response is received. In these cases you'd use synchronous adapters like this:

   final SimpleHttpClient client = new SimpleHttpClient(baseUrl)
        .configureConnection(SimpleHttpClient.ConnectionType.Single)
        .addHeader("User-Agent", "Netgym network library (https://github.com/braginxv/netgym)");

   final Pattern fileNamePattern = Pattern.compile(".*/(?=[^/]+$)");
   
   Arrays.stream(urls)
    .map(url -> new Pair<>(url, client.syncRawGET(url)))
    .map(completion -> {
           try {
               return new Pair<>(completion.getKey(), completion.getValue().awaitResult());
           } catch (InterruptedException e) {
               System.err.printf("image \"%s\" wasn't loaded\n", completion.getValue());
               return null;
           }
        })
   .filter(Objects::nonNull)
   .forEach(image -> {
      Either<String, Response> serverResponse = image.getValue();
      serverResponse.left().apply(System.err::println);
      serverResponse.right().apply(response ->
         {
           if (response.getCode() != HttpURLConnection.HTTP_OK) {
              System.err.printf("Downloading failed with HTTP code: %d\n", response.getCode());
              return;
           }
            try {
               Files.write(Paths.get(imageDirectory,
               fileNamePattern.matcher(image.getKey()).replaceFirst("")), response.getContent());
            } catch (IOException e) {
                e.printStackTrace();
            }
         });
   });
   
   // shutdown Network Client before application is terminated
   ClientSystem.client().shutdown();
   ClientSystem.client().awaitTerminating();

RxJava example

  final SimpleHttpClient client = new SimpleHttpClient(baseUrl)
     .configureConnection(SimpleHttpClient.ConnectionType.Single)
     .addHeader("User-Agent", "Netgym network library (https://github.com/braginxv/netgym)");
   
  final Pattern fileNamePattern = Pattern.compile(".*/(?=[^/]+$)");
  
  Observable<Pair<String, byte[]>> imageSource = Observable
     .fromIterable(Arrays.asList(urls))
     .flatMap((Function<String, ObservableSource<Pair<String, Response>>>) path ->
        Observable.create((ObservableOnSubscribe<Pair<String, Response>>) emitter ->
          client.asyncRawGET(path, result -> {
            result.left().apply(message -> emitter.onError(new RuntimeException(message)));
      
            result.right().apply(response -> {
                 emitter.onNext(new Pair<>(path, response));
                 emitter.onComplete();
            });
     })))
     .filter(imageData -> imageData.getValue().getCode() == HttpURLConnection.HTTP_OK)
     .map(imageData -> new Pair<>(fileNamePattern.matcher(imageData.getKey()).replaceFirst(""),
        imageData.getValue().getContent()));

  imageSource.blockingSubscribe(image -> Files.write(Paths.get(imageDirectory, image.getKey()), image.getValue()));

  // shutdown Network Client before application is terminated
  ClientSystem.client().shutdown();
  ClientSystem.client().awaitTerminating();

Note that there is no need to engage auxiliary threads through subscribeOn() cuz netgym client optimally parallelizes network connections itself and performs requests asynchronously.

HTTP connection types

The client supports following connections (HTTP/1.1)

  1. simpleHttpClient.configureConnection(SimpleHttpClient.ConnectionType.Single) It's a closable TCP connection being disconnected after having obtained a response. Due to the fact that these connections are executed in parallel and not depend on each other, this type of connection can be fastest to perform parallel requests.
  2. simpleHttpClient.configureConnection(SimpleHttpClient.ConnectionType.Persistent) In this case it is allowed to send multiple requests through the same TCP and TLS sessions. These connections are also called "keep-alive".
  3. simpleHttpClient.configureConnection(SimpleHttpClient.ConnectionType.Pipelining) Similar to Persistent connections the Pipelining connection is used to sending multiple requests, but it doesn't wait the response of the previous request to send a new one, hence this connection is faster than the persistent connection. In spite of Pipelining connections are the part of standard HTTP/1.1 they may not be supported or may be supported partially by many servers (in particular to prevent DoS-attacks).
  4. simpleHttpClient.configurePipeliningConnection(TIME_DELAY_BETWEEN_REQUEST_SENDING) Sometimes a remote server cannot process multiple requests sent all at once in Pipelining connection, but it could be done in this way if requests were sent with a little delay. Invoke this method to use Pipelining connection and specify delay in ms between requests being sent.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages