Fast and efficient Hessian v1 and v2 client library.
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"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"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".
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).
The following benchmarks focus on deserialization for two reasons:
- deserialization is a lot harder to implement efficiently
- 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
MemoryStreampre-benchmark and deserialized straight from it. - BenchmarkDotNet was used for profiling.
- For the
NHessian v2test, the data stream was converted into a v2 stream using NHessian.
| Time | Memory Allocation |
|---|---|
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"By default, NHessian will de-/serialize public, protected and private instance fields.
The following exceptions apply:
NonSerializedmarked fields are ignoredreadonlyfields are serialized but are ignored during deserialization
NOTE class fields like
staticandconstare ignored
Field de-/serialization behavior of NHessian can be customized by
- create a custom implementation of
ITypeInformationProvider(deriving fromDefaultTypeInformationProvideris 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();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:
unwrapServiceExceptionsis 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 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.
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.
.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
DateTimeKindisUtc, the instance is serialized as is. - If
DateTimeKindisLocal, the instance is converted to UTC and serialized. - If
DateTimeKindisUnspecified, the instance is assumed to beLocal. Being local, it is converted to UTC and serialized.
During deserialization:
- DateTime instances are returned as
DateTimeKind.Localby NHessian
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- Server library
- Method overloading
- Support for remote