use of io.micronaut.http.server.netty.multipart.NettyPartData in project micronaut-core by micronaut-projects.
the class RoutingInBoundHandler method buildSubscriber.
private Subscriber<Object> buildSubscriber(NettyHttpRequest<?> request, RouteMatch<?> finalRoute, MonoSink<RouteMatch<?>> emitter) {
boolean isFormData = request.isFormOrMultipartData();
if (isFormData) {
return new CompletionAwareSubscriber<Object>() {
final boolean alwaysAddContent = request.isFormData();
RouteMatch<?> routeMatch = finalRoute;
final AtomicBoolean executed = new AtomicBoolean(false);
final AtomicLong pressureRequested = new AtomicLong(0);
final ConcurrentHashMap<String, Sinks.Many<Object>> subjectsByDataName = new ConcurrentHashMap<>();
final Collection<Sinks.Many<Object>> downstreamSubscribers = Collections.synchronizedList(new ArrayList<>());
final ConcurrentHashMap<IdentityWrapper, HttpDataReference> dataReferences = new ConcurrentHashMap<>();
final ConversionService conversionService = ConversionService.SHARED;
Subscription s;
final LongConsumer onRequest = num -> pressureRequested.updateAndGet(p -> {
long newVal = p - num;
if (newVal < 0) {
s.request(num - p);
return 0;
} else {
return newVal;
}
});
Flux processFlowable(Sinks.Many<Object> many, HttpDataReference dataReference, boolean controlsFlow) {
Flux flux = many.asFlux();
if (controlsFlow) {
flux = flux.doOnRequest(onRequest);
}
return flux.doAfterTerminate(() -> {
if (controlsFlow) {
dataReference.destroy();
}
});
}
@Override
protected void doOnSubscribe(Subscription subscription) {
this.s = subscription;
subscription.request(1);
}
@Override
protected void doOnNext(Object message) {
boolean executed = this.executed.get();
if (message instanceof ByteBufHolder) {
if (message instanceof HttpData) {
HttpData data = (HttpData) message;
if (LOG.isTraceEnabled()) {
LOG.trace("Received HTTP Data for request [{}]: {}", request, message);
}
String name = data.getName();
Optional<Argument<?>> requiredInput = routeMatch.getRequiredInput(name);
if (requiredInput.isPresent()) {
Argument<?> argument = requiredInput.get();
Supplier<Object> value;
boolean isPublisher = Publishers.isConvertibleToPublisher(argument.getType());
boolean chunkedProcessing = false;
if (isPublisher) {
HttpDataReference dataReference = dataReferences.computeIfAbsent(new IdentityWrapper(data), key -> new HttpDataReference(data));
Argument typeVariable;
if (StreamingFileUpload.class.isAssignableFrom(argument.getType())) {
typeVariable = ARGUMENT_PART_DATA;
} else {
typeVariable = argument.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
}
Class typeVariableType = typeVariable.getType();
Sinks.Many<Object> namedSubject = subjectsByDataName.computeIfAbsent(name, key -> makeDownstreamUnicastProcessor());
chunkedProcessing = PartData.class.equals(typeVariableType) || Publishers.isConvertibleToPublisher(typeVariableType) || ClassUtils.isJavaLangType(typeVariableType);
if (Publishers.isConvertibleToPublisher(typeVariableType)) {
boolean streamingFileUpload = StreamingFileUpload.class.isAssignableFrom(typeVariableType);
if (streamingFileUpload) {
typeVariable = ARGUMENT_PART_DATA;
} else {
typeVariable = typeVariable.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
}
dataReference.subject.getAndUpdate(subject -> {
if (subject == null) {
Sinks.Many<Object> childSubject = makeDownstreamUnicastProcessor();
Flux flowable = processFlowable(childSubject, dataReference, true);
if (streamingFileUpload && data instanceof FileUpload) {
namedSubject.tryEmitNext(new NettyStreamingFileUpload((FileUpload) data, serverConfiguration.getMultipart(), getIoExecutor(), (Flux<PartData>) flowable));
} else {
namedSubject.tryEmitNext(flowable);
}
return childSubject;
}
return subject;
});
}
Sinks.Many<Object> subject;
final Sinks.Many<Object> ds = dataReference.subject.get();
if (ds != null) {
subject = ds;
} else {
subject = namedSubject;
}
Object part = data;
if (chunkedProcessing) {
HttpDataReference.Component component;
try {
component = dataReference.addComponent();
if (component == null) {
s.request(1);
return;
}
} catch (IOException e) {
subject.tryEmitError(e);
s.cancel();
return;
}
part = new NettyPartData(dataReference, component);
}
if (data instanceof FileUpload && StreamingFileUpload.class.isAssignableFrom(argument.getType())) {
dataReference.upload.getAndUpdate(upload -> {
if (upload == null) {
return new NettyStreamingFileUpload((FileUpload) data, serverConfiguration.getMultipart(), getIoExecutor(), (Flux<PartData>) processFlowable(subject, dataReference, true));
}
return upload;
});
}
Optional<?> converted = conversionService.convert(part, typeVariable);
converted.ifPresent(subject::tryEmitNext);
if (data.isCompleted() && chunkedProcessing) {
subject.tryEmitComplete();
}
value = () -> {
StreamingFileUpload upload = dataReference.upload.get();
if (upload != null) {
return upload;
} else {
return processFlowable(namedSubject, dataReference, dataReference.subject.get() == null);
}
};
} else {
if (data instanceof Attribute && !data.isCompleted()) {
request.addContent(data);
s.request(1);
return;
} else {
value = () -> {
if (data.refCnt() > 0) {
return data;
} else {
return null;
}
};
}
}
if (!executed) {
String argumentName = argument.getName();
if (!routeMatch.isSatisfied(argumentName)) {
Object fulfillParamter = value.get();
routeMatch = routeMatch.fulfill(Collections.singletonMap(argumentName, fulfillParamter));
// the data to the request ensures it is cleaned up after the route completes.
if (!alwaysAddContent && fulfillParamter instanceof ByteBufHolder) {
request.addContent((ByteBufHolder) fulfillParamter);
}
}
if (isPublisher && chunkedProcessing) {
// accounting for the previous request
pressureRequested.incrementAndGet();
}
if (routeMatch.isExecutable() || message instanceof LastHttpContent) {
executeRoute();
executed = true;
}
}
if (alwaysAddContent) {
request.addContent(data);
}
if (!executed || !chunkedProcessing) {
s.request(1);
}
} else {
request.addContent(data);
s.request(1);
}
} else {
request.addContent((ByteBufHolder) message);
s.request(1);
}
} else {
((NettyHttpRequest) request).setBody(message);
s.request(1);
}
}
@Override
protected void doOnError(Throwable t) {
s.cancel();
for (Sinks.Many<Object> subject : downstreamSubscribers) {
subject.tryEmitError(t);
}
emitter.error(t);
}
@Override
protected void doOnComplete() {
for (Sinks.Many<Object> subject : downstreamSubscribers) {
// subjects will ignore the onComplete if they're already done
subject.tryEmitComplete();
}
executeRoute();
}
private Sinks.Many<Object> makeDownstreamUnicastProcessor() {
Sinks.Many<Object> processor = Sinks.many().unicast().onBackpressureBuffer();
downstreamSubscribers.add(processor);
return processor;
}
private void executeRoute() {
if (executed.compareAndSet(false, true)) {
emitter.success(routeMatch);
}
}
};
} else {
return new CompletionAwareSubscriber<Object>() {
private Subscription s;
private RouteMatch<?> routeMatch = finalRoute;
private AtomicBoolean executed = new AtomicBoolean(false);
@Override
protected void doOnSubscribe(Subscription subscription) {
this.s = subscription;
subscription.request(1);
}
@Override
protected void doOnNext(Object message) {
if (message instanceof ByteBufHolder) {
request.addContent((ByteBufHolder) message);
s.request(1);
} else {
((NettyHttpRequest) request).setBody(message);
s.request(1);
}
}
@Override
protected void doOnError(Throwable t) {
s.cancel();
emitter.error(t);
}
@Override
protected void doOnComplete() {
if (executed.compareAndSet(false, true)) {
emitter.success(routeMatch);
}
}
};
}
}
Aggregations