Skip to content

The New nGrinder HTTP Client

Imbyungjun edited this page Mar 16, 2021 · 7 revisions

Until nGrinder 3.5.3, legacy HTTP client has not updated and exposed on many vulnerabilities. Also, modern HTTP specs were not supported like HTTP2.

From nGrinder 3.5.4, nGrinder provides new HTTP client that supports HTTP2, latest cookie specs, and HTTP methods PATCH.

The new HTTP client for nGrinder must be light-weight so the agent can focus on performance test. The new HTTP client is implemented based on Apache httpcomponents-core.

The new HTTP client supports both HTTP1 and HTTP2. You can enforce protocol version with invoking setVersionPolicy(). Available options are FORCE_HTTP_1, FORCE_HTTP_2 and NEGOTIATE . Default version policy is NEGOTIATE. So, HTTP client negotiates the protocol version with target server.

Basic Request Sample

import org.ngrinder.http.HTTPRequest
import org.ngrinder.http.HTTPResponse

import org.apache.hc.core5.http2.HttpVersionPolicy

@RunWith(GrinderRunner)
class TestRunner {

    public static GTest test
    public static HTTPRequest request
    public static Map<String, String> headers = [:]
    public static Map<String, String> params = [:]

    @BeforeProcess
    public static void beforeProcess() {
        test = new GTest(1, "www.google.com")
        request = new HTTPRequest()
        grinder.logger.info("before process.");
    }

    @BeforeThread
    public void beforeThread() {
        test.record(this, "test")
        grinder.statistics.delayReports=true;
        grinder.logger.info("before thread.");
    }

    @Before
    public void before() {
        request.setHeaders(headers)
        request.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)  // or HttpVersionPolicy.FORCE_HTTP_2
        grinder.logger.info("before. init headers and cookies");
    }

    @Test
    public void test(){
        HTTPResponse result = request.GET("https://www.google.com", params)
        grinder.logger.info("protocol version: {}", result.version);
        grinder.logger.info("body: {}", result.bodyText);

        if (result.statusCode == 301 || result.statusCode == 302) {
            grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", result.statusCode);
        } else {
            assertThat(result.statusCode, is(200));
        }
    }
}

Supported Parameters

Legacy nGrinder HTTP client supports only NVPair as a parameter. It was enough for setting request parameters or headers but, in case you have to use bunch of parameters, you also had to write more boilerplate codes.

Now, the new HTTP client supports org.apache.hc.core5.http.NameValuePair and org.apache.hc.core5.http.Header as a parameter. But, you don't have to worry about instantiating implementation class. Map is also supported as a parameter. If you pass a parameter as Map, new HTTP client converts it to appropriate parameter type and request is executed. The NVPair is also supported for easy to migrate existing performance test scripts.

import org.ngrinder.http.HTTPRequest
import org.ngrinder.http.HTTPResponse

@RunWith(GrinderRunner)
class TestRunner {

    public static GTest test
    public static HTTPRequest request

    @BeforeProcess
    public static void beforeProcess() {
        test = new GTest(1, "www.google.com")
        request = new HTTPRequest()
        grinder.logger.info("before process.");
    }

    @BeforeThread
    public void beforeThread() {
        test.record(this, "test")
        grinder.statistics.delayReports=true;
        grinder.logger.info("before thread.");
    }

    @Test
    public void test(){
        // You can pass a parameter with Groovy Map literal
        HTTPResponse result = request.GET("https://www.google.com", [ "param1": "value1" ], [ "header1": "value1" ])

        if (result.statusCode == 301 || result.statusCode == 302) {
            grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", result.statusCode);
        } else {
            assertThat(result.statusCode, is(200));
        }
    }
}

Or you can pass a parameter with instantiating a list of org.apache.hc.core5.http.message.BasicHeader or org.apache.hc.core5.http.message.BasicNameValuePair yourself.

import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.message.BasicNameValuePair;

import org.ngrinder.http.HTTPRequest
import org.ngrinder.http.HTTPResponse

@RunWith(GrinderRunner)
class TestRunner {

    public static GTest test
    public static HTTPRequest request

    public List<Header> headers = [ new BasicHeader("header1", "value1") ]
    public List<NameValuePair> params = [ new BasicNameValuePair("param1", "value1") ]

    @BeforeProcess
    public static void beforeProcess() {
        test = new GTest(1, "www.google.com")
        request = new HTTPRequest()
        grinder.logger.info("before process.");
    }

    @BeforeThread
    public void beforeThread() {
        test.record(this, "test")
        grinder.statistics.delayReports=true;
        grinder.logger.info("before thread.");
    }

    @Test
    public void test(){
        HTTPResponse result = request.GET("https://www.google.com", params, headers)

        if (result.statusCode == 301 || result.statusCode == 302) {
            grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", result.statusCode);
        } else {
            assertThat(result.statusCode, is(200));
        }
    }
}

Partial Read Sample

In case the test target API produces a large response body, the performance test will derive humongous network traffic and it's almost same as DDOS attack. You can reduce network traffic by reading only a part of response body. The partial response reading feature is only supported on HTTP1 protocol currently.

import org.ngrinder.http.HTTPRequest
import org.ngrinder.http.HTTPResponse

import org.apache.hc.core5.http2.HttpVersionPolicy

@RunWith(GrinderRunner)
class TestRunner {

    public static GTest test
    public static HTTPRequest request
    public static Map<String, String> headers = [:]
    public static Map<String, String> params = [:]

    @BeforeProcess
    public static void beforeProcess() {
        test = new GTest(1, "www.google.com")
        request = new HTTPRequest()
        grinder.logger.info("before process.");
    }

    @BeforeThread
    public void beforeThread() {
        test.record(this, "test")
        grinder.statistics.delayReports=true;
        grinder.logger.info("before thread.");
    }

    @Before
    public void before() {
        request.setHeaders(headers)
        request.setReadBytes(1024)  // Set how many bytes will you read from response body
        request.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)
        grinder.logger.info("before. init headers and cookies");
    }

    @Test
    public void test(){
        HTTPResponse result = request.GET("https://www.google.com", params)
        grinder.logger.info("protocol version: {}", result.version);
        grinder.logger.info("body: {}", result.bodyText);

        if (result.statusCode == 301 || result.statusCode == 302) {
            grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", result.statusCode);
        } else {
            assertThat(result.statusCode, is(200));
        }
    }
}
Clone this wiki locally