Skip to content

Repository files navigation

NHessian

License: MIT # Build Status Test Status Azure DevOps coverage CodeFactor

Fast and efficient Hessian v1 and v2 client library.

Table of Contents

Usage

Simple usage

The easiest way to use this library is the built in HessianService extension.

/*
 * public interface ITestService
 * {
 *     string hello();
 * }
 */

var service = new System.Net.Http.HttpClient()
    .HessianService<ITestService>(
        new Uri("http://localhost:8080/hessian/test"));

Console.WriteLine(service.hello());   // "Hello, World"

HessianService also supports async execution out of the box. Simply use Task or Task<T> as the result type and the call is executed async.

/*
 * public interface ITestService
 * {    
 *     Task<string> hello();
 * }
 */

var service = new System.Net.Http.HttpClient()
    .HessianService<ITestService>(
        new Uri("http://localhost:8080/hessian/test"));

Console.WriteLine(await service.hello())   // "Hello, World"

Advanced usage

HessianService is often too limited as it hides all of the HttpClient code. For example, it is not possible to check the HTTP Code, cancel or use libraries like Polly to implement retry. For those scenarios HessianContent and ReadAsHessianAsync can be used to retain low level access.

var options = new ClientOptions();

var response = await new HttpClient()
    .SendAsync(new HttpRequestMessage()
    {
        Method = HttpMethod.Post,
        RequestUri = new Uri("http://localhost:8080/hessian/test"),
        Content = new HessianContent("hello", Array.Empty<object>(), options)
    });

var result = await response.Content.ReadAsHessianAsync(typeof(string), options);

Console.WriteLine(result);   // "Hello, World"

Motivation

A project I am working on needed a fast and memory efficient hessian v2 client library to talk to a Java backend.

Existing .NET hessian libraries are a combination of "v1 only", "slow" or "memory ineffcient".

Performance

As stated, the main motivation for creating this library was the inefficiency of existing .NET implementations.

Following is a comparison of three real world payloads deserialized with NHessian and CZD.HessianCSharp.

CZD.HessianCSharp is the most downloaded hessian implementation on nuget (and the one that worked best for me so far).

Benchmarks

The following benchmarks focus on deserialization for two reasons:

  1. deserialization is a lot harder to implement efficiently
  2. deserializing is presumably a lot more important for a client than serialization

Context:

  • Payloads were taken from production and are Hessian v1 encoded data streams
  • Measured is pure deserialization. Data is loaded into a MemoryStream pre-benchmark and deserialized straight from it.
  • BenchmarkDotNet was used for profiling.
  • For the NHessian v2 test, the data stream was converted into a v2 stream using NHessian.
Time Memory Allocation
Time Memory Allocation

Advanced Usages

Custom type bindings

Hessian doesn't really specify what remoted type strings look like. Type strings usually refer to an actuall type name but they don't have to.

For example, java uses [int for int arrays (http://hessian.caucho.com/doc/hessian-java-binding-draft-spec.xtp).

The TypeBindings class and paramter allows it define custom bindings.

JavaTypeBindings are included by default and can be extended if required.

/*
 * TypeBindings.Java  includes byndings for 
* "[int", "[long", "[boolean", "[double" and "[string"
 */

var service = new System.Net.Http.HttpClient()
    .HessianService<ITestService>(
        new Uri("http://localhost:8080/hessian/test"),
        TypeBindings.Java);

Console.WriteLine(await service.hello())   // "Hello, World"

Field de-/serialization rules

Defaults

By default, NHessian will de-/serialize public, protected and private instance fields.

The following exceptions apply:

  • NonSerialized marked fields are ignored
  • readonly fields are serialized but are ignored during deserialization

NOTE class fields like static and const are ignored

Customization

Field de-/serialization behavior of NHessian can be customized by

  • create a custom implementation of ITypeInformationProvider (deriving from DefaultTypeInformationProvider is recommended)
  • Override GetSerializableFields/GetDeserializableFields
  • set an instance of your custom implementation to TypeInformationProvider.Default

Example implementation where fields starting with __ should be ignored during serialization:

public class MyTypeInformationProvider : DefaultTypeInformationProvider
{
    protected override FieldInfo[] GetSerializableFieldsOverride(Type type)
    {
        return base.GetSerializableFieldsOverride(type)
            .Where(f => !f.Name.StartsWith("__"))
            .ToArray();
    }
}

TypeInformationProvider.Default = new MyTypeInformationProvider();

Error handling

Hessian specifies a set of faults that the server can report.

The most important one is ServiceException that indicates that the called method threw an exception.

NHessian service proxy will throw the reported exception if:

  • unwrapServiceExceptions is set to true (set by default)
  • Backend remoted an exception that derives from Exception

If the above conditions do not apply and for any other fault type, NHessian will throw a HessianRemoteException providing relevant information.

Strings

Strings are a major challenge during deserialization. Especially in v1 where the same type names are remoted over and over again.

In order to increase performance and memory usage, NHessian contains two optimizations.

String Interning

Strings under a certain length (48 chars) and all hessian v1 type names are interned.

The interning works in the same way as the Microsoft NameTable class. If the same set of characters has already been encountered, the previously created string is returned.

Dates

.Net DateTime instances have one of the following kinds Local, Utc or Unspecified. The hessian protocol always specifies dates/times as UTC.

During serialization, the following rules apply:

  • If DateTimeKind is Utc, the instance is serialized as is.
  • If DateTimeKind is Local, the instance is converted to UTC and serialized.
  • If DateTimeKind is Unspecified, the instance is assumed to be Local. Being local, it is converted to UTC and serialized.

During deserialization:

  • DateTime instances are returned as DateTimeKind.Local by NHessian

Test-Server

NHessian includes a set of integration tests. The server project can be found here: https://github.com/k4tan/NHessian-TestServer

The server is available as a docker image. Before running tests, start the server via:

docker run --rm -it -p 8080:8080 k4tan/hessian-test-server:latest

Missing

About

Hessian 2.0 client library

Topics

Resources

Stars

11 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages