Mercurial > jhg
changeset 687:9859fcea475d
Towards ssh remote repositories: refactor HgRemoteRepository - move http related code to HttpConnector
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Sat, 27 Jul 2013 18:34:14 +0200 |
parents | f1f095e42555 |
children | 1499139a600a |
files | src/org/tmatesoft/hg/internal/remote/Connector.java src/org/tmatesoft/hg/internal/remote/HttpConnector.java src/org/tmatesoft/hg/internal/remote/SshConnector.java src/org/tmatesoft/hg/repo/HgRemoteRepository.java |
diffstat | 4 files changed, 619 insertions(+), 374 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/internal/remote/Connector.java Sat Jul 27 18:34:14 2013 +0200 @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2013 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.internal.remote; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.util.Collection; +import java.util.List; + +import org.tmatesoft.hg.core.HgRemoteConnectionException; +import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.core.SessionContext; +import org.tmatesoft.hg.repo.HgRuntimeException; +import org.tmatesoft.hg.repo.HgRemoteRepository.Range; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public interface Connector { + static final String CMD_HELLO = "hello"; // TODO enum + static final String CMD_CAPABILITIES = "capabilities"; + static final String CMD_HEADS = "heads"; + static final String CMD_BETWEEN = "between"; + static final String CMD_BRANCHES = "branches"; + static final String CMD_CHANGEGROUP = "changegroup"; + static final String CMD_UNBUNDLE = "unbundle"; + static final String CMD_PUSHKEY = "pushkey"; + static final String CMD_LISTKEYS = "listkeys"; + static final String NS_BOOKMARKS = "bookmarks"; + static final String NS_PHASES = "phases"; + + void init(URL url, SessionContext sessionContext, Object globalConfig) throws HgRuntimeException; + String getServerLocation(); + // + void connect() throws HgRemoteConnectionException, HgRuntimeException; + void disconnect() throws HgRemoteConnectionException, HgRuntimeException; + void sessionBegin() throws HgRemoteConnectionException, HgRuntimeException; + void sessionEnd() throws HgRemoteConnectionException, HgRuntimeException; + // + String getCapabilities() throws HgRemoteConnectionException, HgRuntimeException; + + InputStream heads() throws HgRemoteConnectionException, HgRuntimeException; + InputStream between(Collection<Range> ranges) throws HgRemoteConnectionException, HgRuntimeException; + InputStream branches(List<Nodeid> nodes) throws HgRemoteConnectionException, HgRuntimeException; + InputStream changegroup(List<Nodeid> roots) throws HgRemoteConnectionException, HgRuntimeException; + OutputStream unbundle(long outputLen, List<Nodeid> remoteHeads) throws HgRemoteConnectionException, HgRuntimeException; + InputStream pushkey(String opName, String namespace, String key, String oldValue, String newValue) throws HgRemoteConnectionException, HgRuntimeException; + InputStream listkeys(String namespace, String actionName) throws HgRemoteConnectionException, HgRuntimeException; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/internal/remote/HttpConnector.java Sat Jul 27 18:34:14 2013 +0200 @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2013 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.internal.remote; + +import static org.tmatesoft.hg.util.LogFacility.Severity.Info; + +import java.io.BufferedReader; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.tmatesoft.hg.core.HgRemoteConnectionException; +import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.core.SessionContext; +import org.tmatesoft.hg.internal.PropertyMarshal; +import org.tmatesoft.hg.repo.HgRemoteRepository.Range; +import org.tmatesoft.hg.repo.HgRuntimeException; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class HttpConnector implements Connector { + private URL url; + private SSLContext sslContext; + private String authInfo; + private boolean debug; + private SessionContext sessionCtx; + // + private HttpURLConnection conn; + + public void init(URL url, SessionContext sessionContext, Object globalConfig) throws HgRuntimeException { + this.url = url; + sessionCtx = sessionContext; + debug = new PropertyMarshal(sessionCtx).getBoolean("hg4j.remote.debug", false); + if (url.getUserInfo() != null) { + String ai = null; + try { + // Hack to get Base64-encoded credentials + Preferences tempNode = Preferences.userRoot().node("xxx"); + tempNode.putByteArray("xxx", url.getUserInfo().getBytes()); + ai = tempNode.get("xxx", null); + tempNode.removeNode(); + } catch (BackingStoreException ex) { + sessionContext.getLog().dump(getClass(), Info, ex, null); + // IGNORE + } + authInfo = ai; + } else { + authInfo = null; + } + } + + public void connect() throws HgRemoteConnectionException, HgRuntimeException { + if ("https".equals(url.getProtocol())) { + try { + sslContext = SSLContext.getInstance("SSL"); + class TrustEveryone implements X509TrustManager { + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + if (debug) { + System.out.println("checkClientTrusted:" + authType); + } + } + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + if (debug) { + System.out.println("checkServerTrusted:" + authType); + } + } + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }; + sslContext.init(null, new TrustManager[] { new TrustEveryone() }, null); + } catch (Exception ex) { + throw new HgRemoteConnectionException("Can't initialize secure connection", ex); + } + } else { + sslContext = null; + } + } + + public void disconnect() throws HgRemoteConnectionException, HgRuntimeException { + // TODO Auto-generated method stub + + } + + public void sessionBegin() throws HgRemoteConnectionException, HgRuntimeException { + // TODO Auto-generated method stub + + } + + public void sessionEnd() throws HgRemoteConnectionException, HgRuntimeException { + if (conn != null) { + conn.disconnect(); + conn = null; + } + } + + public String getServerLocation() { + if (url.getUserInfo() == null) { + return url.toExternalForm(); + } + if (url.getPort() != -1) { + return String.format("%s://%s:%d%s", url.getProtocol(), url.getHost(), url.getPort(), url.getPath()); + } else { + return String.format("%s://%s%s", url.getProtocol(), url.getHost(), url.getPath()); + } + } + + public String getCapabilities() throws HgRemoteConnectionException { + // say hello to server, check response + try { + URL u = new URL(url, url.getPath() + "?cmd=hello"); + HttpURLConnection c = setupConnection(u.openConnection()); + c.connect(); + if (debug) { + dumpResponseHeader(u); + } + BufferedReader r = new BufferedReader(new InputStreamReader(c.getInputStream(), "US-ASCII")); + String line = r.readLine(); + c.disconnect(); + final String capsPrefix = CMD_CAPABILITIES + ':'; + if (line != null && line.startsWith(capsPrefix)) { + return line.substring(capsPrefix.length()).trim(); + } + // for whatever reason, some servers do not respond to hello command (e.g. svnkit) + // but respond to 'capabilities' instead. Try it. + // TODO [post-1.0] tests needed + u = new URL(url, url.getPath() + "?cmd=capabilities"); + c = setupConnection(u.openConnection()); + c.connect(); + if (debug) { + dumpResponseHeader(u); + } + r = new BufferedReader(new InputStreamReader(c.getInputStream(), "US-ASCII")); + line = r.readLine(); + c.disconnect(); + if (line != null && line.startsWith(capsPrefix)) { + return line.substring(capsPrefix.length()).trim(); + } + return new String(); + } catch (MalformedURLException ex) { + throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand(CMD_HELLO).setServerInfo(getServerLocation()); + } catch (IOException ex) { + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(CMD_HELLO).setServerInfo(getServerLocation()); + } + } + + public InputStream heads() throws HgRemoteConnectionException, HgRuntimeException { + try { + URL u = new URL(url, url.getPath() + "?cmd=heads"); + conn = setupConnection(u.openConnection()); + conn.connect(); + if (debug) { + dumpResponseHeader(u); + } + return conn.getInputStream(); + } catch (MalformedURLException ex) { + throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand(CMD_HEADS).setServerInfo(getServerLocation()); + } catch (IOException ex) { + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(CMD_HEADS).setServerInfo(getServerLocation()); + } + } + + public InputStream between(Collection<Range> ranges) throws HgRemoteConnectionException, HgRuntimeException { + StringBuilder sb = new StringBuilder(20 + ranges.size() * 82); + sb.append("pairs="); + for (Range r : ranges) { + r.append(sb); + sb.append('+'); + } + if (sb.charAt(sb.length() - 1) == '+') { + // strip last space + sb.setLength(sb.length() - 1); + } + try { + boolean usePOST = ranges.size() > 3; + URL u = new URL(url, url.getPath() + "?cmd=between" + (usePOST ? "" : '&' + sb.toString())); + conn = setupConnection(u.openConnection()); + if (usePOST) { + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Length", String.valueOf(sb.length()/*nodeids are ASCII, bytes == characters */)); + conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + conn.setDoOutput(true); + conn.connect(); + OutputStream os = conn.getOutputStream(); + os.write(sb.toString().getBytes()); + os.flush(); + os.close(); + } else { + conn.connect(); + } + if (debug) { + System.out.printf("%d ranges, method:%s \n", ranges.size(), conn.getRequestMethod()); + dumpResponseHeader(u); + } + return conn.getInputStream(); + } catch (MalformedURLException ex) { + throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand(CMD_BETWEEN).setServerInfo(getServerLocation()); + } catch (IOException ex) { + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(CMD_BETWEEN).setServerInfo(getServerLocation()); + } + } + + public InputStream branches(List<Nodeid> nodes) throws HgRemoteConnectionException, HgRuntimeException { + StringBuilder sb = appendNodeidListArgument("nodes", nodes, null); + try { + URL u = new URL(url, url.getPath() + "?cmd=branches&" + sb.toString()); + conn = setupConnection(u.openConnection()); + conn.connect(); + if (debug) { + dumpResponseHeader(u); + } + return conn.getInputStream(); + } catch (MalformedURLException ex) { + throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand(CMD_BRANCHES).setServerInfo(getServerLocation()); + } catch (IOException ex) { + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(CMD_BRANCHES).setServerInfo(getServerLocation()); + } + } + + public InputStream changegroup(List<Nodeid> roots) throws HgRemoteConnectionException, HgRuntimeException { + StringBuilder sb = appendNodeidListArgument("roots", roots, null); + try { + URL u = new URL(url, url.getPath() + "?cmd=changegroup&" + sb.toString()); + conn = setupConnection(u.openConnection()); + conn.connect(); + if (debug) { + dumpResponseHeader(u); + } + return conn.getInputStream(); + } catch (MalformedURLException ex) { // XXX in fact, this exception might be better to be re-thrown as RuntimeEx, + // as there's little user can do about this issue (URLs are constructed by our code) + throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("changegroup").setServerInfo(getServerLocation()); + } catch (IOException ex) { + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("changegroup").setServerInfo(getServerLocation()); + } + } + + // + // FIXME consider HttpURLConnection#setChunkedStreamingMode() as described at + // http://stackoverflow.com/questions/2793150/how-to-use-java-net-urlconnection-to-fire-and-handle-http-requests + public OutputStream unbundle(long outputLen, List<Nodeid> remoteHeads) throws HgRemoteConnectionException, HgRuntimeException { + StringBuilder sb = appendNodeidListArgument(CMD_HEADS, remoteHeads, null); + try { + final URL u = new URL(url, url.getPath() + "?cmd=unbundle&" + sb.toString()); + conn = setupConnection(u.openConnection()); + conn.setRequestMethod("POST"); + conn.setDoOutput(true); + conn.setRequestProperty("Content-Type", "application/mercurial-0.1"); + conn.setRequestProperty("Content-Length", String.valueOf(outputLen)); + conn.connect(); + return new FilterOutputStream(conn.getOutputStream()) { + public void close() throws IOException { + super.close(); + if (debug) { + dumpResponseHeader(u); + dumpResponse(); + } + try { + checkResponseOk("Push", CMD_UNBUNDLE); + } catch (HgRemoteConnectionException ex) { + IOException e = new IOException(ex.getMessage()); + // not e.initCause(ex); as HgRemoteConnectionException is just a message holder + e.setStackTrace(ex.getStackTrace()); + throw e; + } + } + }; + } catch (MalformedURLException ex) { + throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand(CMD_UNBUNDLE).setServerInfo(getServerLocation()); + } catch (IOException ex) { + // FIXME consume c.getErrorStream as http://docs.oracle.com/javase/6/docs/technotes/guides/net/http-keepalive.html suggests + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(CMD_UNBUNDLE).setServerInfo(getServerLocation()); + } + } + + public InputStream pushkey(String opName, String namespace, String key, String oldValue, String newValue) throws HgRemoteConnectionException, HgRuntimeException { + try { + final String p = String.format("%s?cmd=pushkey&namespace=%s&key=%s&old=%s&new=%s", url.getPath(), namespace, key, oldValue, newValue); + URL u = new URL(url, p); + conn = setupConnection(u.openConnection()); + conn.setRequestMethod("POST"); + conn.connect(); + if (debug) { + dumpResponseHeader(u); + } + checkResponseOk(opName, "pushkey"); + return conn.getInputStream(); + } catch (MalformedURLException ex) { + throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("pushkey").setServerInfo(getServerLocation()); + } catch (IOException ex) { + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("pushkey").setServerInfo(getServerLocation()); + } + } + + public InputStream listkeys(String namespace, String actionName) throws HgRemoteConnectionException, HgRuntimeException { + try { + URL u = new URL(url, url.getPath() + "?cmd=listkeys&namespace=" + namespace); + conn = setupConnection(u.openConnection()); + conn.connect(); + if (debug) { + dumpResponseHeader(u); + } + checkResponseOk(actionName, "listkeys"); + return conn.getInputStream(); + } catch (MalformedURLException ex) { + throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand(CMD_LISTKEYS).setServerInfo(getServerLocation()); + } catch (IOException ex) { + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(CMD_LISTKEYS).setServerInfo(getServerLocation()); + } + } + + private void checkResponseOk(String opName, String remoteCmd) throws HgRemoteConnectionException, IOException { + if (conn.getResponseCode() != 200) { + String m = conn.getResponseMessage() == null ? "unknown reason" : conn.getResponseMessage(); + String em = String.format("%s failed: %s (HTTP error:%d)", opName, m, conn.getResponseCode()); + throw new HgRemoteConnectionException(em).setRemoteCommand(remoteCmd).setServerInfo(getServerLocation()); + } + } + + private HttpURLConnection setupConnection(URLConnection urlConnection) { + urlConnection.setRequestProperty("User-Agent", "hg4j/1.0.0"); + urlConnection.addRequestProperty("Accept", "application/mercurial-0.1"); + if (authInfo != null) { + urlConnection.addRequestProperty("Authorization", "Basic " + authInfo); + } + if (sslContext != null) { + ((HttpsURLConnection) urlConnection).setSSLSocketFactory(sslContext.getSocketFactory()); + } + return (HttpURLConnection) urlConnection; + } + + private StringBuilder appendNodeidListArgument(String key, List<Nodeid> values, StringBuilder sb) { + if (sb == null) { + sb = new StringBuilder(20 + values.size() * 41); + } + sb.append(key); + sb.append('='); + for (Nodeid n : values) { + sb.append(n.toString()); + sb.append('+'); + } + if (sb.charAt(sb.length() - 1) == '+') { + // strip last space + sb.setLength(sb.length() - 1); + } + return sb; + } + + private void dumpResponseHeader(URL u) { + System.out.printf("Query (%d bytes):%s\n", u.getQuery().length(), u.getQuery()); + System.out.println("Response headers:"); + final Map<String, List<String>> headerFields = conn.getHeaderFields(); + for (String s : headerFields.keySet()) { + System.out.printf("%s: %s\n", s, conn.getHeaderField(s)); + } + } + + private void dumpResponse() throws IOException { + if (conn.getContentLength() > 0) { + final Object content = conn.getContent(); + System.out.println(content); + } + } +}
--- a/src/org/tmatesoft/hg/internal/remote/SshConnector.java Thu Jul 25 22:12:14 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/remote/SshConnector.java Sat Jul 27 18:34:14 2013 +0200 @@ -21,24 +21,20 @@ import java.io.EOFException; import java.io.File; import java.io.FilterInputStream; +import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.URL; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; import org.tmatesoft.hg.core.HgRemoteConnectionException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.core.SessionContext; -import org.tmatesoft.hg.internal.Internals; -import org.tmatesoft.hg.repo.HgBundle; import org.tmatesoft.hg.repo.HgRemoteRepository.Range; import org.tmatesoft.hg.repo.HgRuntimeException; import org.tmatesoft.hg.util.LogFacility.Severity; @@ -54,7 +50,7 @@ * @author Artem Tikhomirov * @author TMate Software Ltd. */ -public class SshConnector { +public class SshConnector implements Connector { private SessionContext sessionCtx; private URL url; private Connection conn; @@ -64,9 +60,12 @@ private StreamGobbler remoteErr, remoteOut; private OutputStream remoteIn; - public void connect(URL url, SessionContext sessionContext, Object globalConfig) throws HgRemoteConnectionException { + public void init(URL url, SessionContext sessionContext, Object globalConfig) throws HgRuntimeException { sessionCtx = sessionContext; this.url = url; + } + + public void connect() throws HgRemoteConnectionException, HgRuntimeException { try { conn = new Connection(url.getHost(), url.getPort() == -1 ? 22 : url.getPort()); conn.connect(); @@ -78,7 +77,7 @@ ConnectionInfo ci = conn.getConnectionInfo(); System.out.printf("%s %s %s %d %s %s %s\n", ci.clientToServerCryptoAlgorithm, ci.clientToServerMACAlgorithm, ci.keyExchangeAlgorithm, ci.keyExchangeCounter, ci.serverHostKeyAlgorithm, ci.serverToClientCryptoAlgorithm, ci.serverToClientMACAlgorithm); } catch (IOException ex) { - throw new HgRemoteConnectionException("Failed to authenticate", ex).setServerInfo(getLocation()); + throw new HgRemoteConnectionException("Failed to authenticate", ex).setServerInfo(getServerLocation()); } } @@ -119,8 +118,41 @@ forceSessionClose(); } - public String getLocation() { - return ""; + public String getServerLocation() { + return ""; // FIXME + } + + public String getCapabilities() throws HgRemoteConnectionException { + try { + consume(remoteOut); + consume(remoteErr); + remoteIn.write(CMD_HELLO.getBytes()); + remoteIn.write('\n'); + remoteIn.write(CMD_CAPABILITIES.getBytes()); // see http connector for details + remoteIn.write('\n'); + remoteIn.write(CMD_HEADS.getBytes()); + remoteIn.write('\n'); + checkError(); + int responseLen = readResponseLength(); + checkError(); + FilterStream s = new FilterStream(remoteOut, responseLen); + BufferedReader r = new BufferedReader(new InputStreamReader(s)); + String line; + while ((line = r.readLine()) != null) { + if (line.startsWith(CMD_CAPABILITIES) && line.length() > (CMD_CAPABILITIES.length()+1)) { + line = line.substring(CMD_CAPABILITIES.length()); + if (line.charAt(0) == ':') { + return line.substring(CMD_CAPABILITIES.length() + 1); + } + } + } + r.close(); + consume(remoteOut); + checkError(); + return new String(); + } catch (IOException ex) { + throw new HgRemoteConnectionException("Failed to initiate dialog with server", ex).setRemoteCommand(CMD_HELLO).setServerInfo(getServerLocation()); + } } public InputStream heads() throws HgRemoteConnectionException { @@ -148,10 +180,28 @@ return executeCommand("changegroup", Collections.singletonList(new Parameter("roots", l))); } - public void unbundle(HgBundle bundle, List<Nodeid> remoteHeads) throws HgRemoteConnectionException, HgRuntimeException { + public OutputStream unbundle(long outputLen, List<Nodeid> remoteHeads) throws HgRemoteConnectionException, HgRuntimeException { String l = join(remoteHeads, ' '); - Collections.singletonList(new Parameter("heads", l)); - throw Internals.notImplemented(); + try { + consume(remoteOut); + consume(remoteErr); + remoteIn.write(CMD_UNBUNDLE.getBytes()); + remoteIn.write('\n'); + writeParameters(Collections.singletonList(new Parameter("heads", l))); + checkError(); + return new FilterOutputStream(remoteIn) { + @Override + public void close() throws IOException { + out.flush(); + @SuppressWarnings("unused") + int responseLen = readResponseLength(); + checkError(); + // XXX perhaps, need to return responseLen to caller? + } + }; + } catch (IOException ex) { + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(CMD_UNBUNDLE).setServerInfo(getServerLocation()); + } } public InputStream pushkey(String opName, String namespace, String key, String oldValue, String newValue) throws HgRemoteConnectionException, HgRuntimeException { @@ -167,70 +217,30 @@ return executeCommand("listkeys", Collections.singletonList(new Parameter("namespace", namespace))); } - - public Set<String> initCapabilities() throws HgRemoteConnectionException { - try { - final String CMD_CAPABILITIES = "capabilities"; - final String CMD_HEADS = "heads"; - final String CMD_HELLO = "hello"; - consume(remoteOut); - consume(remoteErr); - remoteIn.write(CMD_HELLO.getBytes()); - remoteIn.write('\n'); - remoteIn.write(CMD_CAPABILITIES.getBytes()); // see http connector for - remoteIn.write('\n'); - remoteIn.write(CMD_HEADS.getBytes()); - remoteIn.write('\n'); - checkError(); - int responseLen = readResponseLength(); - checkError(); - FilterStream s = new FilterStream(remoteOut, responseLen); - BufferedReader r = new BufferedReader(new InputStreamReader(s)); - String line; - while ((line = r.readLine()) != null) { - if (line.startsWith(CMD_CAPABILITIES) && line.length() > (CMD_CAPABILITIES.length()+1)) { - line = line.substring(CMD_CAPABILITIES.length()); - if (line.charAt(0) == ':') { - String[] caps = line.substring(CMD_CAPABILITIES.length() + 1).split("\\s"); - return new HashSet<String>(Arrays.asList(caps)); - } - } - } - r.close(); - consume(remoteOut); - checkError(); - return Collections.emptySet(); - } catch (IOException ex) { - throw new HgRemoteConnectionException("Failed to initiate dialog with server", ex).setRemoteCommand("hello").setServerInfo(getLocation()); - } catch (HgRemoteConnectionException ex) { - ex.setRemoteCommand("hello").setServerInfo(getLocation()); - throw ex; - } - } - private InputStream executeCommand(String cmd, List<Parameter> parameters) throws HgRemoteConnectionException { try { consume(remoteOut); consume(remoteErr); remoteIn.write(cmd.getBytes()); remoteIn.write('\n'); - for (Parameter p : parameters) { - remoteIn.write(p.name().getBytes()); - remoteIn.write(' '); - remoteIn.write(String.valueOf(p.size()).getBytes()); - remoteIn.write('\n'); - remoteIn.write(p.data()); - remoteIn.write('\n'); - } + writeParameters(parameters); checkError(); int responseLen = readResponseLength(); checkError(); return new FilterStream(remoteOut, responseLen); } catch (IOException ex) { - throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(cmd).setServerInfo(getLocation()); - } catch (HgRemoteConnectionException ex) { - ex.setRemoteCommand(cmd).setServerInfo(getLocation()); - throw ex; + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(cmd).setServerInfo(getServerLocation()); + } + } + + private void writeParameters(List<Parameter> parameters) throws IOException { + for (Parameter p : parameters) { + remoteIn.write(p.name().getBytes()); + remoteIn.write(' '); + remoteIn.write(String.valueOf(p.size()).getBytes()); + remoteIn.write('\n'); + remoteIn.write(p.data()); + remoteIn.write('\n'); } } @@ -240,14 +250,14 @@ } } - private void checkError() throws IOException, HgRemoteConnectionException { + private void checkError() throws IOException { if (remoteErr.available() > 0) { StringBuilder sb = new StringBuilder(); int c; while ((c = remoteErr.read()) != -1) { sb.append((char)c); } - throw new HgRemoteConnectionException(sb.toString()); + throw new IOException(sb.toString()); } }
--- a/src/org/tmatesoft/hg/repo/HgRemoteRepository.java Thu Jul 25 22:12:14 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgRemoteRepository.java Sat Jul 27 18:34:14 2013 +0200 @@ -16,7 +16,7 @@ */ package org.tmatesoft.hg.repo; -import static org.tmatesoft.hg.util.LogFacility.Severity.Info; +import static org.tmatesoft.hg.internal.remote.Connector.*; import static org.tmatesoft.hg.util.Outcome.Kind.Failure; import static org.tmatesoft.hg.util.Outcome.Kind.Success; @@ -31,12 +31,8 @@ import java.io.StreamTokenizer; import java.net.ContentHandler; import java.net.ContentHandlerFactory; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -48,27 +44,23 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.prefs.BackingStoreException; -import java.util.prefs.Preferences; import java.util.zip.InflaterInputStream; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - import org.tmatesoft.hg.core.HgBadArgumentException; import org.tmatesoft.hg.core.HgIOException; import org.tmatesoft.hg.core.HgRemoteConnectionException; import org.tmatesoft.hg.core.HgRepositoryNotFoundException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.core.SessionContext; +import org.tmatesoft.hg.internal.BundleSerializer; import org.tmatesoft.hg.internal.DataSerializer; import org.tmatesoft.hg.internal.DataSerializer.OutputStreamSerializer; -import org.tmatesoft.hg.internal.BundleSerializer; import org.tmatesoft.hg.internal.EncodingHelper; +import org.tmatesoft.hg.internal.FileUtils; import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.internal.PropertyMarshal; +import org.tmatesoft.hg.internal.remote.Connector; +import org.tmatesoft.hg.internal.remote.HttpConnector; import org.tmatesoft.hg.util.LogFacility.Severity; import org.tmatesoft.hg.util.Outcome; import org.tmatesoft.hg.util.Pair; @@ -84,13 +76,11 @@ */ public class HgRemoteRepository implements SessionContext.Source { - private final URL url; - private final SSLContext sslContext; - private final String authInfo; private final boolean debug; private HgLookup lookupHelper; private final SessionContext sessionContext; private Set<String> remoteCapabilities; + private Connector remote; static { URLConnection.setContentHandlerFactory(new ContentHandlerFactory() { @@ -123,50 +113,10 @@ if (url == null || ctx == null) { throw new IllegalArgumentException(); } - this.url = url; sessionContext = ctx; debug = new PropertyMarshal(ctx).getBoolean("hg4j.remote.debug", false); - if ("https".equals(url.getProtocol())) { - try { - sslContext = SSLContext.getInstance("SSL"); - class TrustEveryone implements X509TrustManager { - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - if (debug) { - System.out.println("checkClientTrusted:" + authType); - } - } - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - if (debug) { - System.out.println("checkServerTrusted:" + authType); - } - } - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - }; - sslContext.init(null, new TrustManager[] { new TrustEveryone() }, null); - } catch (Exception ex) { - throw new HgBadArgumentException("Can't initialize secure connection", ex); - } - } else { - sslContext = null; - } - if (url.getUserInfo() != null) { - String ai = null; - try { - // Hack to get Base64-encoded credentials - Preferences tempNode = Preferences.userRoot().node("xxx"); - tempNode.putByteArray("xxx", url.getUserInfo().getBytes()); - ai = tempNode.get("xxx", null); - tempNode.removeNode(); - } catch (BackingStoreException ex) { - sessionContext.getLog().dump(getClass(), Info, ex, null); - // IGNORE - } - authInfo = ai; - } else { - authInfo = null; - } + remote = new HttpConnector(); + remote.init(url, ctx, null); } public boolean isInvalid() throws HgRemoteConnectionException { @@ -178,14 +128,7 @@ * @return human-readable address of the server, without user credentials or any other security information */ public String getLocation() { - if (url.getUserInfo() == null) { - return url.toExternalForm(); - } - if (url.getPort() != -1) { - return String.format("%s://%s:%d%s", url.getProtocol(), url.getHost(), url.getPort(), url.getPath()); - } else { - return String.format("%s://%s%s", url.getProtocol(), url.getHost(), url.getPath()); - } + return remote.getServerLocation(); } public SessionContext getSessionContext() { @@ -193,15 +136,12 @@ } public List<Nodeid> heads() throws HgRemoteConnectionException { - HttpURLConnection c = null; + if (isInvalid()) { + return Collections.emptyList(); + } try { - URL u = new URL(url, url.getPath() + "?cmd=heads"); - c = setupConnection(u.openConnection()); - c.connect(); - if (debug) { - dumpResponseHeader(u, c); - } - InputStreamReader is = new InputStreamReader(c.getInputStream(), "US-ASCII"); + remote.sessionBegin(); + InputStreamReader is = new InputStreamReader(remote.heads(), "US-ASCII"); StreamTokenizer st = new StreamTokenizer(is); st.ordinaryChars('0', '9'); // wordChars performs |, hence need to 0 first st.wordChars('0', '9'); @@ -211,14 +151,10 @@ parseResult.add(Nodeid.fromAscii(st.sval)); } return parseResult; - } catch (MalformedURLException ex) { - throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("heads").setServerInfo(getLocation()); } catch (IOException ex) { - throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("heads").setServerInfo(getLocation()); + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(CMD_HEADS).setServerInfo(getLocation()); } finally { - if (c != null) { - c.disconnect(); - } + remote.sessionEnd(); } } @@ -234,44 +170,13 @@ * @throws HgRemoteConnectionException */ public Map<Range, List<Nodeid>> between(Collection<Range> ranges) throws HgRemoteConnectionException { - if (ranges.isEmpty()) { + if (ranges.isEmpty() || isInvalid()) { return Collections.emptyMap(); } - // if fact, shall do other way round, this method shall send LinkedHashMap<Range, List<Nodeid>> rv = new LinkedHashMap<HgRemoteRepository.Range, List<Nodeid>>(ranges.size() * 4 / 3); - StringBuilder sb = new StringBuilder(20 + ranges.size() * 82); - sb.append("pairs="); - for (Range r : ranges) { - r.append(sb); - sb.append('+'); - } - if (sb.charAt(sb.length() - 1) == '+') { - // strip last space - sb.setLength(sb.length() - 1); - } - HttpURLConnection c = null; try { - boolean usePOST = ranges.size() > 3; - URL u = new URL(url, url.getPath() + "?cmd=between" + (usePOST ? "" : '&' + sb.toString())); - c = setupConnection(u.openConnection()); - if (usePOST) { - c.setRequestMethod("POST"); - c.setRequestProperty("Content-Length", String.valueOf(sb.length()/*nodeids are ASCII, bytes == characters */)); - c.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - c.setDoOutput(true); - c.connect(); - OutputStream os = c.getOutputStream(); - os.write(sb.toString().getBytes()); - os.flush(); - os.close(); - } else { - c.connect(); - } - if (debug) { - System.out.printf("%d ranges, method:%s \n", ranges.size(), c.getRequestMethod()); - dumpResponseHeader(u, c); - } - InputStreamReader is = new InputStreamReader(c.getInputStream(), "US-ASCII"); + remote.sessionBegin(); + InputStreamReader is = new InputStreamReader(remote.between(ranges), "US-ASCII"); StreamTokenizer st = new StreamTokenizer(is); st.ordinaryChars('0', '9'); st.wordChars('0', '9'); @@ -315,28 +220,20 @@ } is.close(); return rv; - } catch (MalformedURLException ex) { - throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("between").setServerInfo(getLocation()); } catch (IOException ex) { - throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("between").setServerInfo(getLocation()); + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(CMD_BETWEEN).setServerInfo(getLocation()); } finally { - if (c != null) { - c.disconnect(); - } + remote.sessionEnd(); } } public List<RemoteBranch> branches(List<Nodeid> nodes) throws HgRemoteConnectionException { - StringBuilder sb = appendNodeidListArgument("nodes", nodes, null); - HttpURLConnection c = null; + if (isInvalid()) { + return Collections.emptyList(); + } try { - URL u = new URL(url, url.getPath() + "?cmd=branches&" + sb.toString()); - c = setupConnection(u.openConnection()); - c.connect(); - if (debug) { - dumpResponseHeader(u, c); - } - InputStreamReader is = new InputStreamReader(c.getInputStream(), "US-ASCII"); + remote.sessionBegin(); + InputStreamReader is = new InputStreamReader(remote.branches(nodes), "US-ASCII"); StreamTokenizer st = new StreamTokenizer(is); st.ordinaryChars('0', '9'); st.wordChars('0', '9'); @@ -354,14 +251,10 @@ rv.add(rb); } return rv; - } catch (MalformedURLException ex) { - throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("branches").setServerInfo(getLocation()); } catch (IOException ex) { - throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("branches").setServerInfo(getLocation()); + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(CMD_BRANCHES).setServerInfo(getLocation()); } finally { - if (c != null) { - c.disconnect(); - } + remote.sessionEnd(); } } @@ -382,32 +275,23 @@ * as one may expect according to http://mercurial.selenic.com/wiki/BundleFormat) */ public HgBundle getChanges(List<Nodeid> roots) throws HgRemoteConnectionException, HgRuntimeException { + if (isInvalid()) { + return null; // XXX valid retval??? + } List<Nodeid> _roots = roots.isEmpty() ? Collections.singletonList(Nodeid.NULL) : roots; - StringBuilder sb = appendNodeidListArgument("roots", _roots, null); - HttpURLConnection c = null; try { - URL u = new URL(url, url.getPath() + "?cmd=changegroup&" + sb.toString()); - c = setupConnection(u.openConnection()); - c.connect(); + remote.sessionBegin(); + File tf = writeBundle(remote.changegroup(_roots), false, "HG10GZ" /*didn't see any other that zip*/); if (debug) { - dumpResponseHeader(u, c); - } - File tf = writeBundle(c.getInputStream(), false, "HG10GZ" /*didn't see any other that zip*/); - if (debug) { - System.out.printf("Wrote bundle %s for roots %s\n", tf, sb); + System.out.printf("Wrote bundle %s for roots %s\n", tf, roots); } return getLookupHelper().loadBundle(tf); - } catch (MalformedURLException ex) { // XXX in fact, this exception might be better to be re-thrown as RuntimeEx, - // as there's little user can do about this issue (URLs are constructed by our code) - throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("changegroup").setServerInfo(getLocation()); } catch (IOException ex) { - throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("changegroup").setServerInfo(getLocation()); + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(CMD_CHANGEGROUP).setServerInfo(getLocation()); } catch (HgRepositoryNotFoundException ex) { - throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("changegroup").setServerInfo(getLocation()); + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(CMD_CHANGEGROUP).setServerInfo(getLocation()); } finally { - if (c != null) { - c.disconnect(); - } + remote.sessionEnd(); } } @@ -418,41 +302,33 @@ // or get from remote server??? throw Internals.notImplemented(); } - StringBuilder sb = appendNodeidListArgument("heads", remoteHeads, null); - - HttpURLConnection c = null; + if (isInvalid()) { + return; + } DataSerializer.DataSource bundleData = BundleSerializer.newInstance(sessionContext, bundle); + OutputStream os = null; try { - URL u = new URL(url, url.getPath() + "?cmd=unbundle&" + sb.toString()); - c = setupConnection(u.openConnection()); - c.setRequestMethod("POST"); - c.setRequestProperty("Content-Length", String.valueOf(bundleData.serializeLength())); - c.setRequestProperty("Content-Type", "application/mercurial-0.1"); - c.setDoOutput(true); - c.connect(); - OutputStream os = c.getOutputStream(); + remote.sessionBegin(); + os = remote.unbundle(bundleData.serializeLength(), remoteHeads); bundleData.serialize(new OutputStreamSerializer(os)); os.flush(); os.close(); - if (debug) { - dumpResponseHeader(u, c); - dumpResponse(c); - } - checkResponseOk(c, "Push", "unbundle"); - } catch (MalformedURLException ex) { - throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("unbundle").setServerInfo(getLocation()); + os = null; } catch (IOException ex) { throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("unbundle").setServerInfo(getLocation()); } catch (HgIOException ex) { throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("unbundle").setServerInfo(getLocation()); } finally { - if (c != null) { - c.disconnect(); - } + new FileUtils(sessionContext.getLog(), this).closeQuietly(os); + remote.sessionEnd(); } } public Bookmarks getBookmarks() throws HgRemoteConnectionException, HgRuntimeException { + initCapabilities(); + if (!remoteCapabilities.contains(CMD_PUSHKEY)) { // (sic!) listkeys is available when pushkey in caps + return new Bookmarks(Collections.<Pair<String, Nodeid>>emptyList()); + } final String actionName = "Get remote bookmarks"; final List<Pair<String, String>> values = listkeys("bookmarks", actionName); ArrayList<Pair<String, Nodeid>> rv = new ArrayList<Pair<String, Nodeid>>(); @@ -470,10 +346,10 @@ public Outcome updateBookmark(String name, Nodeid oldRev, Nodeid newRev) throws HgRemoteConnectionException, HgRuntimeException { initCapabilities(); - if (!remoteCapabilities.contains("pushkey")) { + if (!remoteCapabilities.contains(CMD_PUSHKEY)) { return new Outcome(Failure, "Server doesn't support pushkey protocol"); } - if (pushkey("Update remote bookmark", "bookmarks", name, oldRev.toString(), newRev.toString())) { + if (pushkey("Update remote bookmark", NS_BOOKMARKS, name, oldRev.toString(), newRev.toString())) { return new Outcome(Success, String.format("Bookmark %s updated to %s", name, newRev.shortNotation())); } return new Outcome(Failure, String.format("Bookmark update (%s: %s -> %s) failed", name, oldRev.shortNotation(), newRev.shortNotation())); @@ -481,11 +357,11 @@ public Phases getPhases() throws HgRemoteConnectionException, HgRuntimeException { initCapabilities(); - if (!remoteCapabilities.contains("pushkey")) { + if (!remoteCapabilities.contains(CMD_PUSHKEY)) { // old server defaults to publishing return new Phases(true, Collections.<Nodeid>emptyList()); } - final List<Pair<String, String>> values = listkeys("phases", "Get remote phases"); + final List<Pair<String, String>> values = listkeys(NS_PHASES, "Get remote phases"); boolean publishing = false; ArrayList<Nodeid> draftRoots = new ArrayList<Nodeid>(); for (Pair<String, String> l : values) { @@ -507,10 +383,10 @@ public Outcome updatePhase(HgPhase from, HgPhase to, Nodeid n) throws HgRemoteConnectionException, HgRuntimeException { initCapabilities(); - if (!remoteCapabilities.contains("pushkey")) { + if (!remoteCapabilities.contains(CMD_PUSHKEY)) { return new Outcome(Failure, "Server doesn't support pushkey protocol"); } - if (pushkey("Update remote phases", "phases", n.toString(), String.valueOf(from.mercurialOrdinal()), String.valueOf(to.mercurialOrdinal()))) { + if (pushkey("Update remote phases", NS_PHASES, n.toString(), String.valueOf(from.mercurialOrdinal()), String.valueOf(to.mercurialOrdinal()))) { return new Outcome(Success, String.format("Phase of %s updated to %s", n.shortNotation(), to.name())); } return new Outcome(Failure, String.format("Phase update (%s: %s -> %s) failed", n.shortNotation(), from.name(), to.name())); @@ -523,47 +399,17 @@ private void initCapabilities() throws HgRemoteConnectionException { - if (remoteCapabilities == null) { - remoteCapabilities = new HashSet<String>(); - // say hello to server, check response - try { - URL u = new URL(url, url.getPath() + "?cmd=hello"); - HttpURLConnection c = setupConnection(u.openConnection()); - c.connect(); - if (debug) { - dumpResponseHeader(u, c); - } - BufferedReader r = new BufferedReader(new InputStreamReader(c.getInputStream(), "US-ASCII")); - String line = r.readLine(); - c.disconnect(); - final String capsPrefix = "capabilities:"; - if (line == null || !line.startsWith(capsPrefix)) { - // for whatever reason, some servers do not respond to hello command (e.g. svnkit) - // but respond to 'capabilities' instead. Try it. - // TODO [post-1.0] tests needed - u = new URL(url, url.getPath() + "?cmd=capabilities"); - c = setupConnection(u.openConnection()); - c.connect(); - if (debug) { - dumpResponseHeader(u, c); - } - r = new BufferedReader(new InputStreamReader(c.getInputStream(), "US-ASCII")); - line = r.readLine(); - c.disconnect(); - if (line == null || line.trim().length() == 0) { - return; - } - } else { - line = line.substring(capsPrefix.length()).trim(); - } - String[] caps = line.split("\\s"); - remoteCapabilities.addAll(Arrays.asList(caps)); - c.disconnect(); - } catch (MalformedURLException ex) { - throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("hello").setServerInfo(getLocation()); - } catch (IOException ex) { - throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("hello").setServerInfo(getLocation()); - } + if (remoteCapabilities != null) { + return; + } + remote.connect(); + try { + remote.sessionBegin(); + String capsLine = remote.getCapabilities(); + String[] caps = capsLine.split("\\s"); + remoteCapabilities = new HashSet<String>(Arrays.asList(caps)); + } finally { + remote.sessionEnd(); } } @@ -575,18 +421,12 @@ } private List<Pair<String,String>> listkeys(String namespace, String actionName) throws HgRemoteConnectionException, HgRuntimeException { - HttpURLConnection c = null; try { - URL u = new URL(url, url.getPath() + "?cmd=listkeys&namespace=" + namespace); - c = setupConnection(u.openConnection()); - c.connect(); - if (debug) { - dumpResponseHeader(u, c); - } - checkResponseOk(c, actionName, "listkeys"); + remote.sessionBegin(); ArrayList<Pair<String, String>> rv = new ArrayList<Pair<String, String>>(); + InputStream response = remote.listkeys(namespace, actionName); // output of listkeys is encoded with UTF-8 - BufferedReader r = new BufferedReader(new InputStreamReader(c.getInputStream(), EncodingHelper.getUTF8())); + BufferedReader r = new BufferedReader(new InputStreamReader(response, EncodingHelper.getUTF8())); String l; while ((l = r.readLine()) != null) { int sep = l.indexOf('\t'); @@ -598,94 +438,24 @@ } r.close(); return rv; - } catch (MalformedURLException ex) { - throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("listkeys").setServerInfo(getLocation()); } catch (IOException ex) { - throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("listkeys").setServerInfo(getLocation()); + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(CMD_LISTKEYS).setServerInfo(getLocation()); } finally { - if (c != null) { - c.disconnect(); - } + remote.sessionEnd(); } } private boolean pushkey(String opName, String namespace, String key, String oldValue, String newValue) throws HgRemoteConnectionException, HgRuntimeException { - HttpURLConnection c = null; try { - final String p = String.format("%s?cmd=pushkey&namespace=%s&key=%s&old=%s&new=%s", url.getPath(), namespace, key, oldValue, newValue); - URL u = new URL(url, p); - c = setupConnection(u.openConnection()); - c.setRequestMethod("POST"); - c.connect(); - if (debug) { - dumpResponseHeader(u, c); - } - checkResponseOk(c, opName, "pushkey"); - final InputStream is = c.getInputStream(); + remote.sessionBegin(); + final InputStream is = remote.pushkey(opName, namespace, key, oldValue, newValue); int rv = is.read(); is.close(); return rv == '1'; - } catch (MalformedURLException ex) { - throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("pushkey").setServerInfo(getLocation()); } catch (IOException ex) { - throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("pushkey").setServerInfo(getLocation()); + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(CMD_PUSHKEY).setServerInfo(getLocation()); } finally { - if (c != null) { - c.disconnect(); - } - } - } - - private void checkResponseOk(HttpURLConnection c, String opName, String remoteCmd) throws HgRemoteConnectionException, IOException { - if (c.getResponseCode() != 200) { - String m = c.getResponseMessage() == null ? "unknown reason" : c.getResponseMessage(); - String em = String.format("%s failed: %s (HTTP error:%d)", opName, m, c.getResponseCode()); - throw new HgRemoteConnectionException(em).setRemoteCommand(remoteCmd).setServerInfo(getLocation()); - } - } - - private HttpURLConnection setupConnection(URLConnection urlConnection) { - urlConnection.setRequestProperty("User-Agent", "hg4j/1.0.0"); - urlConnection.addRequestProperty("Accept", "application/mercurial-0.1"); - if (authInfo != null) { - urlConnection.addRequestProperty("Authorization", "Basic " + authInfo); - } - if (sslContext != null) { - ((HttpsURLConnection) urlConnection).setSSLSocketFactory(sslContext.getSocketFactory()); - } - return (HttpURLConnection) urlConnection; - } - - private StringBuilder appendNodeidListArgument(String key, List<Nodeid> values, StringBuilder sb) { - if (sb == null) { - sb = new StringBuilder(20 + values.size() * 41); - } - sb.append(key); - sb.append('='); - for (Nodeid n : values) { - sb.append(n.toString()); - sb.append('+'); - } - if (sb.charAt(sb.length() - 1) == '+') { - // strip last space - sb.setLength(sb.length() - 1); - } - return sb; - } - - private void dumpResponseHeader(URL u, HttpURLConnection c) { - System.out.printf("Query (%d bytes):%s\n", u.getQuery().length(), u.getQuery()); - System.out.println("Response headers:"); - final Map<String, List<String>> headerFields = c.getHeaderFields(); - for (String s : headerFields.keySet()) { - System.out.printf("%s: %s\n", s, c.getHeaderField(s)); - } - } - - private void dumpResponse(HttpURLConnection c) throws IOException { - if (c.getContentLength() > 0) { - final Object content = c.getContent(); - System.out.println(content); + remote.sessionEnd(); } }