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:
-
Receiving the request or response
-
Detecting whether it can be decoded
-
Converting the binary content into an object
-
Converting the object into XML
-
Storing the XML content in the request or response
Encoding is for requests only. It consists in the following operations:
-
Generating the request with XML content
-
Converting the XML content into an object
-
Converting the object into binary content
-
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 theEncoder
,Decoder
, andNamer
functions. -
internal
is used by NeoLoad to perform internal operations. -
predicates
is made of classes which implement thecom.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 aDecoder
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, predicateurlContains
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 manyContexts
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