从 socket.getInpitStream() 读取时返回的二进制字符

Binary characters returned when reading from socket.getInpitStream()

package com.examenginedashboard.docker.utils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import com.examenginedashboard.CONSTANTS.MyValuesConstans; 
import com.examenginedashboard.codePG.service.HttpHijack;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.DockerCmdExecFactory;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.InspectExecResponse;
import com.github.dockerjava.api.model.Bind;
import com.github.dockerjava.api.model.ExposedPort;
import com.github.dockerjava.api.model.Ports;
import com.github.dockerjava.api.model.Volume;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.command.ExecStartResultCallback;
import com.github.dockerjava.jaxrs.JerseyDockerCmdExecFactory;

 public class APIBasedCodeCompiler {

    public static void connectToDocker(){
        DockerCmdExecFactory dockerCmdExecFactory = new                                             JerseyDockerCmdExecFactory()
        .withReadTimeout(1000)
        .withConnectTimeout(1000)
        .withMaxTotalConnections(100)
        .withMaxPerRouteConnections(10);
        ExposedPort tcp22 = ExposedPort.tcp(22);
        ExposedPort tcp23 = ExposedPort.tcp(23);


        Ports portBindings = new Ports();
        portBindings.bind(tcp22, Ports.Binding.bindPort(11022));
        portBindings.bind(tcp23, Ports.Binding.bindPort(11023));

DockerClientConfig config = DefaultDockerClientConfig. createDefaultConfigBuilder()
                    .withDockerHost("tcp://127.0.0.1:2375")
                    .build();

        DockerClient docker = DockerClientBuilder.getInstance(config).build();

        Volume volume1 = new Volume("/mydockerbuild"); 
      CreateContainerResponse containerResp = docker.createContainerCmd("busybox")
                .withImage(MyValuesConstans.JAVA_DOCKER)
                .withCmd("sh", "-c", "while :; do sleep 1; done")
                .withAttachStderr(true)
                .withAttachStdout(true)
                .withAttachStdin(true)
                .withVolumes(volume1)
                .withBinds(new Bind("/home/itcostcut/mydockerbuild",volume1))
                .exec();
        String containerId = containerResp.getId();
        docker.startContainerCmd(containerId).exec();
        System.out.println("HOST........... ");
        ByteArrayOutputStream stdout = new ByteArrayOutputStream();
        ByteArrayOutputStream stderr = new ByteArrayOutputStream();


      final String[] command = {"bash", "-c", "cd mydockerbuild/ && javac NumberToWord.java  && java -cp . NumberToWord exit"};
        ExecCreateCmdResponse execCreateCmdResponse = docker.execCreateCmd(containerId)
                .withAttachStdout(true)
                .withAttachStderr(true)
                .withAttachStdin(true)
                .withCmd(command)
                .exec();
        InspectExecResponse inpect = docker.inspectExecCmd(execCreateCmdResponse.getId()).exec();
        System.out.println("Inspect Info........... "+inpect);
     HashMap<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        HttpHijack ws;
        // http://localhost:4243
        //unix:///var/run/docker.sock

            docker.execStartCmd(execCreateCmdResponse.getId()).exec(
                    new ExecStartResultCallback(stdout, stderr));


          System.out.println("Output: "+stdout.toString());
          System.out.println("Error: "+stderr.toString());

        try {
            ws = new HttpHijack(new URI("http://127.0.0.1:2375/v1.27/exec/" + execCreateCmdResponse.getId() + "/start"));
             String payload = "{\"Detach\": false,\"Tty\": false}";
                ws.post(headers, payload);
                String request = "10";
                //Thread.sleep(3000);
                InputStream input = ws.send(request);
                int expectedDataLength=5000;
                ByteArrayOutputStream baos = new ByteArrayOutputStream(expectedDataLength);
                byte[] chunk = new byte[expectedDataLength];
                int numBytesJustRead;
                while((numBytesJustRead = input.read(chunk)) != -1) {
                    baos.write(chunk, 0, numBytesJustRead);
                }
                System.out.println("Result ............... "+baos.toString());


        } catch (URISyntaxException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }}
      public static void main(String[] args){   
      connectToDocker();
       }}

下面是连接到 Java Docker 的 HttpHijack.java class。

package com.examenginedashboard.codePG.service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Map.Entry;

import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HttpHijack {

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

private URI uri;

private Socket socket;

private boolean handshakeCompleted;

private InputStream chin;

private OutputStream chout;

public HttpHijack(URI url) {
    uri = url;
}

public void post(Map<String, String> headers, String payload) throws java.io.IOException {
    String host = uri.getHost();
    System.out.println("Hostname ........."+host);
    String path = uri.getPath();
    System.out.println("Path..............."+path);
    if (path.equals("")) {
        path = "/";
    }

    String query = uri.getQuery();
    System.out.println("Query................"+query);
    if (query != null) {
        path = path + "?" + query;
    }

    socket = createSocket();
    chout = socket.getOutputStream();
    StringBuffer extraHeaders = new StringBuffer();
    if (headers != null) {
        for (Entry<String, String> entry : headers.entrySet()) {
            extraHeaders.append(entry.getKey() + ": " + entry.getValue() + "\r\n");
        }
    }

    StringBuffer request = new StringBuffer();
    request.append("POST " + path + " HTTP/1.1\r\n");
    request.append("Upgrade: tcp\r\n");
    request.append("Connection: Upgrade\r\n");
    request.append("Host: " + host + "\r\n");
    if (headers != null) {
        for (Entry<String, String> entry : headers.entrySet()) {
            request.append(entry.getKey() + ": " + entry.getValue() + "\r\n");
        }
    }

    request.append("Content-Length: " + payload.length() + "\r\n");
    request.append("\r\n");
    request.append(payload);

    chout.write(request.toString().getBytes());
    chout.flush();

    chin = socket.getInputStream();
    BufferedReader reader = new BufferedReader(new InputStreamReader(chin));
    String header = reader.readLine();
    if (!header.equals("HTTP/1.1 101 UPGRADED")) {
        throw new IOException("Invalid handshake response: " + header);
    }

    do {
        header = reader.readLine();
        log.info("header: {}", header);
    } while (!header.equals(""));

    handshakeCompleted = true;
}

private Socket createSocket() throws java.io.IOException {
    String scheme = uri.getScheme();
    String host = uri.getHost();

    int port = uri.getPort();
    if (port == -1) {
        if (scheme.equals("https")) {
            port = 443;
        } else if (scheme.equals("http")) {
            port = 80;
        } else {
            throw new IllegalArgumentException("Unsupported scheme");
        }
    }

    if (scheme.equals("https")) {
        SocketFactory factory = SSLSocketFactory.getDefault();
        return factory.createSocket(host, port);
    } else {
        return new Socket(host, port);
    }
}

public InputStream send(String command) throws java.io.IOException {
    if (!handshakeCompleted) {
        throw new IllegalStateException("Handshake not complete");
    }

    chout.write(command.getBytes(StandardCharsets.UTF_8));
    System.out.println("Input Bytes ...... "+command.getBytes("UTF-8"));
    chout.flush();
    // looks like "exit" can't explicitly close the session,
    // shutdown output stream to force close it
    // so that stdout/stderr can be consumed via inputstream
        socket.shutdownOutput();
        System.out.println("Socket String .............. "+socket.toString());

        return socket.getInputStream();

}

public void close() throws java.io.IOException {
    chin.close();
    chout.close();
    socket.close();
}}

我将尝试解释我想要实现的目标。在我的场景中,我试图在 docker 上执行一些 Java 程序,因为我使用了 Java:7 docker 图像。 命令 = {"bash", "-c", "cd mydockerbuild/ && javac NumberToWord.java && java -cp . NumberToWord exit"};用于 运行 宁 java 代码和 NumberToWord.java 是 java class。我们可以将任何 java 代码带到 运行 但它应该需要一些输入才能执行,在我的情况下用户输入是 10.

问题是当我从 socket.getInputStream 读取输出时,输出的某些行是二进制字符,请参阅下面的日志。

May 27, 2017 3:46:14 PM com.examenginedashboard.codePG.
 service.HttpHijack  post
  INFO: header: Content-Type: application/vnd.
  docker.raw-stream
  May 27, 2017 3:46:14 PM com.examenginedashboard.
  codePG.service.HttpHijack post
 INFO: header: Connection: Upgrade
 May 27, 2017 3:46:14 PM com.examenginedashboard.
 codePG.service.HttpHijack post
 INFO: header: Upgrade: tcp
  May 27, 2017 3:46:14 PM com.examenginedashboard.
  codePG.service.HttpHijack post
 INFO: header: Api-Version: 1.27
 May 27, 2017 3:46:14 PM com.examenginedashboard.
 codePG.service.HttpHijack post
 INFO: header: Docker-Experimental: false
 May 27, 2017 3:46:14 PM com.examenginedashboard.
  codePG.service.HttpHijack post
 INFO: header: Server: Docker/17.03.1-ce (linux)
  May 27, 2017 3:46:14 PM com.examenginedashboard.
 codePG.service.HttpHijack post
 INFO: header: 
 Input Bytes ...... [B@6436a7db
 Socket String ..............    Socket[addr=/127.0.0.1,port=2375,
  localport=60804]
  Result ............... 

实际输出如图所示Final Output Here

问题出在 docker 原始流上。它将 stdoudstderr 复用到一个通道中。为此,在每一位内容之前,它发送一个 8 位 header.

  • 如果以下内容为stdout,则第一位为1,stderr为2,stdin为0。
  • 第二位、第三位和第四位始终为零
  • 最后4位是big endian中内容的长度。

要对此进行解码,请执行以下操作:

  1. 阅读header
  2. 确定内容长度
  3. 阅读内容(注意,即使内容是针对stderr的,我们不想要它,我们仍然需要阅读它)。
  4. 如果内容是实际内容,将其添加到缓冲区

以下测试针对您提供的数组执行此操作:

public static void main(String[] args) throws FileNotFoundException, IOException {

    byte[] data = new byte[] {1, 0, 0, 0, 0, 0, 0, 102, 89, 111, 117, 114, 32, 119, 111, 114, 100, 115, 32, 97, 114, 101, 32, 104, 101, 114, 101, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 32, 33, 33, 10, 89, 111, 117, 114, 32, 119, 111, 114, 100, 115, 32, 97, 114, 101, 32, 104, 101, 114, 101, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 32, 33, 33, 10, 89, 111, 117, 114, 32, 119, 111, 114, 100, 115, 32, 97, 114, 101, 32, 104, 101, 114, 101, 46, 46, 46, 46, 46, 46, 46, 46, 46,46, 46, 32, 33, 33, 10, 1, 0, 0, 0, 0, 0, 0, 21, 105, 110, 112, 117, 116, 32, 105, 110, 32, 87, 111, 114, 100, 115, 32, 58, 32, 32, 84, 101, 110, 1, 0, 0, 0, 0, 0, 0, 1, 10, 1, 0, 0, 0, 0, 0, 0, 19, 67, 79, 77, 69, 32, 79, 78, 32, 76, 73, 75, 69, 32, 73, 84, 32, 33, 33, 10};
    InputStream is = new ByteArrayInputStream(data); //I just created this for testing

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] header = new byte[8];
    while(true){
        if(!readFully(is, header, 8)) break;
        int contentSize = ((header[4] & 0xFF) << 24) | ((header[5] & 0xFF) << 16)
                | ((header[6] & 0xFF) << 8) | (header[7] & 0xFF);
        boolean actualContent = header[0] == 1;
        byte[] content = new byte[contentSize];
        if(!readFully(is, content, contentSize))break;
        if(actualContent)
            baos.write(content, 0, contentSize);
        else
            System.err.println(new String(content));
    }

    System.out.println(baos.toString());


}

/**
 * Read a given number of bytes from the stream and store them in the buffer.
 * 
 * @param   is
 *          The input stream to read from
 * @param   buffer
 *          The buffer to store it in
 * @size    size
 *          The number of bytes to read
 * 
 * @return  True if and only if all bytes were read.
 */
public static boolean readFully(InputStream is, byte[] buffer, int size) throws IOException{
    int totalAmountRead = 0;
    while(totalAmountRead < size){
        int amountJustRead = is.read(buffer, totalAmountRead, size-totalAmountRead);
        if(amountJustRead == -1)
            return false;
        totalAmountRead += amountJustRead;
    }

    return true;
}

这给出了输出:

Your words are here........... !!
Your words are here........... !!
Your words are here........... !!
input in Words :  Ten
COME ON LIKE IT !!