Endpoints issue requests to obtain resources or use logical level services.
Details of how NetKernel mediates requests in the ROC abstraction are provided in the
ROC In a Nutshell
book.
Physical level code is able to access the logical level through an instance of
INKFRequestContext
which is provided
as a method argument to the endpoint with each call from the kernel.
By convention, the local name used in the documentation
is
context.
For example:
public void onSource(INKFRequestContext context)
{
// Endpoint may use the context argument to issue requests
...
}
Convenience Methods
The context object provides several methods to
succinctly issue common request forms.
source(...)
Instead of using the following code:
request = context.createRequest("res:/resources/readme.txt");
request.setRepresentationClass(String.class);
text = context.issueRequest(request);one may use the succinct code:
String text = context.source("res:/resources/readme.txt", String.class):or
Object text = context.source("res:/resources/readme.txt"):sink(...)
The sink(...) method issues a request with the
SINK verb.
The two arguments include the resource identifier and a representation
object containing the information to be transferred to the resource.
The representation is set as the primary argument for the request
(as compared to named arguments):
String primaryArgument = "Sink this text";
context.sink("transient:ID", primaryArgument);
transrept(...)
The transrept(...) method issues a request with the
TRANSREPT verb.
This method is used if an endpoint has a resource representation
in one form and needs to work with the information in a different form.
For example, the following request is issued to convert an
HDS node to a W3C DOM Document object:
IHDSNode node = // ...
Document doc = context.transrept(node, Document.class);
exists(...)
The exists(...) method issues a request with the
EXISTS verb.
This method is used to get the boolean response about the status
of a resource.
For example:
if (context.exists("res:/customer/44"))
{
...
}
requestNew(...)
The method name new() conflicts with a Java reserved word.
The
requestNew(...) method issues a request with the
NEW verb.
Making General Requests
To fully customize a request, a request object is first created
and then its methods are used to provide changes.
The first step is creating a request object with the
createRequest(...)
method using a resource identifier:
INKFRequest request = context.createRequest("res:/resources/readme.txt");or with the
createRequestToEndoint(...) method using an
endpoint identifier
INKFRequest request = context.createRequestToEndpoint("widget:box-with-title")Once created, the request can be issued immediately or arguments and meta data may be added first.
Arguments
In most programming systems there are two approaches to passing information
in a request: pass-by-reference and pass-by-value.
With pass-by-reference an identifier for the information is provided in
the request and the recipient then uses the reference to retrieve the
information.
With pass-by-value, the information representation is pushed to the
recipient within the request.
NetKernel and ROC recognizes that pass-by-value is really a special case of the
more general and powerful pass-by-reference approach.
Please refer to the physical level documentation on
pass by value for more details.
Pass-By-Reference
To pass a request argument by reference,
add the named argument along with its value (which is
a resource identifier) using the
addArgument(...)
method.
For example, the following code will request that the toUpper
service process the resource identified by
res:/resources/readme.txt:
request = context.createRequest("active:toUpper");
request.addArgument("operand", "res:/resources/readme.txt");Pass-By-Value
To pass a request argument by value,
add the named argument along with the object containing
the value using the
addArgumentByValue(...) method.
For example, the following code will request that the toUpper
service process the information passed by value as a String:
String message = "Hi, this is a message";
request = context.createRequest("active:toUpper");
request.addArgumentByValue("operand", message);
Comparison
The NKF API
has been designed to hide differences
between pass-by-reference and pass-by-value from the resolved endpoint.
As discussed in the
request analysis
documentation, an endpoint issues a SOURCE request using the
arg: URI scheme to obtain the representation of the argument;
it does not know which approach was used by the client.
Identifier Encoding
If information to be passed in a request can be converted into textual form,
it is possible to encode the information within the resource identifier.
REST Path Value Encoding
REST path value encoding has its origins in the http URI scheme
as used in the World Wide Web.
The scheme specific part of the http URI is a hierarchical,
tree-shape structure structure using the slash ("/") character to delimit
path parts or tree hierarchy levels.
For example,
http://mycom.com/customer/44584,
is an identifier that can be interpreted as an identifier for
the specific customer with the customer "ID" of 44584.
Within NetKernel, the res URI scheme used to identify general resources,
uses the same hierarchical path structure as the http URI scheme.
Using grammars
, it is easy to construct a
REST path that encodes information.
For example, the following grammar:
<accessor>
<id>customer:endpoint</id>
<grammar>res:/customer/
<group name="customer-id">
<regex type="integer" />
</group>
</grammar>
</accessor>
associates the trailing part of an identifier with the argument named "customer-id".
If the grammar is associated with the logical endpoint identifier "customer:endpoint",
the following code will create a properly encoded REST identifier:
INKFRequest request = context.createRequestToEndpoint("customer:endpoint");
request.addArgument("customer-id", "42");
request.setRepresentationClass(String.class);
String identifier = (String)context.issueRequest(request);
Identifier Values
Named argument values can provide a computational value instead of a reference to a resource.
For example, a fibonacci number computing service can use the following grammar:
<grammar>
<active>
<identifier>active:fib</identifier>
<argument name="n" min="1" max="1" />
</active>
</grammar>
which means that identifiers such as
active:fib+n@5
and
active:fib+n@8
identify resources that are based on the result of computing the
fibonacci number for 5'' and ''8.
To get the value of the named argument, use the
getArgumentValue(...)
method.
This will return a
String
with the value of the argument.
For example, to get the value of
the argument "n" using the grammar (above):
String operand = context.getThisRequest().getArgumentValue("n");Representation Class
When the resource representation returned in a response must be an instance of a
particular class, that class is specified using the
setRepresentationClass(...) method.
The requesting endpoint will either receive the information in this form or
be thrown an exception.
If the kernel detects a mismatch between the required representation class
and the representation class provided by the resolved endpoint, it transparently
requests a transreption
from one form to the other.
For example, the following code sets the requested representation class
to be a String:
request.setRepresentationClass(String.class);
Issue Request
The endpoint has a choice of issuing a request synchronously or asynchronously.
If it issues the request synchronously, its progress is blocked until
the response arrives.
If it issues the request asynchronously, the endpoint will continue to
execute until it elects to explicitly wait for the response.
Synchronous
An endpoint issues a request synchronously with the
issueRequest(...)
method, which blocks until a response is returned.
This method returns the representation contained in the response.
INKFRequest request = context.createRequest(...);
Object representation = context.issue(request);
Some requests, such as a SINK request, are not expected to return a representation.
In such a case simply issue the request and ignore any returned
object:
INKFRequest request = context.createRequest(...);
request.setVerb(INKFRequestReadOnly.VERB_SINK);
context.issueRequest(request);
Asynchronous
An endpoint may issue a request asynchronously with the
issueAsyncRequest(...)
method, which immediately returns a
INKFAsyncRequestHandle
object.
The endpoint may then continue processing while the
sub-request is being processed (potentially on a different CPU core).
When the endpoint is ready to accept the response to the sub-request
it calls the
join() method on the returned handle.
The join method ensures that the endpoint code will
not proceed without the response to the asynchronous sub-request,
blocking progress if the sub-request has not yet returned its response.
(An alternate method join(...) allows a timeout to be specified)
INKFRequest request = context.createRequest("active:longProcess");
INKFAsyncRequestHandle requestHandle = context.issueAsyncRequest(request);
//... perform other work
// Now wait for the sub-request to return
Object representation = requestHandle.join();
Fire-And-Forget
There is no harm done ignoring the response from an asynchronous sub-request.
If a service is needed but it does not matter when the work completes and
the results can be ignored, then the fire-and-forget pattern may be used.
For example, sending a message to a log sub-system can be done asynchronously
and without the response:
INKFRequest request = context.createRequest("active:logSystem");
request.addArgument("message", "Just started our work!");
context.issueAsyncRequest(request);
Asynchronous Response Listener
Instead of using the
join()
method to rendezvous with the response from an asynchronous sub-request,
a response listener can created and set on the handle.
The INKFAsyncRequestListener
interface has two methods:
receiveResponse(...)
and
receiveException(...),
one of which is called when the sub-request returns with a normal representation
or an exception representation in the response.
For example, the following class implements the listener interface:
class MyRequestListener implements INKFAsyncRequestListener
{
public void receiveException(NKFException aException,
INKFRequest aRequest, INKFRequestContext aContext)
throws Exception
{
// Process an exception from the sub-request
}
public void receiveResponse(INKFResponseReadOnly aResponse,
INKFRequestContext aContext)
throws Exception
{
// Process a response from the sub-request
}
}
and is set as the listener to a sub-request:
INKFRequest request = context.createRequest("active:longProcess");
INKFAsyncRequestHandle requestHandle = context.issueAsyncRequest(request);
handle.setListener(new MyRequestListener());
context.setResponse(null); //inhibits the return of a response from this code
Notice that the context associated with the evaluated request must
be set to null to inhibit the response being returned
from this code (it must be returned by the listener code).
Advanced Features
Priority
It is possible to set the priority of a request using the
setPriority(...)
method.
There are three priority levels set by integer constants in the
interface INKFRequestReadOnly
:
INKFRequestReadOnly.PRIORITY_MIN;
INKFRequestReadOnly.PRIORITY_STD;
INKFRequestReadOnly.PRIORITY_MAX;
By default, requests are set to the standard priority and may be set to a lower
priority:
request.setPriority(INKFRequestReadOnly.PRIORITY_MIN);
or a higher priority:
request.setPriority(INKFRequestReadOnly.PRIORITY_MAX);
Changing the priority of a request impacts how the kernel selects which request to
schedule when a NetKernel application is under heavy load.
It is recommended that requests use the default standard priority
and for requests that can wait for servicing then use the minimum priority.
The maximum priority should not be used as it will potentially conflict
with NetKernel system administration tools and services.
Request headers associate meta information with a request.
Headers may be made "sticky" - once they are set, they stay associated with
the request and are transferred to any sub-requests spawned through
the processing of the request.
Headers are managed as a key-value map with a
String
used as the key and any object stored as the value.
Header entries are set on a INKFRequest
and can be retrieved from a INKFRequestReadOnly
instance.
The kernel interprets headers as hints about how it should processes requests.
The headers recognized by the kernel are provided on the
Kernel Request Headers page.
Although information can be associated with a request by setting
a request header, these must not be used to store or transfer
state affecting logical processing of a resource.
Their sole function is to capture and transfer meta-level information.
Application designs may benefit from associating meta information with a request.
For example, an external message ID could be associated with a request and, if made
sticky, will appear in all subsequently generated requests.
This example associates an external message identifier with a request
and all subsequent sub-requests:
INKFRequest request = context.createRequest(...);
Integer messageID = // code that retrieves the external message ID
request.setHeader("JMS_MESSAGE_ID", messageID, true);
The header information can be read and used in log messages, etc:
public void onSource(INKFRequestContext context)
{
Integer messageID = context.getThisRequest().getHeaderValue("JMS_MESSAGE_ID");
...
}
If application request header information should only live for the duration of
the request then do not set the "sticky bit" to true:
INKFRequest request = context.createRequest(...);
Integer messageID = // code that retrieves the external message ID
request.setHeader("JMS_MESSAGE_ID", messageID);
Working with the request response
There are some situations where it is important to have
access to more than just the representation returned in a
response.
The method issueRequestForResponse(...) issues
a request and returns a
INKFResponseReadOnly object
which contains additional information.
INKRequest request = context.createRequest(...);
INKFResponseReadOnly response = context.issueRequestForResponse(request);
The following table lists the methods available on the response object:
| Method | Returns | Use |
| isExpired() | boolean | Indicates if the representation is no longer valid |
| getRepresentation() | Object | Representation returned in the response |
| getRequest() | INKFRequest | Request corresponding to this response |
| getMimeType() | String | The MIME type of the returned representation |
| hasMetaData() | boolean | indicates if the response has meta data |
| getMetaDataKeys() | Iterator<String> | An iterator of meta data key values (as Strings) |
| getMetaData(String key) | Object | A object associated with the supplied meta date key value |
| getMetaData() | IRequestResponseFields | A object with response fields |
|