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

de.gesellix.docker.engine.OkResponseCallback Maven / Gradle / Ivy

package de.gesellix.docker.engine;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
import okio.BufferedSink;
import okio.Okio;
import okio.Source;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

public class OkResponseCallback implements Callback {

  private static final Logger log = LoggerFactory.getLogger(OkResponseCallback.class);

  private final ConnectionProvider connectionProvider;
  private final AttachConfig attachConfig;

  public OkResponseCallback(ConnectionProvider connectionProvider, AttachConfig attachConfig) {
    this.connectionProvider = connectionProvider;
    this.attachConfig = attachConfig;
  }

  @Override
  public void onFailure(@NotNull Call call, @NotNull final IOException e) {
    log.error("connection failed: " + e.getMessage(), e);
    attachConfig.onFailure(e);
  }

  public void onFailure(Exception e) {
    log.error("error", e);
    attachConfig.onFailure(e);
  }

  @Override
  public void onResponse(@NotNull final Call call, @NotNull final Response response) throws IOException {
    TcpUpgradeVerificator.ensureTcpUpgrade(response);

    if (attachConfig.getStreams().getStdin() != null) {
      // pass input from the client via stdin and pass it to the output stream
      // running it in an own thread allows the client to gain back control
      final Source stdinSource = Okio.source(attachConfig.getStreams().getStdin());
      Thread writer = new Thread(() -> {
        try {
          final BufferedSink bufferedSink = Okio.buffer(getConnectionProvider().getSink());
          bufferedSink.writeAll(stdinSource);
          bufferedSink.flush();
          attachConfig.onSinkWritten(response);
          CountDownLatch done = new CountDownLatch(1);
          delayed(100, "writer", () -> {
            try {
              bufferedSink.close();
              attachConfig.onSinkClosed(response);
            }
            catch (Exception e) {
              log.warn("error", e);
            }
            return null;
          }, done);
          done.await(5, TimeUnit.SECONDS);
        }
        catch (InterruptedException e) {
          log.debug("stdin->sink interrupted", e);
          Thread.currentThread().interrupt();
        }
        catch (Exception e) {
          onFailure(e);
        }
        finally {
          log.trace("writer finished");
        }
      });
      writer.setName("stdin-writer " + call.request().url().encodedPath());
      writer.start();
    }
    else {
      log.debug("no stdin.");
    }

    if (attachConfig.getStreams().getStdout() != null) {
      final BufferedSink bufferedStdout = Okio.buffer(Okio.sink(attachConfig.getStreams().getStdout()));
      Thread reader = new Thread(() -> {
        try {
          bufferedStdout.writeAll(getConnectionProvider().getSource());
          bufferedStdout.flush();
          CountDownLatch done = new CountDownLatch(1);
          delayed(100, "reader", () -> {
            attachConfig.onSourceConsumed();
            return null;
          }, done);
          done.await(5, TimeUnit.SECONDS);
        }
        catch (InterruptedException e) {
          log.debug("source->stdout interrupted", e);
          Thread.currentThread().interrupt();
        }
        catch (Exception e) {
          onFailure(e);
        }
        finally {
          log.trace("reader finished");
        }
      });
      reader.setName("stdout-reader " + call.request().url().encodedPath());
      reader.start();
    }
    else {
      log.debug("no stdout.");
    }

    attachConfig.onResponse(response);
  }

  public static void delayed(long delay, String name, final Supplier action, final CountDownLatch done) {
    new Timer(true).schedule(new TimerTask() {
      @Override
      public void run() {
        Thread.currentThread().setName("Delayed " + name + " action (" + Thread.currentThread().getName() + ")");
        try {
          action.get();
        }
        finally {
          done.countDown();
          cancel();
        }
      }
    }, delay);
  }

  public ConnectionProvider getConnectionProvider() {
    return connectionProvider;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy