Mercurial > jhg
comparison src/org/tmatesoft/hg/repo/HgRemoteRepository.java @ 177:e10225daface
Use POST for long between queries. Batch between queries (pass multiple pairs to a server) to minimize number thereof
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Sat, 02 Apr 2011 23:05:28 +0200 |
| parents | a8df7162ec75 |
| children | 62665d8f0686 |
comparison
equal
deleted
inserted
replaced
| 176:a8df7162ec75 | 177:e10225daface |
|---|---|
| 16 */ | 16 */ |
| 17 package org.tmatesoft.hg.repo; | 17 package org.tmatesoft.hg.repo; |
| 18 | 18 |
| 19 import java.io.File; | 19 import java.io.File; |
| 20 import java.io.IOException; | 20 import java.io.IOException; |
| 21 import java.io.InputStream; | 21 import java.io.InputStreamReader; |
| 22 import java.io.OutputStream; | |
| 22 import java.io.StreamTokenizer; | 23 import java.io.StreamTokenizer; |
| 24 import java.net.HttpURLConnection; | |
| 23 import java.net.MalformedURLException; | 25 import java.net.MalformedURLException; |
| 24 import java.net.URL; | 26 import java.net.URL; |
| 25 import java.net.URLConnection; | 27 import java.net.URLConnection; |
| 26 import java.security.cert.CertificateException; | 28 import java.security.cert.CertificateException; |
| 27 import java.security.cert.X509Certificate; | 29 import java.security.cert.X509Certificate; |
| 28 import java.util.Collection; | 30 import java.util.Collection; |
| 29 import java.util.Collections; | 31 import java.util.Collections; |
| 32 import java.util.Iterator; | |
| 30 import java.util.LinkedHashMap; | 33 import java.util.LinkedHashMap; |
| 31 import java.util.LinkedList; | 34 import java.util.LinkedList; |
| 32 import java.util.List; | 35 import java.util.List; |
| 33 import java.util.Map; | 36 import java.util.Map; |
| 34 import java.util.prefs.BackingStoreException; | 37 import java.util.prefs.BackingStoreException; |
| 37 import javax.net.ssl.HttpsURLConnection; | 40 import javax.net.ssl.HttpsURLConnection; |
| 38 import javax.net.ssl.SSLContext; | 41 import javax.net.ssl.SSLContext; |
| 39 import javax.net.ssl.TrustManager; | 42 import javax.net.ssl.TrustManager; |
| 40 import javax.net.ssl.X509TrustManager; | 43 import javax.net.ssl.X509TrustManager; |
| 41 | 44 |
| 45 import org.tmatesoft.hg.core.HgBadStateException; | |
| 42 import org.tmatesoft.hg.core.HgException; | 46 import org.tmatesoft.hg.core.HgException; |
| 43 import org.tmatesoft.hg.core.Nodeid; | 47 import org.tmatesoft.hg.core.Nodeid; |
| 44 | 48 |
| 45 /** | 49 /** |
| 46 * WORK IN PROGRESS, DO NOT USE | 50 * WORK IN PROGRESS, DO NOT USE |
| 104 return Collections.singletonList(Nodeid.fromAscii("71ddbf8603e8e09d54ac9c5fe4bb5ae824589f1d")); | 108 return Collections.singletonList(Nodeid.fromAscii("71ddbf8603e8e09d54ac9c5fe4bb5ae824589f1d")); |
| 105 // return Collections.emptyList(); | 109 // return Collections.emptyList(); |
| 106 } | 110 } |
| 107 | 111 |
| 108 public List<Nodeid> between(Nodeid tip, Nodeid base) throws HgException { | 112 public List<Nodeid> between(Nodeid tip, Nodeid base) throws HgException { |
| 113 Range r = new Range(base, tip); | |
| 114 // XXX shall handle errors like no range key in the returned map, not sure how. | |
| 115 return between(Collections.singletonList(r)).get(r); | |
| 116 } | |
| 117 | |
| 118 /** | |
| 119 * @param ranges | |
| 120 * @return map, where keys are input instances, values are corresponding server reply | |
| 121 * @throws HgException | |
| 122 */ | |
| 123 public Map<Range, List<Nodeid>> between(Collection<Range> ranges) throws HgException { | |
| 124 if (ranges.isEmpty()) { | |
| 125 return Collections.emptyMap(); | |
| 126 } | |
| 127 // if fact, shall do other way round, this method shall send | |
| 128 LinkedHashMap<Range, List<Nodeid>> rv = new LinkedHashMap<HgRemoteRepository.Range, List<Nodeid>>(ranges.size() * 4 / 3); | |
| 129 StringBuilder sb = new StringBuilder(20 + ranges.size() * 82); | |
| 130 sb.append("pairs="); | |
| 131 for (Range r : ranges) { | |
| 132 sb.append(r.end.toString()); | |
| 133 sb.append('-'); | |
| 134 sb.append(r.start.toString()); | |
| 135 sb.append('+'); | |
| 136 } | |
| 137 if (sb.charAt(sb.length() - 1) == '+') { | |
| 138 // strip last space | |
| 139 sb.setLength(sb.length() - 1); | |
| 140 } | |
| 109 try { | 141 try { |
| 110 LinkedList<Nodeid> rv = new LinkedList<Nodeid>(); | 142 boolean usePOST = ranges.size() > 3; |
| 111 URL u = new URL(url, url.getPath() + "?cmd=between&pairs=" + tip.toString() + '-' + base.toString()); | 143 URL u = new URL(url, url.getPath() + "?cmd=between" + (usePOST ? "" : '&' + sb.toString())); |
| 112 URLConnection c = setupConnection(u.openConnection()); | 144 HttpURLConnection c = setupConnection(u.openConnection()); |
| 113 c.connect(); | 145 if (usePOST) { |
| 114 System.out.println("Query:" + u.getQuery()); | 146 c.setRequestMethod("POST"); |
| 147 c.setRequestProperty("Content-Length", String.valueOf(sb.length()/*nodeids are ASCII, bytes == characters */)); | |
| 148 c.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); | |
| 149 c.setDoOutput(true); | |
| 150 c.connect(); | |
| 151 OutputStream os = c.getOutputStream(); | |
| 152 os.write(sb.toString().getBytes()); | |
| 153 os.flush(); | |
| 154 os.close(); | |
| 155 } else { | |
| 156 c.connect(); | |
| 157 } | |
| 158 System.out.printf("%d ranges, method:%s \n", ranges.size(), c.getRequestMethod()); | |
| 159 System.out.printf("Query (%d bytes):%s\n", u.getQuery().length(), u.getQuery()); | |
| 115 System.out.println("Response headers:"); | 160 System.out.println("Response headers:"); |
| 116 final Map<String, List<String>> headerFields = c.getHeaderFields(); | 161 final Map<String, List<String>> headerFields = c.getHeaderFields(); |
| 117 for (String s : headerFields.keySet()) { | 162 for (String s : headerFields.keySet()) { |
| 118 System.out.printf("%s: %s\n", s, c.getHeaderField(s)); | 163 System.out.printf("%s: %s\n", s, c.getHeaderField(s)); |
| 119 } | 164 } |
| 120 InputStream is = c.getInputStream(); | 165 InputStreamReader is = new InputStreamReader(c.getInputStream(), "US-ASCII"); |
| 121 StreamTokenizer st = new StreamTokenizer(is); | 166 StreamTokenizer st = new StreamTokenizer(is); |
| 122 st.ordinaryChars('0', '9'); | 167 st.ordinaryChars('0', '9'); |
| 123 st.wordChars('0', '9'); | 168 st.wordChars('0', '9'); |
| 169 st.eolIsSignificant(true); | |
| 170 Iterator<Range> rangeItr = ranges.iterator(); | |
| 171 LinkedList<Nodeid> currRangeList = null; | |
| 172 Range currRange = null; | |
| 173 boolean possiblyEmptyNextLine = true; | |
| 124 while (st.nextToken() != StreamTokenizer.TT_EOF) { | 174 while (st.nextToken() != StreamTokenizer.TT_EOF) { |
| 125 System.out.println(st.sval); | 175 if (st.ttype == StreamTokenizer.TT_EOL) { |
| 126 Nodeid nid = Nodeid.fromAscii(st.sval); | 176 if (possiblyEmptyNextLine) { |
| 127 rv.addLast(nid); | 177 // newline follows newline; |
| 178 assert currRange == null; | |
| 179 assert currRangeList == null; | |
| 180 if (!rangeItr.hasNext()) { | |
| 181 throw new HgBadStateException(); | |
| 182 } | |
| 183 rv.put(rangeItr.next(), Collections.<Nodeid>emptyList()); | |
| 184 } else { | |
| 185 if (currRange == null || currRangeList == null) { | |
| 186 throw new HgBadStateException(); | |
| 187 } | |
| 188 // indicate next range value is needed | |
| 189 currRange = null; | |
| 190 currRangeList = null; | |
| 191 possiblyEmptyNextLine = true; | |
| 192 } | |
| 193 } else { | |
| 194 possiblyEmptyNextLine = false; | |
| 195 if (currRange == null) { | |
| 196 if (!rangeItr.hasNext()) { | |
| 197 throw new HgBadStateException(); | |
| 198 } | |
| 199 currRange = rangeItr.next(); | |
| 200 currRangeList = new LinkedList<Nodeid>(); | |
| 201 rv.put(currRange, currRangeList); | |
| 202 } | |
| 203 Nodeid nid = Nodeid.fromAscii(st.sval); | |
| 204 currRangeList.addLast(nid); | |
| 205 } | |
| 128 } | 206 } |
| 129 is.close(); | 207 is.close(); |
| 130 return rv; | 208 return rv; |
| 131 } catch (MalformedURLException ex) { | 209 } catch (MalformedURLException ex) { |
| 132 throw new HgException(ex); | 210 throw new HgException(ex); |
| 133 } catch (IOException ex) { | 211 } catch (IOException ex) { |
| 134 throw new HgException(ex); | 212 throw new HgException(ex); |
| 135 } | 213 } |
| 136 } | 214 } |
| 137 | 215 |
| 138 /** | |
| 139 * @param ranges | |
| 140 * @return map, where keys are input instances, values are corresponding server reply | |
| 141 * @throws HgException | |
| 142 */ | |
| 143 public Map<Range, List<Nodeid>> between(Collection<Range> ranges) throws HgException { | |
| 144 // if fact, shall do other way round, this method shall send | |
| 145 LinkedHashMap<Range, List<Nodeid>> rv = new LinkedHashMap<HgRemoteRepository.Range, List<Nodeid>>(ranges.size() * 4 / 3); | |
| 146 for (Range r : ranges) { | |
| 147 List<Nodeid> between = between(r.end, r.start); | |
| 148 rv.put(r, between); | |
| 149 } | |
| 150 return rv; | |
| 151 } | |
| 152 | |
| 153 public List<RemoteBranch> branches(List<Nodeid> nodes) { | 216 public List<RemoteBranch> branches(List<Nodeid> nodes) { |
| 154 return Collections.emptyList(); | 217 return Collections.emptyList(); |
| 155 } | 218 } |
| 156 | 219 |
| 157 // WireProtocol wiki: roots = a list of the latest nodes on every service side changeset branch that both the client and server know about. | 220 // WireProtocol wiki: roots = a list of the latest nodes on every service side changeset branch that both the client and server know about. |
| 158 public HgBundle getChanges(List<Nodeid> roots) throws HgException { | 221 public HgBundle getChanges(List<Nodeid> roots) throws HgException { |
| 159 return new HgLookup().loadBundle(new File("/temp/hg/hg-bundle-000000000000-gz.tmp")); | 222 return new HgLookup().loadBundle(new File("/temp/hg/hg-bundle-000000000000-gz.tmp")); |
| 160 } | 223 } |
| 161 | 224 |
| 162 private URLConnection setupConnection(URLConnection urlConnection) { | 225 private HttpURLConnection setupConnection(URLConnection urlConnection) { |
| 163 urlConnection.addRequestProperty("User-Agent", "hg4j/0.5.0"); | 226 urlConnection.setRequestProperty("User-Agent", "hg4j/0.5.0"); |
| 164 urlConnection.addRequestProperty("Accept", "application/mercurial-0.1"); | 227 urlConnection.addRequestProperty("Accept", "application/mercurial-0.1"); |
| 165 if (authInfo != null) { | 228 if (authInfo != null) { |
| 166 urlConnection.addRequestProperty("Authorization", "Basic " + authInfo); | 229 urlConnection.addRequestProperty("Authorization", "Basic " + authInfo); |
| 167 } | 230 } |
| 168 if (sslContext != null) { | 231 if (sslContext != null) { |
| 169 ((HttpsURLConnection) urlConnection).setSSLSocketFactory(sslContext.getSocketFactory()); | 232 ((HttpsURLConnection) urlConnection).setSSLSocketFactory(sslContext.getSocketFactory()); |
| 170 } | 233 } |
| 171 return urlConnection; | 234 return (HttpURLConnection) urlConnection; |
| 172 } | 235 } |
| 173 | 236 |
| 174 public static final class Range { | 237 public static final class Range { |
| 175 /** | 238 /** |
| 176 * Root of the range, earlier revision | 239 * Root of the range, earlier revision |
