How to Capture and Replay HTTP Requests with FunRequest in Java

This article explains how to extend a testing framework with a FunRequest class that records HTTP request and response details, provides fluent setters, supports GET and POST, and enables saving and reproducing requests for faster bug diagnosis.

FunTester
FunTester
FunTester
How to Capture and Replay HTTP Requests with FunRequest in Java

The FunRequest class captures the complete HTTP request and response (the "scene") to enable reproducible debugging in Java test frameworks. It stores the original HttpRequestBase and the parsed CloseableHttpResponse as JSON.

Class Definition

public class FunRequest extends FanLibrary implements Serializable, Cloneable {
    private static final long serialVersionUID = -4153600036943378727L;
    static Logger logger = LoggerFactory.getLogger(FunRequest.class);

    RequestType requestType;               // GET or POST
    HttpRequestBase request;               // underlying request object
    String host = "";
    String apiName = "";
    String uri = "";
    List<Header> headers = new ArrayList<>();
    JSONObject args = new JSONObject();    // GET parameters
    JSONObject params = new JSONObject(); // POST form parameters
    JSONObject json = new JSONObject();    // POST JSON body
    JSONObject response = new JSONObject();
    // constructors and fluent methods omitted for brevity
}

Factory Methods

static FunRequest isGet() { return new FunRequest(RequestType.GET); }
static FunRequest isPost() { return new FunRequest(RequestType.POST); }

Fluent Setters (method chaining)

FunRequest setHost(String host) { this.host = host; return this; }
FunRequest setApiName(String apiName) { this.apiName = apiName; return this; }
FunRequest setUri(String uri) { this.uri = uri; return this; }
FunRequest addHeader(Object key, Object value) { headers.add(getHeader(key.toString(), value.toString())); return this; }
FunRequest addArgs(Object key, Object value) { args.put(key, value); return this; }
FunRequest addParam(Object key, Object value) { params.put(key, value); return this; }
FunRequest addJson(Object key, Object value) { json.put(key, value); return this; }
FunRequest setHeaders(List<Header> headers) { this.headers.addAll(headers); return this; }
FunRequest setArgs(JSONObject args) { this.args.putAll(args); return this; }
FunRequest setParams(JSONObject params) { this.params.putAll(params); return this; }
FunRequest setJson(JSONObject json) { this.json.putAll(json); return this; }

Building the Request

HttpRequestBase getRequest() {
    if (request != null) return request;
    if (StringUtils.isEmpty(uri)) uri = host + apiName;
    switch (requestType) {
        case RequestType.GET:
            request = FanLibrary.getHttpGet(uri, args);
            break;
        case RequestType.POST:
            if (!params.isEmpty()) {
                request = FanLibrary.getHttpPost(uri + changeJsonToArguments(args), params);
            } else if (!json.isEmpty()) {
                request = FanLibrary.getHttpPost(uri + changeJsonToArguments(args), json.toString());
            } else {
                request = FanLibrary.getHttpPost(uri + changeJsonToArguments(args));
            }
            break;
    }
    for (Header h : headers) { request.addHeader(h); }
    logger.debug("Request info: {}", new RequestInfo(request).toString());
    return request;
}

Lazy Response Retrieval

JSONObject getResponse() {
    if (response.isEmpty()) {
        response = getHttpResponse(request == null ? getRequest() : request);
    }
    return response;
}

Reconstructing from an Existing Request

static FunRequest initFromRequest(HttpRequestBase base) {
    String method = base.getMethod();
    RequestType type = RequestType.getRequestType(method);
    String uri = base.getURI().toString();
    List<Header> hdrs = Arrays.asList(base.getAllHeaders());

    if (type == RequestType.GET) {
        return FunRequest.isGet().setUri(uri).setHeaders(hdrs);
    }
    // POST handling
    HttpPost post = (HttpPost) base;
    HttpEntity entity = post.getEntity();
    String content = EntityUtils.toString(entity);
    String ct = entity.getContentType().getValue();
    if (HttpClientConstant.ContentType_TEXT.getValue().equalsIgnoreCase(ct) ||
        HttpClientConstant.ContentType_JSON.getValue().equalsIgnoreCase(ct)) {
        return FunRequest.isPost().setUri(uri).setHeaders(hdrs).setJson(JSONObject.fromObject(content));
    } else if (HttpClientConstant.ContentType_FORM.getValue().equalsIgnoreCase(ct)) {
        return FunRequest.isPost().setUri(uri).setHeaders(hdrs).setParams(getJson(content.split("&")));
    } else {
        RequestException.fail("Unsupported request type!");
    }
    return null;
}

Saving Request and Response

public static void save(HttpRequestBase base, JSONObject response) {
    FunRequest fr = initFromRequest(base);
    fr.setResponse(response);
    String path = "/request/" + Time.getDate().substring(8) + SPACE_1 + fr.getUri().replace(OR, PART);
    Save.info(path, fr.toString());
}

Utility: Header → JSON

public static JSONObject header2Json(List<Header> headers) {
    JSONObject obj = new JSONObject();
    headers.forEach(h -> obj.put(h.getName(), h.getValue()));
    return obj;
}

Clone and String Representation

@Override
FunRequest clone() {
    FunRequest copy = new FunRequest();
    copy.setRequest(cloneRequest(getRequest()));
    return copy;
}

@Override
public String toString() {
    return "{" +
        "requestType='" + requestType.getName() + "', " +
        "host='" + host + "', " +
        "apiName='" + apiName + "', " +
        "uri='" + uri + "', " +
        "headers=" + header2Json(headers).toString() + ", " +
        "args=" + args.toString() + ", " +
        "params=" + params.toString() + ", " +
        "json=" + json.toString() + ", " +
        "response=" + getResponse().toString() +
        "}";
}

Typical Usage

// Conditional persistence based on a configuration flag
if (SAVE_KEY) {
    FunRequest.save(request, res); // 'res' is the parsed JSON response
}

By persisting the FunRequest object, developers can later reload the file, reconstruct the exact request, and compare the recorded response with a fresh execution, greatly simplifying bug reproduction.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DebuggingJavatestingHttpClienthttprequestbaserequest-replay
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.