Working version of Servlet 3.1 <-> RS bridge.

This commit is contained in:
Arjen Poutsma
2015-07-07 12:18:52 +02:00
parent 3b45087c87
commit f518d76a77
22 changed files with 997 additions and 316 deletions
@@ -21,7 +21,6 @@ import java.io.InputStream;
import org.reactivestreams.Publisher;
import org.springframework.rx.util.BlockingSignalQueue;
import org.springframework.rx.util.BlockingSignalQueueSubscriber;
import org.springframework.util.Assert;
/**
@@ -44,7 +43,7 @@ public class ByteArrayPublisherInputStream extends InputStream {
Assert.notNull(publisher, "'publisher' must not be null");
this.queue = new BlockingSignalQueue<byte[]>();
publisher.subscribe(new BlockingSignalQueueSubscriber<byte[]>(this.queue));
publisher.subscribe(this.queue.subscriber());
}
ByteArrayPublisherInputStream(BlockingSignalQueue<byte[]> queue) {
@@ -7,7 +7,6 @@ import java.util.Arrays;
import org.reactivestreams.Publisher;
import org.springframework.rx.util.BlockingSignalQueue;
import org.springframework.rx.util.BlockingSignalQueuePublisher;
/**
* {@code OutputStream} implementation that stores all written bytes, to be retrieved
@@ -23,7 +22,7 @@ public class ByteArrayPublisherOutputStream extends OutputStream {
* @return a publisher for the written bytes
*/
public Publisher<byte[]> toByteBufPublisher() {
return new BlockingSignalQueuePublisher<byte[]>(this.queue);
return this.queue.publisher();
}
@Override
@@ -19,6 +19,10 @@ package org.springframework.rx.util;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.springframework.util.Assert;
/**
@@ -35,6 +39,8 @@ import org.springframework.util.Assert;
*/
public class BlockingSignalQueue<T> {
private static final int DEFAULT_REQUEST_SIZE_SUBSCRIBER = 1;
private final BlockingQueue<Signal<T>> queue = new LinkedBlockingQueue<Signal<T>>();
@@ -119,121 +125,151 @@ public class BlockingSignalQueue<T> {
return signal != null ? signal.error() : null;
}
private interface Signal<T> {
boolean isOnNext();
T next();
boolean isOnError();
Throwable error();
boolean isComplete();
/**
* Returns a {@code Publisher} backed by this queue.
*/
public Publisher<T> publisher() {
return new BlockingSignalQueuePublisher();
}
private static class OnNext<T> implements Signal<T> {
/**
* Returns a {@code Subscriber} backed by this queue.
*/
public Subscriber<T> subscriber() {
return subscriber(DEFAULT_REQUEST_SIZE_SUBSCRIBER);
}
private final T next;
/**
* Returns a {@code Subscriber} backed by this queue, with the given request size.
* @see Subscription#request(long)
*/
public Subscriber<T> subscriber(long requestSize) {
return new BlockingSignalQueueSubscriber(requestSize);
}
public OnNext(T next) {
Assert.notNull(next, "'next' must not be null");
this.next = next;
}
private class BlockingSignalQueuePublisher implements Publisher<T> {
private Subscriber<? super T> subscriber;
private final Object subscriberMutex = new Object();
@Override
public boolean isOnNext() {
return true;
public void subscribe(Subscriber<? super T> subscriber) {
synchronized (this.subscriberMutex) {
if (this.subscriber != null) {
subscriber.onError(
new IllegalStateException("Only one subscriber allowed"));
}
else {
this.subscriber = subscriber;
final SubscriptionThread thread = new SubscriptionThread();
this.subscriber.onSubscribe(new Subscription() {
@Override
public void request(long n) {
thread.request(n);
}
@Override
public void cancel() {
thread.cancel();
}
});
thread.start();
}
}
}
@Override
public T next() {
return next;
}
private class SubscriptionThread extends Thread {
@Override
public boolean isOnError() {
return false;
}
private volatile long demand = 0;
@Override
public Throwable error() {
throw new IllegalStateException();
}
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
if (demand > 0 && isHeadSignal()) {
subscriber.onNext(pollSignal());
if (demand != Long.MAX_VALUE) {
demand--;
}
}
else if (isHeadError()) {
subscriber.onError(pollError());
break;
}
else if (isComplete()) {
subscriber.onComplete();
break;
}
}
}
catch (InterruptedException ex) {
// Allow thread to exit
}
}
@Override
public boolean isComplete() {
return false;
public void request(long n) {
if (n != Long.MAX_VALUE) {
this.demand += n;
}
else {
this.demand = Long.MAX_VALUE;
}
}
public void cancel() {
interrupt();
}
}
}
private static final class OnError<T> implements Signal<T> {
private class BlockingSignalQueueSubscriber implements Subscriber<T> {
private final Throwable error;
private final long requestSize;
public OnError(Throwable error) {
Assert.notNull(error, "'error' must not be null");
this.error = error;
private Subscription subscription;
public BlockingSignalQueueSubscriber(long requestSize) {
this.requestSize = requestSize;
}
@Override
public boolean isOnError() {
return true;
public void onSubscribe(Subscription subscription) {
this.subscription = subscription;
this.subscription.request(this.requestSize);
}
@Override
public Throwable error() {
return error;
public void onNext(T t) {
try {
putSignal(t);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
this.subscription.request(requestSize);
}
@Override
public boolean isOnNext() {
return false;
public void onError(Throwable t) {
try {
putError(t);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
this.subscription.request(requestSize);
}
@Override
public T next() {
throw new IllegalStateException();
}
@Override
public boolean isComplete() {
return false;
public void onComplete() {
try {
complete();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
private static class OnComplete<T> implements Signal<T> {
private static final OnComplete INSTANCE = new OnComplete();
private OnComplete() {
}
@Override
public boolean isComplete() {
return true;
}
@Override
public boolean isOnNext() {
return false;
}
@Override
public T next() {
throw new IllegalStateException();
}
@Override
public boolean isOnError() {
return false;
}
@Override
public Throwable error() {
throw new IllegalStateException();
}
}
}
@@ -1,110 +0,0 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.rx.util;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.springframework.util.Assert;
/**
* @author Arjen Poutsma
*/
public class BlockingSignalQueuePublisher<T> implements Publisher<T> {
private final BlockingSignalQueue<T> queue;
private Subscriber<? super T> subscriber;
private final Object subscriberMutex = new Object();
public BlockingSignalQueuePublisher(BlockingSignalQueue<T> queue) {
Assert.notNull(queue, "'queue' must not be null");
this.queue = queue;
}
@Override
public void subscribe(Subscriber<? super T> subscriber) {
synchronized (this.subscriberMutex) {
if (this.subscriber != null) {
subscriber.onError(
new IllegalStateException("Only one subscriber allowed"));
}
else {
this.subscriber = subscriber;
final SubscriptionThread thread = new SubscriptionThread();
this.subscriber.onSubscribe(new Subscription() {
@Override
public void request(long n) {
thread.request(n);
}
@Override
public void cancel() {
thread.cancel();
}
});
thread.start();
}
}
}
private class SubscriptionThread extends Thread {
private volatile long requestCount = 0;
private long l = 0;
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
if ((l < requestCount || requestCount == Long.MAX_VALUE) &&
queue.isHeadSignal()) {
subscriber.onNext(queue.pollSignal());
l++;
}
else if (queue.isHeadError()) {
subscriber.onError(queue.pollError());
break;
}
else if (queue.isComplete()) {
subscriber.onComplete();
break;
}
}
}
catch (InterruptedException ex) {
// Allow thread to exit
}
}
public void request(long n) {
if (n != Long.MAX_VALUE) {
this.requestCount += n;
}
else {
this.requestCount = Long.MAX_VALUE;
}
}
public void cancel() {
interrupt();
}
}
}
@@ -1,113 +0,0 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.rx.util;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.springframework.util.Assert;
/**
* A simple byte array {@link Subscriber} that puts all published bytes on a
* {@link @BlockingSignalQueue}.
*
* @author Arjen Poutsma
*/
public class BlockingSignalQueueSubscriber<T> implements Subscriber<T> {
/**
* The default request size to use.
*/
public static final int DEFAULT_REQUEST_SIZE = 1;
private final BlockingSignalQueue<T> queue;
private Subscription subscription;
private int initialRequestSize = DEFAULT_REQUEST_SIZE;
private int requestSize = DEFAULT_REQUEST_SIZE;
/**
* Creates a new {@code BlockingSignalQueueSubscriber} using the given queue.
* @param queue the queue to use
*/
public BlockingSignalQueueSubscriber(BlockingSignalQueue<T> queue) {
Assert.notNull(queue, "'queue' must not be null");
this.queue = queue;
}
/**
* Sets the request size used when subscribing, in {@link #onSubscribe(Subscription)}.
* Defaults to {@link #DEFAULT_REQUEST_SIZE}.
* @param initialRequestSize the initial request size
* @see Subscription#request(long)
*/
public void setInitialRequestSize(int initialRequestSize) {
this.initialRequestSize = initialRequestSize;
}
/**
* Sets the request size used after data or an error comes in, in {@link
* #onNext(Object)} and {@link #onError(Throwable)}. Defaults to {@link
* #DEFAULT_REQUEST_SIZE}.
* @see Subscription#request(long)
*/
public void setRequestSize(int requestSize) {
this.requestSize = requestSize;
}
@Override
public void onSubscribe(Subscription subscription) {
this.subscription = subscription;
this.subscription.request(this.initialRequestSize);
}
@Override
public void onNext(T t) {
try {
this.queue.putSignal(t);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
this.subscription.request(requestSize);
}
@Override
public void onError(Throwable t) {
try {
this.queue.putError(t);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
this.subscription.request(requestSize);
}
@Override
public void onComplete() {
try {
this.queue.complete();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
@@ -0,0 +1,54 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.rx.util;
/**
* @author Arjen Poutsma
*/
class OnComplete<T> implements Signal<T> {
public static final OnComplete INSTANCE = new OnComplete();
private OnComplete() {
}
@Override
public boolean isComplete() {
return true;
}
@Override
public boolean isOnNext() {
return false;
}
@Override
public T next() {
throw new IllegalStateException();
}
@Override
public boolean isOnError() {
return false;
}
@Override
public Throwable error() {
throw new IllegalStateException();
}
}
@@ -0,0 +1,57 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.rx.util;
import org.springframework.util.Assert;
/**
* @author Arjen Poutsma
*/
final class OnError<T> implements Signal<T> {
private final Throwable error;
public OnError(Throwable error) {
Assert.notNull(error, "'error' must not be null");
this.error = error;
}
@Override
public boolean isOnError() {
return true;
}
@Override
public Throwable error() {
return error;
}
@Override
public boolean isOnNext() {
return false;
}
@Override
public T next() {
throw new IllegalStateException();
}
@Override
public boolean isComplete() {
return false;
}
}
@@ -0,0 +1,57 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.rx.util;
import org.springframework.util.Assert;
/**
* @author Arjen Poutsma
*/
class OnNext<T> implements Signal<T> {
private final T next;
public OnNext(T next) {
Assert.notNull(next, "'next' must not be null");
this.next = next;
}
@Override
public boolean isOnNext() {
return true;
}
@Override
public T next() {
return next;
}
@Override
public boolean isOnError() {
return false;
}
@Override
public Throwable error() {
throw new IllegalStateException();
}
@Override
public boolean isComplete() {
return false;
}
}
@@ -0,0 +1,33 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.rx.util;
/**
* @author Arjen Poutsma
*/
interface Signal<T> {
boolean isOnNext();
T next();
boolean isOnError();
Throwable error();
boolean isComplete();
}
@@ -22,11 +22,16 @@ import javax.servlet.AsyncContext;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @author Arjen Poutsma
*/
class AsyncContextSynchronizer {
private static final Log logger = LogFactory.getLog(AsyncContextSynchronizer.class);
private static final int READ_COMPLETE = 1;
private static final int WRITE_COMPLETE = 1 << 1;
@@ -50,7 +55,9 @@ class AsyncContextSynchronizer {
}
public void readComplete() {
logger.debug("Read complete");
if (complete.compareAndSet(WRITE_COMPLETE, COMPLETE)) {
logger.debug("Complete");
this.asyncContext.complete();
}
else {
@@ -59,7 +66,9 @@ class AsyncContextSynchronizer {
}
public void writeComplete() {
logger.debug("Write complete");
if (complete.compareAndSet(READ_COMPLETE, COMPLETE)) {
logger.debug("Complete");
this.asyncContext.complete();
}
else {
@@ -0,0 +1,28 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.rx.web.servlet;
import org.reactivestreams.Publisher;
/**
* @author Arjen Poutsma
*/
public interface HttpHandler {
Publisher<byte[]> handle(Publisher<byte[]> request);
}
@@ -0,0 +1,62 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.rx.web.servlet;
import java.io.IOException;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.reactivestreams.Publisher;
/**
* @author Arjen Poutsma
*/
@WebServlet(asyncSupported = true )
public class HttpHandlerServlet extends HttpServlet {
private static final int BUFFER_SIZE = 4096;
private HttpHandler handler;
public void setHandler(HttpHandler handler) {
this.handler = handler;
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
AsyncContext context = request.startAsync();
final AsyncContextSynchronizer contextSynchronizer =
new AsyncContextSynchronizer(context);
RequestBodyPublisher requestPublisher = new RequestBodyPublisher(contextSynchronizer, BUFFER_SIZE);
request.getInputStream().setReadListener(requestPublisher);
ResponseBodySubscriber responseSubscriber = new ResponseBodySubscriber(contextSynchronizer);
response.getOutputStream().setWriteListener(responseSubscriber);
Publisher<byte[]> responsePublisher = this.handler.handle(requestPublisher);
responsePublisher.subscribe(responseSubscriber);
}
}
@@ -0,0 +1,142 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.rx.web.servlet;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
/**
* @author Arjen Poutsma
*/
public class RequestBodyPublisher implements ReadListener, Publisher<byte[]> {
private final Charset UTF_8 = Charset.forName("UTF-8");
private static final Log logger = LogFactory.getLog(RequestBodyPublisher.class);
private final AsyncContextSynchronizer synchronizer;
private final byte[] buffer;
private long demand;
private Subscriber<? super byte[]> subscriber;
public RequestBodyPublisher(AsyncContextSynchronizer synchronizer, int bufferSize) {
this.synchronizer = synchronizer;
this.buffer = new byte[bufferSize];
}
@Override
public void subscribe(Subscriber<? super byte[]> s) {
this.subscriber = s;
this.subscriber.onSubscribe(new RequestBodySubscription());
}
@Override
public void onDataAvailable() throws IOException {
ServletInputStream input = this.synchronizer.getInputStream();
while (true) {
logger.debug("Demand: " + this.demand);
if (demand <= 0) {
break;
}
boolean ready = input.isReady();
logger.debug("Input " + ready + "/" + input.isFinished());
if (!ready) {
break;
}
int read = input.read(buffer);
logger.debug("Input read:" + read);
if (read == -1) {
break;
}
else if (read > 0) {
if (demand != Long.MAX_VALUE) {
demand--;
}
byte[] copy = Arrays.copyOf(this.buffer, read);
// logger.debug("Next: " + new String(copy, UTF_8));
this.subscriber.onNext(copy);
}
}
}
@Override
public void onAllDataRead() throws IOException {
logger.debug("All data read");
this.synchronizer.readComplete();
this.subscriber.onComplete();
}
@Override
public void onError(Throwable t) {
logger.error("RequestBodyPublisher Error", t);
this.subscriber.onError(t);
}
private class RequestBodySubscription implements Subscription {
@Override
public void request(long n) {
logger.debug("Updating demand " + demand + " by " + n);
boolean stalled = demand <= 0;
if (n != Long.MAX_VALUE && demand != Long.MAX_VALUE) {
demand += n;
}
else {
demand = Long.MAX_VALUE;
}
if (stalled) {
try {
onDataAvailable();
}
catch (IOException ex) {
onError(ex);
}
}
}
@Override
public void cancel() {
synchronizer.readComplete();
demand = 0;
}
}
}
@@ -0,0 +1,111 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.rx.web.servlet;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.springframework.util.Assert;
/**
* @author Arjen Poutsma
*/
public class ResponseBodySubscriber implements WriteListener, Subscriber<byte[]> {
private static final Log logger = LogFactory.getLog(ResponseBodySubscriber.class);
private final AsyncContextSynchronizer synchronizer;
private Subscription subscription;
private byte[] buffer;
private AtomicBoolean complete = new AtomicBoolean(false);
public ResponseBodySubscriber(AsyncContextSynchronizer synchronizer) {
this.synchronizer = synchronizer;
}
@Override
public void onSubscribe(Subscription subscription) {
this.subscription = subscription;
this.subscription.request(1);
}
@Override
public void onNext(byte[] bytes) {
logger.debug("Next: " + bytes.length + " bytes");
Assert.isNull(buffer);
this.buffer = bytes;
try {
onWritePossible();
}
catch (IOException e) {
onError(e);
}
}
@Override
public void onComplete() {
logger.debug("Complete buffer: " + (buffer == null));
if (complete.compareAndSet(false, true) && buffer == null) {
this.synchronizer.writeComplete();
}
}
@Override
public void onWritePossible() throws IOException {
ServletOutputStream output = this.synchronizer.getOutputStream();
boolean ready = output.isReady();
logger.debug("Output: " + ready + " buffer: " + (buffer == null));
if (this.buffer != null && ready) {
output.write(this.buffer);
this.buffer = null;
if (!complete.get()) {
this.subscription.request(1);
}
else {
this.synchronizer.writeComplete();
}
}
else if (this.buffer == null && ready) {
this.subscription.request(1);
}
}
@Override
public void onError(Throwable t) {
logger.error("ResponseBodySubscriber error", t);
}
private void complete() {
}
}