Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.hyperfoil.core.steps.HttpResponseHandlersImpl Maven / Gradle / Ivy
package io.hyperfoil.core.steps;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.IntFunction;
import io.hyperfoil.api.config.BuilderBase;
import io.hyperfoil.api.config.ErgonomicsBuilder;
import io.hyperfoil.api.config.Locator;
import io.hyperfoil.api.connection.HttpRequest;
import io.hyperfoil.api.processor.HttpRequestProcessorBuilder;
import io.hyperfoil.api.processor.Processor;
import io.hyperfoil.api.session.Action;
import io.hyperfoil.core.builders.ServiceLoadedBuilderProvider;
import io.hyperfoil.core.handlers.RangeStatusValidator;
import io.hyperfoil.core.http.CookieRecorder;
import io.netty.buffer.ByteBuf;
import io.hyperfoil.api.http.HeaderHandler;
import io.hyperfoil.api.http.HttpResponseHandlers;
import io.hyperfoil.api.http.RawBytesHandler;
import io.hyperfoil.api.session.Session;
import io.hyperfoil.api.http.StatusHandler;
import io.hyperfoil.api.session.ResourceUtilizer;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.util.AsciiString;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
public class HttpResponseHandlersImpl implements HttpResponseHandlers, ResourceUtilizer, Serializable {
private static final Logger log = LoggerFactory.getLogger(HttpResponseHandlersImpl.class);
private static final boolean trace = log.isTraceEnabled();
final StatusHandler[] statusHandlers;
final HeaderHandler[] headerHandlers;
final Processor[] bodyHandlers;
final Action[] completionHandlers;
final RawBytesHandler[] rawBytesHandlers;
private HttpResponseHandlersImpl(StatusHandler[] statusHandlers,
HeaderHandler[] headerHandlers,
Processor[] bodyHandlers,
Action[] completionHandlers,
RawBytesHandler[] rawBytesHandlers) {
this.statusHandlers = statusHandlers;
this.headerHandlers = headerHandlers;
this.bodyHandlers = bodyHandlers;
this.completionHandlers = completionHandlers;
this.rawBytesHandlers = rawBytesHandlers;
}
@Override
public void handleStatus(HttpRequest request, int status, String reason) {
Session session = request.session;
if (request.isCompleted()) {
if (trace) {
log.trace("#{} Ignoring status {} as the request has been marked completed (failed).", session.uniqueId(), status);
}
return;
}
if (trace) {
log.trace("#{} Received status {}: {}", session.uniqueId(), status, reason);
}
switch (request.method) {
case GET:
case HEAD:
// TODO: should we store 203, 300, 301 and 410?
// TODO: partial response 206
if (status != 200) {
request.cacheControl.noStore = true;
}
break;
case POST:
case PUT:
case DELETE:
case PATCH:
if (status >= 200 && status <= 399) {
request.session.httpCache().invalidate(request.authority, request.path);
request.cacheControl.invalidate = true;
}
request.cacheControl.noStore = true;
break;
}
request.statistics().addStatus(request.startTimestampMillis(), status);
if (statusHandlers != null) {
for (StatusHandler handler : statusHandlers) {
handler.handleStatus(request, status);
}
}
if (headerHandlers != null) {
for (HeaderHandler handler : headerHandlers) {
handler.beforeHeaders(request);
}
}
if (bodyHandlers != null) {
for (Processor handler : bodyHandlers) {
handler.before(request.session);
}
}
}
@Override
public void handleHeader(HttpRequest request, CharSequence header, CharSequence value) {
Session session = request.session;
if (request.isCompleted()) {
if (trace) {
log.trace("#{} Ignoring header on a failed request: {}: {}", session.uniqueId(), header, value);
}
return;
}
if (trace) {
log.trace("#{} Received header {}: {}", session.uniqueId(), header, value);
}
if (request.cacheControl.invalidate) {
if (AsciiString.contentEqualsIgnoreCase(header, HttpHeaderNames.LOCATION)
|| AsciiString.contentEqualsIgnoreCase(header, HttpHeaderNames.CONTENT_LOCATION)) {
session.httpCache().invalidate(request.authority, value);
}
}
if (headerHandlers != null) {
for (HeaderHandler handler : headerHandlers) {
handler.handleHeader(request, header, value);
}
}
request.session.httpCache().responseHeader(request, header, value);
}
@Override
public void handleThrowable(HttpRequest request, Throwable throwable) {
Session session = request.session;
if (trace) {
log.trace("#{} {} Received exception", throwable, session.uniqueId(), request);
}
if (request.isCompleted()) {
if (trace) {
log.trace("#{} Request has been already completed", session.uniqueId());
}
return;
}
if (request.isValid()) {
// Do not mark as invalid when timed out
request.markInvalid();
}
session.currentRequest(request);
try {
if (request.isRunning()) {
request.setCompleting();
if (completionHandlers != null) {
for (Action handler : completionHandlers) {
handler.run(session);
}
}
}
} finally {
request.statistics().incrementResets(request.startTimestampMillis());
request.setCompleted();
}
}
@Override
public void handleBodyPart(HttpRequest request, ByteBuf data, int offset, int length, boolean isLastPart) {
Session session = request.session;
if (request.isCompleted()) {
if (trace) {
log.trace("#{} Ignoring body part ({} bytes) on a failed request.", session.uniqueId(), data.readableBytes());
}
return;
}
session.currentRequest(request);
if (trace) {
log.trace("#{} Received part ({} bytes):\n{}", session.uniqueId(), data.readableBytes(),
data.toString(data.readerIndex(), data.readableBytes(), StandardCharsets.UTF_8));
}
int dataStartIndex = data.readerIndex();
if (bodyHandlers != null) {
for (Processor handler : bodyHandlers) {
handler.process(request.session, data, offset, length, isLastPart);
data.readerIndex(dataStartIndex);
}
}
session.currentRequest(null);
}
@Override
public void handleEnd(HttpRequest request, boolean executed) {
Session session = request.session;
if (request.isCompleted()) {
if (trace) {
log.trace("#{} Request has been already completed.", session.uniqueId());
}
return;
}
session.currentRequest(request);
if (trace) {
log.trace("#{} Completed request on {}", session.uniqueId(), request.connection());
}
try {
if (executed) {
long endTime = System.nanoTime();
request.statistics().recordResponse(request.startTimestampMillis(), request.sendTimestampNanos() - request.startTimestampNanos(), endTime - request.startTimestampNanos());
if (headerHandlers != null) {
for (HeaderHandler handler : headerHandlers) {
handler.afterHeaders(request);
}
}
if (bodyHandlers != null) {
for (Processor handler : bodyHandlers) {
handler.after(request.session);
}
}
request.session.httpCache().tryStore(request);
}
if (request.isRunning()) {
request.setCompleting();
if (completionHandlers != null) {
for (Action handler : completionHandlers) {
handler.run(session);
}
}
}
} finally {
if (executed && !request.isValid()) {
request.statistics().addInvalid(request.startTimestampMillis());
}
request.setCompleted();
}
}
@Override
public void handleRawRequest(HttpRequest request, ByteBuf data, int offset, int length) {
if (rawBytesHandlers == null) {
return;
}
for (RawBytesHandler rawBytesHandler : rawBytesHandlers) {
rawBytesHandler.onRequest(request, data, offset, length);
}
}
@Override
public void handleRawResponse(HttpRequest request, ByteBuf data, int offset, int length, boolean isLastPart) {
if (rawBytesHandlers == null) {
return;
}
for (RawBytesHandler rawBytesHandler : rawBytesHandlers) {
rawBytesHandler.onResponse(request, data, offset, length, isLastPart);
}
}
@Override
public void reserve(Session session) {
ResourceUtilizer.reserve(session, (Object[]) statusHandlers);
ResourceUtilizer.reserve(session, (Object[]) headerHandlers);
ResourceUtilizer.reserve(session, (Object[]) bodyHandlers);
ResourceUtilizer.reserve(session, (Object[]) completionHandlers);
ResourceUtilizer.reserve(session, (Object[]) rawBytesHandlers);
}
/**
* Manages processing of HTTP responses.
*/
public static class Builder {
// prevents some weird serialization incompatibility
private static final Action STOP_ON_INVALID_RESPONSE = session -> {
if (!session.currentRequest().isValid()) {
session.stop();
}
};
private final HttpRequestStep.Builder parent;
private Locator locator;
private Boolean autoRangeCheck;
private Boolean stopOnInvalid;
private List statusHandlers = new ArrayList<>();
private List headerHandlers = new ArrayList<>();
private List bodyHandlers = new ArrayList<>();
private List completionHandlers = new ArrayList<>();
private List rawBytesHandlers = new ArrayList<>();
public static Builder forTesting() {
return new Builder(null);
}
Builder(HttpRequestStep.Builder parent) {
this.parent = parent;
}
public Builder status(StatusHandler handler) {
statusHandlers.add(() -> handler);
return this;
}
/**
* Handle HTTP response status.
*
* @return Builder.
*/
public ServiceLoadedBuilderProvider status() {
return new ServiceLoadedBuilderProvider<>(StatusHandler.Builder.class, locator, statusHandlers::add);
}
public Builder header(HeaderHandler handler) {
return header(() -> handler);
}
public Builder header(HeaderHandler.Builder builder) {
headerHandlers.add(builder.setLocator(locator));
return this;
}
/**
* Handle HTTP response headers.
*
* @return Builder.
*/
public ServiceLoadedBuilderProvider header() {
return new ServiceLoadedBuilderProvider<>(HeaderHandler.Builder.class, locator, headerHandlers::add);
}
public Builder body(HttpRequestProcessorBuilder builder) {
bodyHandlers.add(builder);
return this;
}
/**
* Handle HTTP response body.
*
* @return Builder.
*/
public ServiceLoadedBuilderProvider body() {
return new ServiceLoadedBuilderProvider<>(HttpRequestProcessorBuilder.class, locator, bodyHandlers::add);
}
public Builder onCompletion(Action handler) {
return onCompletion(() -> handler);
}
public Builder onCompletion(Action.Builder builder) {
completionHandlers.add(builder.setLocator(locator));
return this;
}
/**
* Action executed when the HTTP response is fully received.
*
* @return Builder.
*/
public ServiceLoadedBuilderProvider onCompletion() {
return new ServiceLoadedBuilderProvider<>(Action.Builder.class, locator, completionHandlers::add);
}
public Builder rawBytes(RawBytesHandler handler) {
rawBytesHandlers.add(() -> handler);
return this;
}
public Builder rawBytes(RawBytesHandler.Builder builder) {
rawBytesHandlers.add(builder);
return this;
}
/**
* Handler processing HTTP response before parsing.
*
* @return Builder.
*/
public ServiceLoadedBuilderProvider rawBytes() {
return new ServiceLoadedBuilderProvider<>(RawBytesHandler.Builder.class, locator, this::rawBytes);
}
public HttpRequestStep.Builder endHandler() {
return parent;
}
public void prepareBuild() {
if (ergonomics().repeatCookies()) {
header(new CookieRecorder());
}
// TODO: we might need defensive copies here
statusHandlers.forEach(StatusHandler.Builder::prepareBuild);
headerHandlers.forEach(HeaderHandler.Builder::prepareBuild);
bodyHandlers.forEach(HttpRequestProcessorBuilder::prepareBuild);
completionHandlers.forEach(Action.Builder::prepareBuild);
rawBytesHandlers.forEach(RawBytesHandler.Builder::prepareBuild);
if (autoRangeCheck == null && ergonomics().autoRangeCheck() || autoRangeCheck != null && autoRangeCheck) {
statusHandlers.add(new RangeStatusValidator.Builder().min(200).max(399));
}
// We must add this as the very last action since after calling session.stop() there other handlers won't be called
if (stopOnInvalid == null && ergonomics().stopOnInvalid() || stopOnInvalid != null && stopOnInvalid) {
completionHandlers.add(() -> STOP_ON_INVALID_RESPONSE);
}
}
private ErgonomicsBuilder ergonomics() {
return locator.benchmark().ergonomics();
}
@SuppressWarnings("unchecked")
public HttpResponseHandlersImpl build() {
return new HttpResponseHandlersImpl(
toArray(statusHandlers, StatusHandler.Builder::build, StatusHandler[]::new),
toArray(headerHandlers, HeaderHandler.Builder::build, HeaderHandler[]::new),
toArray(bodyHandlers, b -> b.build(true), Processor[]::new),
toArray(completionHandlers, Action.Builder::build, Action[]::new),
toArray(rawBytesHandlers, RawBytesHandler.Builder::build, RawBytesHandler[]::new));
}
private static T[] toArray(List list, Function build, IntFunction generator) {
if (list.isEmpty()) {
return null;
} else {
return list.stream().map(build).toArray(generator);
}
}
public HttpResponseHandlersImpl.Builder copy(HttpRequestStep.Builder parent, Locator locator) {
Builder copy = new Builder(parent).setLocator(locator);
copy.statusHandlers.addAll(BuilderBase.copy(locator, this.statusHandlers));
copy.headerHandlers.addAll(BuilderBase.copy(locator, this.headerHandlers));
copy.bodyHandlers.addAll(BuilderBase.copy(locator, this.bodyHandlers));
copy.completionHandlers.addAll(BuilderBase.copy(locator, this.completionHandlers));
copy.rawBytesHandlers.addAll(BuilderBase.copy(locator, this.rawBytesHandlers));
return copy;
}
public Builder setLocator(Locator locator) {
this.locator = locator;
statusHandlers.forEach(builder -> builder.setLocator(locator));
headerHandlers.forEach(builder -> builder.setLocator(locator));
bodyHandlers.forEach(builder -> builder.setLocator(locator));
completionHandlers.forEach(builder -> builder.setLocator(locator));
rawBytesHandlers.forEach(builder -> builder.setLocator(locator));
return this;
}
}
}