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