All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.quarkiverse.playpen.VirtualPlaypenClient Maven / Gradle / Ivy

package io.quarkiverse.playpen;

import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeoutException;

import org.jboss.logging.Logger;

import io.netty.channel.FileRegion;
import io.netty.handler.codec.http.DefaultHttpContent;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.quarkiverse.playpen.client.AbstractPlaypenClient;
import io.quarkiverse.playpen.server.PlaypenProxyConstants;
import io.quarkus.netty.runtime.virtual.VirtualClientConnection;
import io.quarkus.netty.runtime.virtual.VirtualResponseHandler;
import io.quarkus.vertx.http.runtime.QuarkusHttpHeaders;
import io.quarkus.vertx.http.runtime.VertxHttpRecorder;
import io.vertx.codegen.annotations.Nullable;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.buffer.impl.BufferImpl;
import io.vertx.core.http.HttpClientResponse;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.streams.ReadStream;
import io.vertx.core.streams.WriteStream;
import io.vertx.core.streams.impl.InboundBuffer;

public class VirtualPlaypenClient extends AbstractPlaypenClient {
    protected static final Logger log = Logger.getLogger(VirtualPlaypenClient.class);

    protected Vertx vertx;

    private class NettyResponseHandler implements VirtualResponseHandler, ReadStream {

        final String responsePath;
        InboundBuffer queue;
        static Buffer end = Buffer.buffer();
        Handler endHandler;
        VirtualClientConnection connection;

        public void setConnection(VirtualClientConnection connection) {
            this.connection = connection;
        }

        private void write(Buffer buf) {
            vertx.runOnContext((v) -> queue.write(buf));
        }

        public NettyResponseHandler(String responsePath, Vertx vertx) {
            this.responsePath = responsePath;
        }

        @Override
        public ReadStream exceptionHandler(@Nullable Handler handler) {
            queue.exceptionHandler(handler);
            return this;
        }

        @Override
        public ReadStream handler(@Nullable Handler handler) {
            if (handler == null) {
                if (queue != null)
                    queue.handler(null);
                return this;
            }
            log.debug("NettyResponseHandler: set handler");
            queue.handler((buf) -> {
                log.debug("NettyResponseHandler: handler");
                if (buf == end) {
                    log.debug("NettyResponseHandler: calling end");
                    connection.close();
                    if (endHandler != null) {
                        endHandler.handle(null);
                    }
                } else {
                    log.debug("NettyResponseHandler: handler.handle(buf)");
                    handler.handle(buf);
                }
            });
            return this;
        }

        @Override
        public ReadStream pause() {
            log.debug("NettyResponseHandler: pause");
            queue.pause();
            return this;
        }

        @Override
        public ReadStream resume() {
            log.debug("NettyResponseHandler: resume");
            boolean result = queue.resume();
            log.debug("NettyResponseHandler: resume returned: " + result);
            return this;
        }

        @Override
        public ReadStream fetch(long amount) {
            log.debug("NettyResponseHandler: fetch");
            queue.fetch(amount);
            return this;
        }

        @Override
        public ReadStream endHandler(@Nullable Handler endHandler) {
            this.endHandler = endHandler;
            return this;
        }

        @Override
        public void handleMessage(Object msg) {
            log.debugv("NettyResponseHandler: handleMessage({0})", msg.getClass().getName());
            if (msg instanceof HttpResponse) {
                queue = new InboundBuffer<>(vertx.getOrCreateContext());
                queue.pause();
                HttpResponse res = (HttpResponse) msg;
                proxyClient.request(HttpMethod.POST, responsePath + "?keepAlive=" + running)
                        .onFailure(exc -> {
                            logError("Proxy handle response failure: " + exc.getMessage());
                            workerOffline();
                        })
                        .onSuccess(pushRequest -> {
                            log.debug("NettyResponseHandler connect accepted for pushResponse");
                            setToken(pushRequest);
                            pushRequest.setTimeout(pollTimeoutMillis);
                            pushRequest.putHeader(PlaypenProxyConstants.STATUS_CODE_HEADER,
                                    Integer.toString(res.status().code()));

                            for (String name : res.headers().names()) {
                                final List allForName = res.headers().getAll(name);
                                if (allForName == null || allForName.isEmpty()) {
                                    continue;
                                }

                                for (Iterator valueIterator = allForName.iterator(); valueIterator.hasNext();) {
                                    String val = valueIterator.next();
                                    if (name.equalsIgnoreCase("Transfer-Encoding")
                                            && val.equals("chunked")) {
                                        // ignore
                                    } else if (name.equalsIgnoreCase("Content-Length")) {
                                        pushRequest.headers().add("Content-Length", val);
                                    } else {
                                        pushRequest.headers().add(PlaypenProxyConstants.HEADER_FORWARD_PREFIX + name, val);
                                    }
                                }
                            }
                            Future req = null;

                            if (msg instanceof FullHttpResponse) {
                                req = pushRequest.send(BufferImpl.buffer(((HttpContent) msg).content()));
                            } else {
                                req = pushRequest.send(this);
                            }
                            // a successful push restarts poll
                            req
                                    .onFailure(exc -> {
                                        if (exc instanceof TimeoutException) {
                                            poll();
                                        } else {
                                            logError("Failed to push service response: " + exc.getMessage());
                                            workerOffline();
                                        }
                                    })
                                    .onSuccess(VirtualPlaypenClient.this::handlePoll);
                        });
            }
            if (!(msg instanceof FullHttpResponse)) { // writing response will be handled above if full
                if (msg instanceof HttpContent) {
                    log.debug("NettyResponseHandler: write HttpContent");
                    write(BufferImpl.buffer(((HttpContent) msg).content()));
                }
                if (msg instanceof FileRegion) {
                    log.error("FileRegion not supported yet");
                    throw new RuntimeException("FileRegion not supported yet");
                }
                if (msg instanceof LastHttpContent) {
                    log.debug("NettyResponseHandler: write LastHttpContent");
                    write(end);
                }
            }
        }

        @Override
        public void close() {

        }
    }

    private class NettyWriteStream implements WriteStream {
        VirtualClientConnection connection;

        public NettyWriteStream(VirtualClientConnection connection) {
            this.connection = connection;
        }

        private void writeHttpContent(Buffer data) {
            log.debug("NettyWriteStream: writeHttpContent");
            // todo getByteBuf copies the underlying byteBuf
            DefaultHttpContent content = new DefaultHttpContent(data.getByteBuf());
            connection.sendMessage(content);

        }

        @Override
        public WriteStream exceptionHandler(@Nullable Handler handler) {
            return this;
        }

        @Override
        public Future write(Buffer data) {
            writeHttpContent(data);
            Promise promise = Promise.promise();
            write(data, promise);
            return promise.future();
        }

        @Override
        public void write(Buffer data, Handler> handler) {
            writeHttpContent(data);
            handler.handle(Future.succeededFuture());
        }

        @Override
        public void end(Handler> handler) {
            log.debug("NettyWriteStream: end");
            connection.sendMessage(LastHttpContent.EMPTY_LAST_CONTENT);
            handler.handle(Future.succeededFuture());
        }

        @Override
        public WriteStream setWriteQueueMaxSize(int maxSize) {
            return this;
        }

        @Override
        public boolean writeQueueFull() {
            return false;
        }

        @Override
        public WriteStream drainHandler(@Nullable Handler handler) {
            return this;
        }
    }

    protected void processPoll(HttpClientResponse pollResponse) {
        log.debug("Unpack poll request");
        String method = pollResponse.getHeader(PlaypenProxyConstants.METHOD_HEADER);
        String uri = pollResponse.getHeader(PlaypenProxyConstants.URI_HEADER);
        String responsePath = pollResponse.getHeader(PlaypenProxyConstants.RESPONSE_LINK);
        NettyResponseHandler handler = new NettyResponseHandler(responsePath, vertx);
        VirtualClientConnection connection = VirtualClientConnection.connect(handler, VertxHttpRecorder.VIRTUAL_HTTP,
                null);
        handler.setConnection(connection);

        QuarkusHttpHeaders quarkusHeaders = new QuarkusHttpHeaders();
        // add context specific things
        io.netty.handler.codec.http.HttpMethod httpMethod = io.netty.handler.codec.http.HttpMethod.valueOf(method);

        DefaultHttpRequest nettyRequest = new DefaultHttpRequest(HttpVersion.HTTP_1_1,
                httpMethod, uri,
                quarkusHeaders);
        pollResponse.headers().forEach((key, val) -> {
            log.debugv("Poll response header: {0} : {1}", key, val);
            int idx = key.indexOf(PlaypenProxyConstants.HEADER_FORWARD_PREFIX);
            if (idx == 0) {
                String headerName = key.substring(PlaypenProxyConstants.HEADER_FORWARD_PREFIX.length());
                nettyRequest.headers().add(headerName, val);
            } else if (key.equalsIgnoreCase("Content-Length")) {
                nettyRequest.headers().add("Content-Length", val);
            }
        });
        if (!nettyRequest.headers().contains(HttpHeaderNames.HOST)) {
            nettyRequest.headers().add(HttpHeaderNames.HOST, "localhost");
        }

        log.debug("send initial nettyRequest");
        connection.sendMessage(nettyRequest);
        pollResponse.pipeTo(new NettyWriteStream(connection));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy