Skip to content

Instantly share code, notes, and snippets.

@missedone
Created June 22, 2015 15:15
Show Gist options
  • Save missedone/76517e618486db746056 to your computer and use it in GitHub Desktop.
Save missedone/76517e618486db746056 to your computer and use it in GitHub Desktop.
Http hijacking for docker startExec API call. (this is not a general purpose hijacking for any http request, not going to replace any http client)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;
import java.net.Socket;
import java.net.URI;
import java.util.Map;
import java.util.Map.Entry;
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();
String path = uri.getPath();
if (path.equals("")) {
path = "/";
}
String query = uri.getQuery();
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("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();
return socket.getInputStream();
}
public void close() throws java.io.IOException {
chin.close();
chout.close();
socket.close();
}
}
@highel
Copy link

highel commented Jun 23, 2015

Hi, I have used your code to implement a super pre-alpha implementation of direct file uploading via REST API into running container, if you have any comment write to boris@treukhov.com

https://github.com/highel/docker-rest-file-upload/blob/master/DockerRestFileUpload.java

@hiepsikhokhao
Copy link

Hi @missedone,

I used your httphijack class but i cannot send many command.
If i call send("ls") once time, it ok but when i call twice It causes error.
How can i use 1 socket stream and can write/read output many time. Please help me. Thanks

String cmd = "bash";
JSONObject input = new JSONObject();
input.put("AttachStdin", true);
input.put("AttachStdout", true);
input.put("Attachstderr", true);
input.put("DetachKeys", "ctrl-p,ctrl-q");
input.put("Tty", false);
JSONArray cmds = new JSONArray();
String[] commands = cmd.split(" ");
for (String c : commands) {
                cmds.add(c);
}
input.put("Cmd", cmds);
String content = CommonUtil.sendApiRequest(Configuration.SWARM_ADDRESS + "/containers/" + id + "/exec", "POST", input.toJSONString(), "", false);
JSONObject jcontent = CommonUtil.parseJSONObject(content);
String token = (String) jcontent.get("Id");
HttpHijack httpHijack = new HttpHijack(new URI(Configuration.SWARM_ADDRESS + "/exec/" + token + "/start"));
JSONObject jsonObject = new JSONObject();
jsonObject.put("Tty", false);
jsonObject.put("Detach", false);
HashMap<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");

httpHijack.post(headers, jsonObject.toString());
CommonUtil.sendApiRequest(Configuration.SWARM_ADDRESS + "/exec/" + token + "/resize?h=40&w=80", "POST", "", "text/plain", false);
InputStream send = httpHijack.send("ls\n");
send = httpHijack.send("ls\n");

Error log:

ERROR - Broken pipe
java.net.SocketException: Broken pipe
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:109)
at java.net.SocketOutputStream.write(SocketOutputStream.java:141)
at zcore.utilities.HttpHijack.send(HttpHijack.java:128)
at caas.web.handler.ApiController.consoleContainer(ApiController.java:1154)
at caas.web.handler.ApiController.doGet(ApiController.java:105)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:845)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:583)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:513)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:52)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:134)
at org.eclipse.jetty.server.Server.handle(Server.java:524)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:319)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:253)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:273)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:95)
at org.eclipse.jetty.io.SelectChannelEndPoint$2.run(SelectChannelEndPoint.java:93)
at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.executeProduceConsume(ExecuteProduceConsume.java:303)
at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.produceConsume(ExecuteProduceConsume.java:148)
at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.run(ExecuteProduceConsume.java:136)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:671)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:589)
at java.lang.Thread.run(Thread.java:745)

Thanks

@ajmalrehman
Copy link

HIi @missedone I am trying tho run a java program using docker-java api ,in my scenario it the java code requires input at runtime , below is my code. Please help how i can i use your program to attach Stdin at runtime.

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.HostConfig;
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://localhost:11023")
					.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)
        		.withExposedPorts(tcp22,tcp23)
        		.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 Program.java  && java -cp . Program exit"};
        ExecCreateCmdResponse execCreateCmdResponse = docker.execCreateCmd(containerId)
        		.withAttachStdout(true)
        		.withAttachStderr(true)
        		.withAttachStdin(true)
        		.withCmd("ls")
        		.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;
		try {
			ws = new HttpHijack(new URI("http://172.17.0.2:2375/v1.19/exec/" + execCreateCmdResponse.getId() + "/start"));
			 String payload = "{\"Detach\": false,\"Tty\": false}";
		        ws.post(headers, payload);
		        String request = "rock";
		        Thread.sleep(3000);
		        InputStream input = ws.send(request);
		        System.out.println("Request output......... "+input.toString());
		} catch (URISyntaxException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        try {
        	docker.execStartCmd(execCreateCmdResponse.getId()).exec(
			        new ExecStartResultCallback(stdout, stderr)).awaitCompletion();
        	
        
		  System.out.println("Output: "+stdout.toString());
		  System.out.println("Error: "+stderr.toString());
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

}

public static void main(String[] args){
	
	connectToDocker();
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment