/*
* Copyright 2016 The Simple File Server 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.sfs.util;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import org.sfs.Server;
import org.sfs.VertxContext;
import org.sfs.rx.ObservableFuture;
import org.sfs.rx.RxHelper;
import rx.Subscriber;
import rx.exceptions.CompositeException;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.Preconditions.checkState;
import static io.vertx.core.buffer.Buffer.buffer;
public class KeepAliveHttpServerResponse implements HttpServerResponse {
private static final Logger LOGGER = LoggerFactory.getLogger(KeepAliveHttpServerResponse.class);
private final VertxContext<Server> vertxContext;
public static final String DELIMITER = "\n";
public static final Buffer DELIMITER_BUFFER = buffer(DELIMITER, UTF_8.toString());
private final HttpServerResponse delegate;
private final long timeout;
private Long periodic;
private boolean keepAliveRunning = false;
private boolean keepAliveStarted = false;
private Handler<Throwable> delegateExceptionHandler;
public KeepAliveHttpServerResponse(VertxContext<Server> vertxContext, long timeout, TimeUnit timeUnit, HttpServerResponse delegate) {
this.vertxContext = vertxContext;
this.delegate = delegate;
this.timeout = timeUnit.toMillis(timeout);
delegate.setChunked(true);
delegate.exceptionHandler(exception -> {
ObservableFuture<Void> handler = RxHelper.observableFuture();
stopKeepAlive(handler);
handler.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
handleThrowable(exception);
}
@Override
public void onError(Throwable e) {
handleThrowable(new CompositeException(exception, e));
}
@Override
public void onNext(Void aVoid) {
}
});
});
startKeepAlive();
}
private void handleThrowable(Throwable e) {
if (delegateExceptionHandler != null) {
delegateExceptionHandler.handle(e);
} else {
LOGGER.error("Unhandled Exception", e);
}
}
public KeepAliveHttpServerResponse startKeepAlive() {
checkState(!keepAliveStarted, "Keep Alive is already running");
keepAliveStarted = true;
delegate.write(DELIMITER_BUFFER);
periodic = vertxContext.vertx().setPeriodic(timeout, event -> {
keepAliveRunning = true;
try {
if (keepAliveStarted) {
delegate.write(DELIMITER_BUFFER);
}
} finally {
keepAliveRunning = false;
}
});
return this;
}
public KeepAliveHttpServerResponse stopKeepAlive(ObservableFuture<Void> handler) {
keepAliveStarted = false;
Long p = periodic;
periodic = null;
if (p != null) {
vertxContext.vertx().cancelTimer(p);
}
if (keepAliveRunning) {
vertxContext.vertx().runOnContext(event -> stopKeepAlive(handler));
} else {
handler.complete(null);
}
return this;
}
@Override
public int getStatusCode() {
return delegate.getStatusCode();
}
@Override
public HttpServerResponse setStatusCode(int statusCode) {
return delegate.setStatusCode(statusCode);
}
@Override
public String getStatusMessage() {
return delegate.getStatusMessage();
}
@Override
public HttpServerResponse setStatusMessage(String statusMessage) {
delegate.setStatusMessage(statusMessage);
return this;
}
@Override
public HttpServerResponse setChunked(boolean chunked) {
delegate.setChunked(chunked);
return this;
}
@Override
public boolean isChunked() {
return delegate.isChunked();
}
@Override
public MultiMap headers() {
return delegate.headers();
}
@Override
public HttpServerResponse putHeader(String name, String value) {
delegate.putHeader(name, value);
return this;
}
@Override
public HttpServerResponse putHeader(CharSequence name, CharSequence value) {
delegate.putHeader(name, value);
return this;
}
@Override
public HttpServerResponse putHeader(String name, Iterable<String> values) {
delegate.putHeader(name, values);
return this;
}
@Override
public HttpServerResponse putHeader(CharSequence name, Iterable<CharSequence> values) {
delegate.putHeader(name, values);
return this;
}
@Override
public MultiMap trailers() {
return delegate.trailers();
}
@Override
public HttpServerResponse putTrailer(String name, String value) {
delegate.putTrailer(name, value);
return this;
}
@Override
public HttpServerResponse putTrailer(CharSequence name, CharSequence value) {
delegate.putTrailer(name, value);
return this;
}
@Override
public HttpServerResponse putTrailer(String name, Iterable<String> values) {
delegate.putTrailer(name, values);
return this;
}
@Override
public HttpServerResponse putTrailer(CharSequence name, Iterable<CharSequence> value) {
delegate.putTrailer(name, value);
return this;
}
@Override
public HttpServerResponse closeHandler(Handler<Void> handler) {
delegate.closeHandler(handler);
return this;
}
@Override
public HttpServerResponse write(Buffer chunk) {
ObservableFuture<Void> handler = RxHelper.observableFuture();
stopKeepAlive(handler);
handler.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.write(chunk);
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
return this;
}
@Override
public HttpServerResponse write(String chunk, String enc) {
ObservableFuture<Void> handler = RxHelper.observableFuture();
stopKeepAlive(handler);
handler.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.write(chunk, enc);
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
return this;
}
@Override
public HttpServerResponse write(String chunk) {
ObservableFuture<Void> handler = RxHelper.observableFuture();
stopKeepAlive(handler);
handler.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.write(chunk);
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
return this;
}
@Override
public void end(String chunk) {
ObservableFuture<Void> handler = RxHelper.observableFuture();
stopKeepAlive(handler);
handler.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.end(chunk);
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
}
@Override
public void end(String chunk, String enc) {
ObservableFuture<Void> handler = RxHelper.observableFuture();
stopKeepAlive(handler);
handler.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.end(chunk, enc);
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
}
@Override
public void end(Buffer chunk) {
ObservableFuture<Void> handler = RxHelper.observableFuture();
stopKeepAlive(handler);
handler.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.end(chunk);
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
}
@Override
public void end() {
ObservableFuture<Void> handler = RxHelper.observableFuture();
stopKeepAlive(handler);
handler.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.end();
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
}
@Override
public HttpServerResponse writeContinue() {
ObservableFuture<Void> handler = RxHelper.observableFuture();
stopKeepAlive(handler);
handler.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.writeContinue();
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
return this;
}
@Override
public HttpServerResponse sendFile(String filename, long offset, long length) {
ObservableFuture<Void> handler = RxHelper.observableFuture();
stopKeepAlive(handler);
handler.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.sendFile(filename, offset, length);
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
return this;
}
@Override
public HttpServerResponse sendFile(String filename, long offset, long length, Handler<AsyncResult<Void>> resultHandler) {
ObservableFuture<Void> handler = RxHelper.observableFuture();
stopKeepAlive(handler);
handler.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.sendFile(filename, offset, length, resultHandler);
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
return this;
}
@Override
public boolean ended() {
return delegate.ended();
}
@Override
public boolean closed() {
return delegate.closed();
}
@Override
public boolean headWritten() {
return delegate.headWritten();
}
@Override
public HttpServerResponse headersEndHandler(Handler<Void> handler) {
delegate.headersEndHandler(handler);
return this;
}
@Override
public HttpServerResponse bodyEndHandler(Handler<Void> handler) {
delegate.bodyEndHandler(handler);
return this;
}
@Override
public long bytesWritten() {
return delegate.bytesWritten();
}
@Override
public int streamId() {
return delegate.streamId();
}
@Override
public HttpServerResponse push(HttpMethod method, String host, String path, Handler<AsyncResult<HttpServerResponse>> handler) {
ObservableFuture<Void> h = RxHelper.observableFuture();
stopKeepAlive(h);
h.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.push(method, host, path, handler);
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
return this;
}
@Override
public HttpServerResponse push(HttpMethod method, String path, MultiMap headers, Handler<AsyncResult<HttpServerResponse>> handler) {
ObservableFuture<Void> h = RxHelper.observableFuture();
stopKeepAlive(h);
h.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.push(method, path, headers, handler);
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
return this;
}
@Override
public HttpServerResponse push(HttpMethod method, String path, Handler<AsyncResult<HttpServerResponse>> handler) {
ObservableFuture<Void> h = RxHelper.observableFuture();
stopKeepAlive(h);
h.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.push(method, path, handler);
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
return this;
}
@Override
public HttpServerResponse push(HttpMethod method, String host, String path, MultiMap headers, Handler<AsyncResult<HttpServerResponse>> handler) {
ObservableFuture<Void> h = RxHelper.observableFuture();
stopKeepAlive(h);
h.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.push(method, host, path, headers, handler);
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
return this;
}
@Override
public void reset(long code) {
ObservableFuture<Void> h = RxHelper.observableFuture();
stopKeepAlive(h);
h.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.reset(code);
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
}
@Override
public HttpServerResponse writeCustomFrame(int type, int flags, Buffer payload) {
ObservableFuture<Void> h = RxHelper.observableFuture();
stopKeepAlive(h);
h.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.writeCustomFrame(type, flags, payload);
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
return this;
}
@Override
public void close() {
ObservableFuture<Void> h = RxHelper.observableFuture();
stopKeepAlive(h);
h.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
delegate.close();
}
@Override
public void onError(Throwable e) {
handleThrowable(e);
}
@Override
public void onNext(Void aVoid) {
}
});
}
@Override
public HttpServerResponse exceptionHandler(Handler<Throwable> handler) {
delegateExceptionHandler = handler;
return this;
}
@Override
public HttpServerResponse setWriteQueueMaxSize(int maxSize) {
delegate.setWriteQueueMaxSize(maxSize);
return this;
}
@Override
public boolean writeQueueFull() {
return delegate.writeQueueFull();
}
@Override
public HttpServerResponse drainHandler(Handler<Void> handler) {
delegate.drainHandler(handler);
return this;
}
}