/* * Copyright (c) 2014 Spotify AB. * * 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.jenkinsci.plugins.dockerbuildstep.log.container; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.io.ByteStreams.copy; import static com.google.common.io.ByteStreams.nullOutputStream; import java.io.Closeable; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import com.google.common.io.ByteStreams; /** * Stream support should be rolled into docker-java, see https://github.com/docker-java/docker-java/issues/42. * * As a workaround we borrowed the decoding magic from https://github.com/spotify/docker-client. * * @see https://github.com/spotify/docker-client/blob/master/src/main/java/com/spotify/docker/client/LogReader.java */ public class DockerLogStreamReader implements Closeable { private final InputStream stream; public static final int HEADER_SIZE = 8; public static final int FRAME_SIZE_OFFSET = 4; private volatile boolean closed; public DockerLogStreamReader(InputStream is) { this.stream = is; } public DockerLogMessage nextMessage() throws IOException { // Read header //TODO header is sent only when TTY is disabled, when enabled, it sends raw stream without any headers final byte[] headerBytes = new byte[HEADER_SIZE]; final int n = ByteStreams.read(stream, headerBytes, 0, HEADER_SIZE); if (n == 0) { return null; } if (n != HEADER_SIZE) { throw new EOFException(); } final ByteBuffer header = ByteBuffer.wrap(headerBytes); final int streamId = header.get(); header.position(FRAME_SIZE_OFFSET); final int frameSize = header.getInt(); // Read frame final byte[] frame = new byte[frameSize]; ByteStreams.readFully(stream, frame); return new DockerLogMessage(streamId, ByteBuffer.wrap(frame)); } @Override protected void finalize() throws Throwable { super.finalize(); if (!closed) { close(); } } public void close() throws IOException { closed = true; // Jersey will close the stream and release the connection after we read // all the data. // We cannot call the stream's close method because it an instance of // UncloseableInputStream, // where close is a no-op. copy(stream, new OutputStream() { /** Discards the specified byte. */ @Override public void write(int b) { } /** Discards the specified byte array. */ @Override public void write(byte[] b) { checkNotNull(b); } /** Discards the specified byte array. */ @Override public void write(byte[] b, int off, int len) { checkNotNull(b); } @Override public String toString() { return "ByteStreams.nullOutputStream()"; }; }); } }