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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
