Skip to content

Build and execute a request

Marko Topolnik edited this page Aug 9, 2017 · 11 revisions

HTTP requests are built by the ReqBuilder object, obtained with

req(<name>).<http-method>(<url>)

Here <name> is the name under which the request will be known in the GUI and <http-method> is the HTTP method in lowercase. If a request needs to be executed, but not shown in the GUI, the name can be left out:

req.<http-method>(<url>)

Without explicit declaration, all named requests are tracked in the GUI. We can also explicitly enumerate all requests which we want to appear in the GUI:

function init() {
  req.declare('getList', 'getOneItem');
}

This approach makes it easier to switch the tracking of individual requests on and off as needed.

Build the URL

The URL can be specified in full as a string, or built using the URL builder, obtained by

url(<base>)

The base URL must contain at least the URL scheme and authority parts; path segments, path parameters, query parameters, and fragment may be added by invoking the builder's methods s(), pp(), q(), and frag(), respectively. For example,

url('http://example.org:8009').frag('mid').s('items','1').q('show','title only').s('details').pp('x','100')

will build this URL:

http://example.org:8009/items/1/details;x=100?show=title+only#mid

Note how s(), q(), and frag() methods may be arbitrarily interleaved because each independently builds its own part of the URL. The pp() method adds parameters to the latest path segment, so it must be properly ordered with respect to the s() method.

To promote easy composition, all varargs methods (s(), q(), and pp()) also accept an array at any position. The array will be flattened out (recursively), as if its elements were individual arguments (parameter-value pairs must stay together, however). For example this allows the following definition:

function commonQueryParams() {
   return ['show','title only', 'filter','user=damien'];
}
url('http://example.org').s('items').q(commonQueryParams())

The result is

http://example.org/items?show=title+only&filter=user%253Ddamien

Add body

The method body() adds body to the request. There is special support to easily build an XML or JSON body. Any JavaScript object or array is automatically JSONized and there is an XML builder object:

nsdecl('xa', 'http://example.org/xa');
xml('root', ns('xa'))
  .el('items')
    .el('item').att('id', '1')
      .textel('name','item-one', 'desc','the first item')

will build this XML:

<?xml version="1.0" encoding="UTF-8"?>
<xa:root xmlns:xa="http://example.org/xa">
  <xa:items>
    <xa:item id="1">
      <xa:name>item-one</xa:name>
      <xa:desc>the first item</xa:desc>
    </xa:item>
  </xa:items>
</xa:root>

Execute request, process response

The request is executed by calling the go() method:

req('example').get('http://www.example.org').go(spy);

The go() method takes an optional parameter, a callback function which will receive the response. In the above example, the built-in spy() function is passed as the callback. This will cause the response to be logged during the init phase. If the response needs no other processing, this is a good default choice.

A JSON response would be processed like this:

req('example').get('http://www.example.org').go(processJson);
function processJson(r) { 
  spy('items: {}', r.jsonBody().items); 
}

and for XML there's XPath support:

req('example').get('http://www.example.org').go(processXml);
function processXml(r) { 
  spy('items: {}', xpath('/response/items/item').evaluate(r.xmlBody()));
}

XPath expressions are compiled on first usage and cached.

More options

Delay

If we don't want all requests in the test scenario to execute immediately, we can schedule them with a delay. We should never use java.lang.Thread.sleep() because that would block the worker thread. In the setting of a stress-testing session this would lead to a quick meltdown of stress tester's resources. The request is delayed by calling delay() on the request builder:

function step1() {
  req('delayedBy3sec').get('http://example.org/item').s(itemId).delay(3).go(step2);
}
function step2() { 
  req('delayedBy3_1to5_2sec').get('http://example.org/user').s(userId).delay(3.1, 5.2).go(spy); 
}
step1();
  • delay(3) delays the request by 3 seconds;
  • delay(3.1, 5.2) delays by a random amount between 3.1 and 5.2 seconds.

Acceptable status codes

To simplify response checking, and also to optimize memory usage, we can specify which HTTP status codes are deemed as an acceptable (non-error) response: either any, success (codes less than 400), or ok (just the 200-something codes):

req('example').get('http://www.example.org').accept('any').go(spy);

The default is success: a response with an error status code will be labeled as a "failure" and the callback will not be called for it. This global default can be changed in the init method, for example

req.acceptableStatus('ok');

Discard response body

In regular operation the entire response body is loaded into RAM before the response object is passed to the callback. Sometimes the response body can be very large. If the callback will not need to analyze the response body, we can order up front to discard the response body:

req('example').get('http://www.example.org').goDiscardingBody(step2);
function step2(r) { spy('Response body: {}', r.stringBody()); } // body is empty
Clone this wiki locally