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.http.steps.HttpResponseHandlersImpl Maven / Gradle / Ivy
package io.hyperfoil.http.steps;
import static io.hyperfoil.core.session.SessionFactory.access;
import static io.hyperfoil.core.session.SessionFactory.sequenceScopedAccess;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import io.hyperfoil.api.config.BuilderBase;
import io.hyperfoil.api.config.Locator;
import io.hyperfoil.api.config.Rewritable;
import io.hyperfoil.api.config.SequenceBuilder;
import io.hyperfoil.api.config.StepBuilder;
import io.hyperfoil.http.api.HttpCache;
import io.hyperfoil.http.api.HttpRequest;
import io.hyperfoil.http.api.FollowRedirect;
import io.hyperfoil.http.api.HeaderHandler;
import io.hyperfoil.http.api.HttpResponseHandlers;
import io.hyperfoil.api.processor.RawBytesHandler;
import io.hyperfoil.api.processor.HttpRequestProcessorBuilder;
import io.hyperfoil.api.processor.Processor;
import io.hyperfoil.api.session.Access;
import io.hyperfoil.api.session.Action;
import io.hyperfoil.api.session.ResourceUtilizer;
import io.hyperfoil.api.session.Session;
import io.hyperfoil.api.session.SessionStopException;
import io.hyperfoil.core.builders.ServiceLoadedBuilderProvider;
import io.hyperfoil.core.data.LimitedPoolResource;
import io.hyperfoil.core.data.Queue;
import io.hyperfoil.core.handlers.ConditionalAction;
import io.hyperfoil.core.handlers.ConditionalProcessor;
import io.hyperfoil.http.api.StatusHandler;
import io.hyperfoil.http.config.HttpErgonomics;
import io.hyperfoil.http.config.HttpPluginBuilder;
import io.hyperfoil.http.html.HtmlHandler;
import io.hyperfoil.http.html.MetaRefreshHandler;
import io.hyperfoil.http.html.RefreshHandler;
import io.hyperfoil.http.handlers.ConditionalHeaderHandler;
import io.hyperfoil.http.handlers.Location;
import io.hyperfoil.http.handlers.RangeStatusValidator;
import io.hyperfoil.http.handlers.Redirect;
import io.hyperfoil.core.steps.AwaitDelayStep;
import io.hyperfoil.core.steps.PushQueueAction;
import io.hyperfoil.core.steps.ScheduleDelayStep;
import io.hyperfoil.core.util.Unique;
import io.hyperfoil.function.SerializableToLongFunction;
import io.hyperfoil.http.cookie.CookieRecorder;
import io.netty.buffer.ByteBuf;
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);
}
try {
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) {
HttpCache.get(request.session).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);
}
}
} catch (SessionStopException e) {
throw e;
} catch (Throwable t) {
log.error("#{} Response status processing failed on {}", t, session.uniqueId(), this);
request.statistics().incrementInternalErrors(request.startTimestampMillis());
request.markInvalid();
session.stop();
}
}
@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);
}
try {
HttpCache httpCache = HttpCache.get(session);
if (request.cacheControl.invalidate) {
if (AsciiString.contentEqualsIgnoreCase(header, HttpHeaderNames.LOCATION)
|| AsciiString.contentEqualsIgnoreCase(header, HttpHeaderNames.CONTENT_LOCATION)) {
httpCache.invalidate(request.authority, value);
}
}
if (headerHandlers != null) {
for (HeaderHandler handler : headerHandlers) {
handler.handleHeader(request, header, value);
}
}
httpCache.responseHeader(request, header, value);
} catch (SessionStopException e) {
throw e;
} catch (Throwable t) {
log.error("#{} Response header processing failed on {}", t, session.uniqueId(), this);
request.statistics().incrementInternalErrors(request.startTimestampMillis());
request.markInvalid();
session.stop();
}
}
@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();
}
try {
if (request.isRunning()) {
request.statistics().incrementResets(request.startTimestampMillis());
request.setCompleting();
if (completionHandlers != null) {
for (Action handler : completionHandlers) {
handler.run(session);
}
}
}
} catch (SessionStopException e) {
throw e;
} catch (Throwable t) {
t.addSuppressed(throwable);
log.error("#{} Exception {} thrown while handling another exception: ", t, session.uniqueId(), throwable.toString());
request.statistics().incrementInternalErrors(request.startTimestampMillis());
session.stop();
} finally {
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;
}
if (trace) {
log.trace("#{} Received part ({} bytes):\n{}", session.uniqueId(), length,
data.toString(offset, length, StandardCharsets.UTF_8));
}
try {
int dataStartIndex = data.readerIndex();
if (bodyHandlers != null) {
for (Processor handler : bodyHandlers) {
handler.process(request.session, data, offset, length, isLastPart);
data.readerIndex(dataStartIndex);
}
}
} catch (SessionStopException e) {
throw e;
} catch (Throwable t) {
log.error("#{} Response body processing failed on {}", t, session.uniqueId(), this);
request.statistics().incrementInternalErrors(request.startTimestampMillis());
request.markInvalid();
session.stop();
}
}
@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;
}
if (trace) {
log.trace("#{} Completed request on {}", session.uniqueId(), request.connection());
}
try {
if (executed) {
request.recordResponse(System.nanoTime());
if (headerHandlers != null) {
for (HeaderHandler handler : headerHandlers) {
handler.afterHeaders(request);
}
}
if (bodyHandlers != null) {
for (Processor handler : bodyHandlers) {
handler.after(request.session);
}
}
HttpCache.get(request.session).tryStore(request);
}
if (request.isRunning()) {
request.setCompleting();
if (completionHandlers != null) {
for (Action handler : completionHandlers) {
handler.run(session);
}
}
}
} catch (SessionStopException e) {
throw e;
} catch (Throwable t) {
log.error("#{} Response completion failed on {}, stopping the session.", t, request.session.uniqueId(), this);
request.statistics().incrementInternalErrors(request.startTimestampMillis());
request.markInvalid();
session.stop();
} finally {
// When one of the handlers calls Session.stop() it may terminate the phase completely,
// sending stats before this finally-block may run. In that case this request gets completed
// when cancelling all the request (including the current request) and we record the invalid
// request directly there.
if (executed && !request.isValid() && !request.isCompleted()) {
request.statistics().addInvalid(request.startTimestampMillis());
}
request.setCompleted();
}
}
@Override
public void handleRawRequest(HttpRequest request, ByteBuf data, int offset, int length) {
if (rawBytesHandlers == null) {
return;
}
try {
for (RawBytesHandler rawBytesHandler : rawBytesHandlers) {
rawBytesHandler.onRequest(request, data, offset, length);
}
} catch (SessionStopException e) {
throw e;
} catch (Throwable t) {
log.error("#{} Raw request processing failed on {}", t, request.session.uniqueId(), this);
request.markInvalid();
request.session.stop();
}
}
@Override
public void handleRawResponse(HttpRequest request, ByteBuf data, int offset, int length, boolean isLastPart) {
if (rawBytesHandlers == null) {
return;
}
try {
for (RawBytesHandler rawBytesHandler : rawBytesHandlers) {
rawBytesHandler.onResponse(request, data, offset, length, isLastPart);
}
} catch (SessionStopException e) {
throw e;
} catch (Throwable t) {
log.error("#{} Raw response processing failed on {}", t, request.session.uniqueId(), this);
request.markInvalid();
request.session.stop();
}
}
@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 implements Rewritable {
private final HttpRequestStep.Builder parent;
private Boolean autoRangeCheck;
private Boolean stopOnInvalid;
private FollowRedirect followRedirect;
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.Builder builder) {
statusHandlers.add(builder);
return this;
}
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, statusHandlers::add);
}
public Builder header(HeaderHandler handler) {
return header(() -> handler);
}
public Builder header(HeaderHandler.Builder builder) {
headerHandlers.add(builder);
return this;
}
/**
* Handle HTTP response headers.
*
* @return Builder.
*/
public ServiceLoadedBuilderProvider header() {
return new ServiceLoadedBuilderProvider<>(HeaderHandler.Builder.class, 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, bodyHandlers::add);
}
public Builder onCompletion(Action handler) {
return onCompletion(() -> handler);
}
public Builder onCompletion(Action.Builder builder) {
completionHandlers.add(builder);
return this;
}
/**
* Action executed when the HTTP response is fully received.
*
* @return Builder.
*/
public ServiceLoadedBuilderProvider onCompletion() {
return new ServiceLoadedBuilderProvider<>(Action.Builder.class, 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, this::rawBytes);
}
/**
* Inject status handler that marks the request as invalid on status 4xx or 5xx.
* Default value depends on ergonomics.autoRangeCheck
* (see User Guide ).
*
* @param autoRangeCheck True for inserting the handler, false otherwise.
* @return Self.
*/
public Builder autoRangeCheck(boolean autoRangeCheck) {
this.autoRangeCheck = autoRangeCheck;
return this;
}
/**
* Inject completion handler that will stop the session if the request has been marked as invalid.
* Default value depends on ergonomics.stopOnInvalid
* (see User Guide ).
*
* @param stopOnInvalid
* @return
*/
public Builder stopOnInvalid(boolean stopOnInvalid) {
this.stopOnInvalid = stopOnInvalid;
return this;
}
/**
* Automatically fire requests when the server responds with redirection.
* Default value depends on ergonomics.followRedirect
* (see User Guide ).
*
* @param followRedirect Types of server response that will trigger the request.
* @return Self.
*/
public Builder followRedirect(FollowRedirect followRedirect) {
this.followRedirect = followRedirect;
return this;
}
public HttpRequestStep.Builder endHandler() {
return parent;
}
public Builder wrapBodyHandlers(Function, HttpRequestProcessorBuilder> func) {
HttpRequestProcessorBuilder wrapped = func.apply(bodyHandlers);
this.bodyHandlers = new ArrayList<>();
this.bodyHandlers.add(wrapped);
return this;
}
public void prepareBuild() {
HttpErgonomics ergonomics = Locator.current().benchmark().plugin(HttpPluginBuilder.class).ergonomics();
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 ? autoRangeCheck : ergonomics.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 ? stopOnInvalid : ergonomics.stopOnInvalid()) {
completionHandlers.add(() -> StopOnInvalidAction.INSTANCE);
}
FollowRedirect followRedirect = this.followRedirect != null ? this.followRedirect : ergonomics.followRedirect();
switch (followRedirect) {
case LOCATION_ONLY:
applyRedirect(true, false);
break;
case HTML_ONLY:
applyRedirect(false, true);
break;
case ALWAYS:
applyRedirect(true, true);
break;
}
}
private void applyRedirect(boolean location, boolean html) {
Locator locator = Locator.current();
// Not sequence-scoped as queue output var, requesting sequence-scoped access explicitly where needed
Unique coordsVar = new Unique();
String redirectSequenceName = String.format("%s_redirect_%08x",
locator.sequence().name(), ThreadLocalRandom.current().nextInt());
String delaySequenceName = String.format("%s_delay_%08x",
locator.sequence().name(), ThreadLocalRandom.current().nextInt());
Queue.Key queueKey = new Queue.Key();
Queue.Key delayedQueueKey = new Queue.Key();
Unique delayedCoordVar = new Unique();
int concurrency = Math.max(1, locator.sequence().rootSequence().concurrency());
if (html) {
Unique delay = new Unique();
SequenceBuilder delaySequence = locator.scenario().sequence(delaySequenceName);
delaySequence.concurrency(Math.max(1, concurrency))
.step(() -> {
Access inputVar = sequenceScopedAccess(delayedCoordVar);
Access delayVar = sequenceScopedAccess(delay);
SerializableToLongFunction delayFunc = session -> TimeUnit.SECONDS.toMillis(((Redirect.Coords) inputVar.getObject(session)).delay);
return new ScheduleDelayStep(delayVar, ScheduleDelayStep.Type.FROM_NOW, delayFunc);
})
.step(() -> new AwaitDelayStep(sequenceScopedAccess(delay)))
.stepBuilder(new StepBuilder.ActionAdapter(() -> new PushQueueAction(
sequenceScopedAccess(delayedCoordVar), queueKey)))
.step(session -> {
session.getResource(delayedQueueKey).consumed(session);
return true;
});
Locator.push(null, delaySequence);
delaySequence.prepareBuild();
Locator.pop();
}
// The redirect sequence (and queue) needs to have twice the concurrency of the original sequence
// because while executing one redirect it might activate a second redirect
int redirectConcurrency = 2 * concurrency;
LimitedPoolResource.Key poolKey = new LimitedPoolResource.Key<>();
{
// Note: there's a lot of copy-paste between current handler because we need to use
// different method variable for current sequence and new sequence since these have incompatible
// indices - had we used the same var one sequence would overwrite other's var.
Unique newTempCoordsVar = new Unique(true);
HttpRequestStep.BodyGeneratorBuilder bodyBuilder = parent.bodyBuilder();
HttpRequestStep.Builder httpRequest = new HttpRequestStep.Builder()
.method(() -> new Redirect.GetMethod(sequenceScopedAccess(coordsVar)))
.path(() -> new Location.GetPath(sequenceScopedAccess(coordsVar)))
.authority(() -> new Location.GetAuthority(sequenceScopedAccess(coordsVar)))
.headerAppenders(parent.headerAppenders())
.body(bodyBuilder == null ? null : bodyBuilder.copy())
.sync(false)
// we want to reuse the same sequence for subsequent requests
.handler().followRedirect(FollowRedirect.NEVER).endHandler();
if (location) {
httpRequest.handler()
.status(new Redirect.StatusHandler.Builder()
.poolKey(poolKey).concurrency(redirectConcurrency).coordsVar(newTempCoordsVar).handlers(statusHandlers))
.header(new Redirect.LocationRecorder.Builder()
.originalSequenceSupplier(() -> new Redirect.GetOriginalSequence(sequenceScopedAccess(coordsVar)))
.concurrency(redirectConcurrency).inputVar(newTempCoordsVar).outputVar(coordsVar)
.queueKey(queueKey).sequence(redirectSequenceName));
}
if (!headerHandlers.isEmpty()) {
Redirect.WrappingHeaderHandler.Builder wrappingHandler = new Redirect.WrappingHeaderHandler.Builder().coordVar(coordsVar).handlers(headerHandlers);
if (location) {
httpRequest.handler().header(new ConditionalHeaderHandler.Builder()
.condition().stringCondition().fromVar(newTempCoordsVar).isSet(false).end()
.handler(wrappingHandler));
} else {
httpRequest.handler().header(wrappingHandler);
}
}
if (!bodyHandlers.isEmpty() || html) {
Consumer handlerConsumer;
ConditionalProcessor.Builder conditionalBodyHandler;
Redirect.WrappingProcessor.Builder wrappingProcessor = new Redirect.WrappingProcessor.Builder().coordVar(coordsVar).processors(bodyHandlers);
if (location) {
if (!bodyHandlers.isEmpty() || html) {
conditionalBodyHandler = new ConditionalProcessor.Builder()
.condition().stringCondition().fromVar(newTempCoordsVar).isSet(false).end()
.processor(wrappingProcessor);
handlerConsumer = conditionalBodyHandler::processor;
httpRequest.handler().body(HttpRequestProcessorBuilder.adapt(conditionalBodyHandler));
} else {
handlerConsumer = null;
}
} else {
assert html;
httpRequest.handler().body(HttpRequestProcessorBuilder.adapt(wrappingProcessor));
handlerConsumer = httpRequest.handler()::body;
}
if (html) {
handlerConsumer.accept(new HtmlHandler.Builder().handler(new MetaRefreshHandler.Builder()
.processor(fragmented -> new RefreshHandler(
queueKey, delayedQueueKey, poolKey, redirectConcurrency, access(coordsVar), access(delayedCoordVar),
redirectSequenceName, delaySequenceName, access(newTempCoordsVar), new Redirect.GetOriginalSequence(sequenceScopedAccess(coordsVar))))));
}
}
if (!completionHandlers.isEmpty()) {
httpRequest.handler().onCompletion(new ConditionalAction.Builder()
.condition().stringCondition().fromVar(newTempCoordsVar).isSet(false).end()
.action(new Redirect.WrappingAction.Builder().coordVar(coordsVar).actions(completionHandlers)));
}
httpRequest.handler().onCompletion(() -> new Location.Complete<>(poolKey, queueKey, sequenceScopedAccess(coordsVar)));
SequenceBuilder redirectSequence = locator.scenario().sequence(redirectSequenceName)
.concurrency(redirectConcurrency)
.stepBuilder(httpRequest)
.rootSequence();
Locator.push(null, redirectSequence);
redirectSequence.prepareBuild();
Locator.pop();
}
Unique tempCoordsVar = new Unique(locator.sequence().rootSequence().concurrency() > 0);
if (location) {
statusHandlers = Collections.singletonList(
new Redirect.StatusHandler.Builder().poolKey(poolKey).concurrency(redirectConcurrency).coordsVar(tempCoordsVar).handlers(statusHandlers));
List headerHandlers = new ArrayList<>();
headerHandlers.add(new Redirect.LocationRecorder.Builder()
.originalSequenceSupplier(() -> Session::currentSequence)
.concurrency(redirectConcurrency).inputVar(tempCoordsVar).outputVar(coordsVar).queueKey(queueKey).sequence(redirectSequenceName));
if (!this.headerHandlers.isEmpty()) {
// Note: we are using stringCondition.isSet despite the variable is not a string - it doesn't matter for the purpose of this check
headerHandlers.add(new ConditionalHeaderHandler.Builder()
.condition().stringCondition().fromVar(tempCoordsVar).isSet(false).end()
.handlers(this.headerHandlers));
}
this.headerHandlers = headerHandlers;
}
if (!bodyHandlers.isEmpty() || html) {
Consumer handlerConsumer;
ConditionalProcessor.Builder conditionalBodyHandler = null;
if (location) {
conditionalBodyHandler = new ConditionalProcessor.Builder()
.condition().stringCondition().fromVar(tempCoordsVar).isSet(false).end()
.processors(bodyHandlers);
handlerConsumer = conditionalBodyHandler::processor;
} else {
handlerConsumer = bodyHandlers::add;
}
if (html) {
handlerConsumer.accept(new HtmlHandler.Builder().handler(new MetaRefreshHandler.Builder()
.processor(fragmented -> new RefreshHandler(
queueKey, delayedQueueKey, poolKey, redirectConcurrency, access(coordsVar), access(delayedCoordVar),
redirectSequenceName, delaySequenceName, access(tempCoordsVar), Session::currentSequence))));
}
if (location) {
bodyHandlers = Collections.singletonList(HttpRequestProcessorBuilder.adapt(conditionalBodyHandler));
}
}
if (!completionHandlers.isEmpty()) {
completionHandlers = Collections.singletonList(
new ConditionalAction.Builder()
.condition().stringCondition().fromVar(tempCoordsVar).isSet(false).end()
.actions(completionHandlers));
}
}
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);
}
}
@Override
public void readFrom(Builder other) {
statusHandlers.addAll(BuilderBase.copy(other.statusHandlers));
headerHandlers.addAll(BuilderBase.copy(other.headerHandlers));
bodyHandlers.addAll(BuilderBase.copy(other.bodyHandlers));
completionHandlers.addAll(BuilderBase.copy(other.completionHandlers));
rawBytesHandlers.addAll(BuilderBase.copy(other.rawBytesHandlers));
autoRangeCheck = other.autoRangeCheck;
stopOnInvalid = other.stopOnInvalid;
followRedirect = other.followRedirect;
}
}
private static class StopOnInvalidAction implements Action {
// prevents some weird serialization incompatibility
private static final Action INSTANCE = new StopOnInvalidAction();
@Override
public void run(Session session) {
if (!session.currentRequest().isValid()) {
session.stop();
}
}
}
}