Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: Extraction of HTTP Connection Manager in FullDuplexHttpStream #10010

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cli/src/main/java/hudson/cli/CLI.java
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ public static int _main(String[] _args) throws Exception {
for (Handler h : Logger.getLogger("").getHandlers()) {
h.setLevel(level);
}
for (Logger logger : new Logger[] {LOGGER, FullDuplexHttpStream.LOGGER, PlainCLIProtocol.LOGGER, Logger.getLogger("org.apache.sshd")}) { // perhaps also Channel
for (Logger logger : new Logger[] {LOGGER, HttpUploadDownloadStream.LOGGER, PlainCLIProtocol.LOGGER, Logger.getLogger("org.apache.sshd")}) { // perhaps also Channel
logger.setLevel(level);
}
args = args.subList(2, args.size());
Expand Down Expand Up @@ -413,7 +413,7 @@ private static int plainHttpConnection(String url, List<String> args, CLIConnect
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier((s, sslSession) -> true);
}
FullDuplexHttpStream streams = new FullDuplexHttpStream(new URL(url), "cli?remoting=false", factory.authorization);
HttpUploadDownloadStream streams = new HttpUploadDownloadStream(new URL(url), "cli?remoting=false", factory.authorization);
try (ClientSideImpl connection = new ClientSideImpl(new PlainCLIProtocol.FramedOutput(streams.getOutputStream()))) {
connection.start(args);
InputStream is = streams.getInputStream();
Expand Down
121 changes: 0 additions & 121 deletions cli/src/main/java/hudson/cli/FullDuplexHttpStream.java

This file was deleted.

50 changes: 50 additions & 0 deletions cli/src/main/java/hudson/cli/HttpConnectionManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package hudson.cli;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.UUID;

public class HttpConnectionManager {

private static final String DOWNLOAD_SIDE = "download";
private static final String UPLOAD_SIDE = "upload";
private static final String SESSION_HEADER = "Session";
private static final String SIDE_HEADER = "Side";
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String HUDSON_DUPLEX_HEADER = "Hudson-Duplex";
private static final String CONTENT_TYPE_HEADER = "Content-type";

private final String authorization;
private final UUID uuid;

public HttpConnectionManager(String authorization) {
this.authorization = authorization;
this.uuid = UUID.randomUUID();
}

public HttpURLConnection createConnection(URL target, String side) throws IOException {
HttpURLConnection connection = (HttpURLConnection) target.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.addRequestProperty(SESSION_HEADER, uuid.toString());
connection.addRequestProperty(SIDE_HEADER, side);
if (authorization != null) {
connection.addRequestProperty(AUTHORIZATION_HEADER, authorization);
}
return connection;
}

public HttpURLConnection createDownloadConnection(URL target) throws IOException {
HttpURLConnection connection = createConnection(target, DOWNLOAD_SIDE);
connection.getOutputStream().close(); // Server expects a closed output stream for download
return connection;
}

public HttpURLConnection createUploadConnection(URL target) throws IOException {
HttpURLConnection connection = createConnection(target, UPLOAD_SIDE);
connection.setChunkedStreamingMode(0); // Unlimited data stream
connection.setRequestProperty(CONTENT_TYPE_HEADER, "application/octet-stream");
return connection;
}
}
71 changes: 71 additions & 0 deletions cli/src/main/java/hudson/cli/HttpUploadDownloadStream.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package hudson.cli;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Creates a capacity-unlimited bi-directional {@link InputStream}/{@link OutputStream} pair over
* HTTP, which is a request/response protocol.
* {@code FullDuplexHttpService} is the counterpart on the server side.
*/
public class HttpUploadDownloadStream {
private final URL base;
private final OutputStream output;
private final InputStream input;

private static final Logger LOGGER = Logger.getLogger(HttpUploadDownloadStream.class.getName());

public HttpUploadDownloadStream(URL base, String relativeTarget, String authorization) throws IOException {
if (!base.toString().endsWith("/")) {
throw new IllegalArgumentException(base.toString());
}
if (relativeTarget.startsWith("/")) {
throw new IllegalArgumentException(relativeTarget);
}

this.base = tryToResolveRedirects(base, authorization);
URL target = new URL(this.base, relativeTarget);

HttpConnectionManager connectionManager = new HttpConnectionManager(authorization);

// Establish download side connection
LOGGER.fine("establishing download side");
HttpURLConnection con = connectionManager.createDownloadConnection(target);
input = con.getInputStream();

if (con.getHeaderField("Hudson-Duplex") == null) {
throw new CLI.NotTalkingToJenkinsException("No Jenkins running at " + target + ", or not serving HTTP Duplex");
}
LOGGER.fine("established download side");

// Establish upload side connection
LOGGER.fine("establishing upload side");
con = connectionManager.createUploadConnection(target);
output = con.getOutputStream();
LOGGER.fine("established upload side");
}

private URL tryToResolveRedirects(URL base, String authorization) {
try {
HttpURLConnection con = new HttpConnectionManager(authorization).createDownloadConnection(base);
con.getInputStream().close();
base = con.getURL();
} catch (Exception ex) {
LOGGER.log(Level.FINE, "Failed to resolve potential redirects", ex);
}
return base;
}

public InputStream getInputStream() {
return input;
}

public OutputStream getOutputStream() {
return output;
}
}
4 changes: 2 additions & 2 deletions core/src/main/java/jenkins/util/FullDuplexHttpService.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
package jenkins.util;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.cli.FullDuplexHttpStream;
import hudson.cli.HttpUploadDownloadStream;
import hudson.model.RootAction;
import hudson.security.csrf.CrumbExclusion;
import hudson.util.ChunkedInputStream;
Expand All @@ -47,7 +47,7 @@
import org.kohsuke.stapler.StaplerResponse2;

/**
* Server-side counterpart to {@link FullDuplexHttpStream}.
* Server-side counterpart to {@link HttpUploadDownloadStream}.
* <p>
* To use, bind this to an endpoint with {@link RootAction} (you will also need a {@link CrumbExclusion}).
* @since 2.54
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

import static org.junit.Assert.assertEquals;

import hudson.cli.FullDuplexHttpStream;
import hudson.cli.HttpUploadDownloadStream;
import hudson.model.InvisibleAction;
import hudson.model.RootAction;
import hudson.security.csrf.CrumbExclusion;
Expand Down Expand Up @@ -56,12 +56,12 @@ public class FullDuplexHttpServiceTest {
public JenkinsRule r = new JenkinsRule();

@Rule
public LoggerRule logging = new LoggerRule().record(FullDuplexHttpService.class, Level.FINE).record(FullDuplexHttpStream.class, Level.FINE);
public LoggerRule logging = new LoggerRule().record(FullDuplexHttpService.class, Level.FINE).record(HttpUploadDownloadStream.class, Level.FINE);

@Test
public void smokes() throws Exception {
logging.record("org.eclipse.jetty", Level.ALL);
FullDuplexHttpStream con = new FullDuplexHttpStream(r.getURL(), "test/", null);
HttpUploadDownloadStream con = new HttpUploadDownloadStream(r.getURL(), "test/", null);
InputStream is = con.getInputStream();
OutputStream os = con.getOutputStream();
os.write(33);
Expand Down