Data Format Extension requests

Some Web applications send or receive content which is unreadable by testers. Such applications are hard to load test because the content of the requests cannot be modified.

The Data Format Extension API is useful to read custom requests in the same way AMF, GWT, and other modules do. NeoLoad converts the content of binary requests 3of a supported format into readable XML. When the existing modules (AMF, GWT, etc.) do not support custom formats, the Data Format Extension API helps testers have NeoLoad decode and encode requests. With the API used in a Java code, custom formats can be read. The API covers the HTTP and WebSocket transport layers.

Note: Advanced Java programing skills are required to use the Data Format Extension API. For more information, see Data Format Extensions: Create an Encoder/Decoder.

Decoding a request or a response consists in the following operations:

  1. Receiving the request or response

  2. Detecting whether it can be decoded

  3. Converting the binary content into an object

  4. Converting the object into XML

  5. Storing the XML content in the request or response

Encoding is for requests only. It consists in the following operations:

  1. Generating the request with XML content

  2. Converting the XML content into an object

  3. Converting the object into binary content

  4. Storing the binary content in the request

The Data Format Extension API helps:

  • identify whether a request or response can be decoded

  • convert the binary content into an object

  • convert the object into binary content

The other steps of the process are handled by NeoLoad.

Once the API is implemented in NeoLoad, it is invoked on recording the custom format application and to generate the load.

Note: An API implementation must be exported as a JAR file before the file is specified in NeoLoad as described in Data Format Extensions

Data Format Extension API architecture

The Data Format Extension API architecture contains the following Java packages:

  • entities is made of classes representing the request and response entities for the HTTP and WebSocket protocols.

  • functions includes the Encoder, Decoder, and Namer functions.

  • internal is used by NeoLoad to perform internal operations.

  • predicates is made of classes which implement the com.google.common.base. Predicate interface. They help define the conditions for use.

The following classes diagram describes the architecture of the main API components.

The API consists of classes, among which the main ones are the following:

  • com.neotys.extensions.codec.functions.Decoder converts the binary content of a request or a response into a Java object.

  • com.neotys.extensions.codec.functions.Encoder converts an object into binary content.

  • com.neotys.extensions.codec.functions.Namer converts an object generated by a Decoder into a character string. It is used to name requests in NeoLoad.

  • AbstractBinder acts as mediator. It links the classes above with conditions on requests and/or responses.

The API uses the Predicate and Function interfaces provided by the Google Guava library.

The API entry point is AbstractBinder is com.neotys.extensions.codec.AbstractBinder. This mediator makes it possible to link the implementations of the Decoder, Encoder and Namer Java interfaces with conditions for use. A condition for use is a Predicate.

Binder

The Binder notifies NeoLoad which conditions are applicable to the Encoder, Decoder, and Namer. It is reloaded each time a JAR file is declared and removed.

The Binder must be:

  • a public class

  • without a constructor or with the default public constructor

Binder's conditions are predicates to determine whether or not the action should be triggered given an input value. Actions that can be triggered are Encoder, Decoder, or Namer.

Predicates are based on the Java object com.google.common.base.Predicate.

List of supported predicates are accessible from class com.neotys.extensions.codec.predicates.MorePredicates:

  • hasHTTPContentType: returns a predicate that evaluates to 'true' if the Entity is an HTTPEntity whose content type is of the given content type.

  • isHTTPEntity: returns a predicate that evaluates to 'true' if the Entity is an HTTPEntity.

  • isWebSocketEntity: returns a predicate that evaluates to 'true' if the Entity is a WebSocketEntity.

  • isRequestEntity: returns a predicate that evaluates to 'true' if the Entity is a request Entity independently from the network protocol.

  • isResponseEntity: returns a predicate that evaluates to 'true' if the Entity is a response Entity independently from the network protocol.

  • hasChannelResponseHTTPHeader: returns a predicate that evaluates to 'true' if a HTTP header from the channel response is equal to a given value.

  • urlContains: returns a predicate that evaluates to "true" when the URL contains the given string, and "false" otherwise. As a result, predicate urlContains can link an HTTP entity (either request or response) to a specific Encoder/Decoder according to the URL of the HTTP request.

Decoder

The Decoder is a function which processes a table of bytes into a Java object instance.

The lifespan of a Decoder is undefined. It is advised to refrain from specifying class fields.

A Decoder must be:

  • a public class,

  • without a constructor, with a default public constructor, or with a public constructor specifying the parameter com.neotys.extensions.codec.functions.contextual.Context.

Encoder

The Encoder is a function which processes an object into a table of bytes.

The lifespan of an Encoder is undefined. It is advised to refrain from specifying class fields.

An Encoder must be:

  • a public class,

  • without a constructor, with a default public constructor, or with a public constructor specifying the parameter com.neotys.extensions.codec.functions.contextual.Context.

Context

When it is necessary to hold information between decoding operations, a public constructor specifying the parameter com.neotys.extensions.codec.functions.contextual.Context must be declared.

The Context instance must be stored as a class field.

The lifespan of the Context depends on the operation:

  • When recording the application, the Context lifespan is the duration of the recording.

  • When generating the load, the Context lifespan is the duration of the WebSocket channel (there are as many Contexts as WebSocket channels per Virtual User instance).

Namer

The Namer makes it possible to name requests when recording an application. This function processes an object generated by a Decoder into a character string.

Implementation management

NeoLoad takes care of calling the instances of the functions to implement—AbstractBinder, Encoder, Decoder, Namer—in isolated threads. No code synchronization is required.

However, it is advised not to define class attributes for the implementations.

Example: Java Serialization

In this example, the requests and responses carry HTTP Content-type headers equal to application/x-java-serialized-object and include serialized objects in binary format.

To decode and encode this type of requests and responses, it is necessary to write the Binder, Encoder, and Decoder.

JavaSerializationBinder

import static com.google.common.base.Predicates.instanceOf;
import static com.neotys.extensions.codec.predicates.MorePredicates.hasHTTPContentType;
import java.io.Serializable;
import com.neotys.extensions.codec.AbstractBinder;
public class JavaSerializationBinder extends AbstractBinder {
private static final String CONTENT_TYPE = "application/x-java-serialized-object";
@Override
protected void configure() {
whenEntity(hasHTTPContentType(CONTENT_TYPE)).decodeWith(JavaSerializationDecoder.class);
whenObject(instanceOf(Serializable.class)).encodeWith(JavaSerializationEncoder.class);
}
}

Any request or response whose Content-type is application/x-java-serialized-object will be decoded with JavaSerializationDecoder.

JavaSerializationDecoder returns java.io.Serializable instances.

Instances of java.io.Serializable are encoded using JavaSerializationEncoder.

JavaSerializationDecoder

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import com.neotys.extensions.codec.functions.Decoder;
public class JavaSerializationDecoder implements Decoder {
@Override
public Object apply(final byte[] input) {
try (final ObjectInputStream objectIn = new ObjectInputStream(new ByteArrayInputStream(input))) {
return objectIn.readObject();
} catch (final IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}

JavaSerializationDecoder simply uses the Java Serialization mecanism to read an object from bytes. For more information, refer to the Java Object Serialization Specification page in the Oracle website.

JavaSerializationEncoder

public class JavaSerializationEncoder implements Encoder {
@Override
public byte[] apply(final Object input) {
final ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
try (final ObjectOutputStream objectOut = new ObjectOutputStream(byteOut)) {
objectOut.writeObject(input);
} catch (final IOException e) {
e.printStackTrace();
}
return byteOut.toByteArray();
}
}

JavaSerializationEncoder uses the Java Serialization mecanism to write an object from bytes. For more information, refer to the Java Object Serialization Specification page in the Oracle website.

META-INF/services/com.neotys.extensions.codec.AbstractBinder file

This file only contains the line:

com.neotys.sample.serialization.JavaSerializationBinder