提交 ca0b6e83 编写于 作者: K kohsuke

I discovered that when Apache sits in front of Hudson as a reverse proxy, it...

I discovered that when Apache sits in front of Hudson as a reverse proxy, it blocks the traffic in such a way that breaks tunneling of the remoting.

The issue is that the CLI initiates the upload side of the connection and sends some small chunk, yet Apache never forwads that to Hudson, presumably because it's trying to buffer more contents from CLI, which never arrives.

A similar problem seems to exist for the download side, although various reports (like http://mail-archives.apache.org/mod_mbox/httpd-users/200509.mbox/%3C20050927152742.GA8726@redhat.com%3E) suggests that this is fixed in a relatively recent version of Apache.

Other pages, like http://httpd.apache.org/docs/2.2/mod/mod_proxy.html , seems to suggest that Apache can be configured not to do this buffering, but my attempt to configure them failed, both on Ubuntu and OpenSolaris. It could be my mistake, but the lesson for me is that even if Apache can be configured not to interfere, it's still rather error prone.

On top of that, it's not hard to imagine that other forward/reverse proxy have similar problems --- IIRC, Dean Yu reported a similar problem.

So I decided to let CLI talk to Hudson via a conventional TCP/IP connection, if possible. An extra bonus is that this is more resource efficient on the server side (which was tying 2 request handling for pushing and pulling before this change.)

The downside of this change is that if the client is behind the proxy, or if the server is behind a reverse proxy, this needs an extra set up. I thought this is a lesser evil, as Hudson tends to run inside a corporate network, and this issue is already better understood since JNLP slaves have the same issue.

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@19378 71c3de6d-444a-0410-be80-ed276b4c234a
上级 c8b13169
......@@ -29,15 +29,21 @@ import hudson.remoting.RemoteOutputStream;
import hudson.remoting.PingThread;
import java.net.URL;
import java.net.URLConnection;
import java.net.Socket;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.ArrayList;
import java.util.logging.Logger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.DataOutputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
/**
* CLI entry point to Hudson.
......@@ -57,20 +63,38 @@ public class CLI {
public CLI(URL hudson, ExecutorService exec) throws IOException, InterruptedException {
String url = hudson.toExternalForm();
if(!url.endsWith("/")) url+='/';
url+="cli";
hudson = new URL(url);
FullDuplexHttpStream con = new FullDuplexHttpStream(hudson);
ownsPool = exec==null;
pool = exec!=null ? exec : Executors.newCachedThreadPool();
channel = new Channel("Chunked connection to "+hudson,
pool,con.getInputStream(),con.getOutputStream());
new PingThread(channel,30*1000) {
protected void onDead() {
// noop. the point of ping is to keep the connection alive
// as most HTTP servers have a rather short read time out
}
}.start();
int clip = getCliTcpPort(url);
if(clip>=0) {
// connect via CLI port
String host = new URL(url).getHost();
LOGGER.fine("Trying to connect directly via TCP/IP to port "+clip+" of "+host);
Socket s = new Socket(host,clip);
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
dos.writeUTF("Protocol:CLI-connect");
channel = new Channel("channel", pool,
new BufferedInputStream(s.getInputStream()),
new BufferedOutputStream(s.getOutputStream()));
} else {
// connect via HTTP
LOGGER.fine("Trying to connect to "+url+" via HTTP");
url+="cli";
hudson = new URL(url);
FullDuplexHttpStream con = new FullDuplexHttpStream(hudson);
channel = new Channel("Chunked connection to "+hudson,
pool,con.getInputStream(),con.getOutputStream());
new PingThread(channel,30*1000) {
protected void onDead() {
// noop. the point of ping is to keep the connection alive
// as most HTTP servers have a rather short read time out
}
}.start();
}
// execute the command
entryPoint = (CliEntryPoint)channel.waitForRemoteProperty(CliEntryPoint.class.getName());
......@@ -79,6 +103,17 @@ public class CLI {
throw new IOException(Messages.CLI_VersionMismatch());
}
/**
* If the server advertises CLI port, returns it.
*/
private int getCliTcpPort(String url) throws IOException {
URLConnection head = new URL(url).openConnection();
head.connect();
String p = head.getHeaderField("X-Hudson-CLI-Port");
if(p==null) return -1;
return Integer.parseInt(p);
}
public void close() throws IOException, InterruptedException {
channel.close();
channel.join();
......@@ -132,4 +167,6 @@ public class CLI {
System.err.println(Messages.CLI_Usage());
System.exit(-1);
}
private static final Logger LOGGER = Logger.getLogger(CLI.class.getName());
}
......@@ -24,9 +24,15 @@
package hudson;
import hudson.model.Hudson;
import hudson.model.Computer;
import hudson.slaves.SlaveComputer;
import hudson.remoting.Channel;
import hudson.remoting.SocketOutputStream;
import hudson.remoting.SocketInputStream;
import hudson.remoting.Channel.Listener;
import hudson.remoting.Channel.Mode;
import hudson.cli.CliManagerImpl;
import hudson.cli.CliEntryPoint;
import java.io.DataInputStream;
import java.io.IOException;
......@@ -40,7 +46,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Listens to incoming TCP connections from JNLP slave agents.
* Listens to incoming TCP connections from JNLP slave agents and CLI.
*
* <h2>Security</h2>
* <p>
......@@ -151,6 +157,8 @@ public final class TcpSlaveAgentListener extends Thread {
String protocol = s.substring(9);
if(protocol.equals("JNLP-connect")) {
runJnlpConnect(in, out);
} else if(protocol.equals("CLI-connect")) {
runCliConnect(in, out);
} else {
error(out, "Unknown protocol:" + s);
}
......@@ -174,6 +182,19 @@ public final class TcpSlaveAgentListener extends Thread {
}
}
/**
* Handles CLI connection request.
*/
private void runCliConnect(DataInputStream in, PrintWriter out) throws IOException, InterruptedException {
out.println("Welcome");
Channel channel = new Channel("CLI channel from " + s.getInetAddress(),
Computer.threadPoolForRemoting, Mode.BINARY,
new BufferedInputStream(new SocketInputStream(this.s)),
new BufferedOutputStream(new SocketOutputStream(this.s)), null, true);
channel.setProperty(CliEntryPoint.class.getName(),new CliManagerImpl(null));
channel.join();
}
/**
* Handles JNLP slave agent connection request.
*/
......
......@@ -25,6 +25,10 @@ THE SOFTWARE.
<st:compress xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
<j:if test="${request.servletPath=='/'}">
<st:header name="X-Hudson" value="${servletContext.getAttribute('version')}" />
<j:if test="${app.tcpSlaveAgentListener!=null}">
<!-- advertize the CLI TCP port -->
<st:header name="X-Hudson-CLI-Port" value="${app.tcpSlaveAgentListener.port}" />
</j:if>
</j:if>
<j:new var="h" className="hudson.Functions" /><!-- instead of JSP functions -->
<l:layout title="${it.class.name=='hudson.model.AllView' ? '%Dashboard' : it.viewName}">
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册