Skip to content

Commit 6448ba1

Browse files
committed
WIP - okhttp transformation done, TLS1.2 should be implemented (needs test), no upload progress yet - noted in FIXME
1 parent ac52746 commit 6448ba1

10 files changed

Lines changed: 248 additions & 298 deletions

File tree

AnkiDroid/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ android {
5757
buildConfigField "String", "ACRA_URL", '"https://ankidroid.org/acra/report"'
5858
}
5959
}
60-
useLibrary 'org.apache.http.legacy'
6160

6261
testOptions {
6362
animationsDisabled true

AnkiDroid/src/main/java/com/ichi2/async/Connection.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545

4646
import java.io.IOException;
4747

48+
import okhttp3.Response;
4849
import timber.log.Timber;
4950

5051
public class Connection extends BaseAsyncTask<Connection.Payload, Object, Connection.Payload> {
@@ -199,12 +200,11 @@ private Payload doOneInBackground(Payload data) {
199200
}
200201

201202

202-
@SuppressWarnings("deprecation") // tracking HTTP transport change in github already
203203
private Payload doInBackgroundLogin(Payload data) {
204204
String username = (String) data.data[0];
205205
String password = (String) data.data[1];
206206
HttpSyncer server = new RemoteServer(this, null);
207-
org.apache.http.HttpResponse ret;
207+
Response ret;
208208
try {
209209
ret = server.hostKey(username, password);
210210
} catch (UnknownHttpResponseException e) {
@@ -223,16 +223,16 @@ private Payload doInBackgroundLogin(Payload data) {
223223
String hostkey = null;
224224
boolean valid = false;
225225
if (ret != null) {
226-
data.returnType = ret.getStatusLine().getStatusCode();
227-
Timber.d("doInBackgroundLogin - response from server: %d, (%s)", data.returnType, ret.getStatusLine().getReasonPhrase());
226+
data.returnType = ret.code();
227+
Timber.d("doInBackgroundLogin - response from server: %d, (%s)", data.returnType, ret.message());
228228
if (data.returnType == 200) {
229229
try {
230-
JSONObject jo = (new JSONObject(server.stream2String(ret.getEntity().getContent())));
230+
JSONObject jo = (new JSONObject(server.stream2String(ret.body().byteStream())));
231231
hostkey = jo.getString("key");
232232
valid = (hostkey != null) && (hostkey.length() > 0);
233233
} catch (JSONException e) {
234234
valid = false;
235-
} catch (IllegalStateException | IOException e) {
235+
} catch (IllegalStateException | NullPointerException e) {
236236
throw new RuntimeException(e);
237237
}
238238
}

AnkiDroid/src/main/java/com/ichi2/libanki/sync/FullSyncer.java

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.util.HashMap;
4040
import java.util.Locale;
4141

42+
import okhttp3.Response;
4243
import timber.log.Timber;
4344

4445
@SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes","PMD.NPathComplexity"})
@@ -73,19 +74,16 @@ public String syncURL() {
7374

7475

7576
@Override
76-
@SuppressWarnings("deprecation") // tracking HTTP transport change in github already
7777
public Object[] download() throws UnknownHttpResponseException {
7878
InputStream cont;
7979
try {
80-
org.apache.http.HttpResponse ret = super.req("download");
81-
if (ret == null) {
80+
Response ret = super.req("download");
81+
if (ret == null || ret.body() == null) {
8282
return null;
8383
}
84-
cont = ret.getEntity().getContent();
85-
} catch (IllegalStateException e1) {
84+
cont = ret.body().byteStream();
85+
} catch (IllegalArgumentException e1) {
8686
throw new RuntimeException(e1);
87-
} catch (IOException e1) {
88-
return null;
8987
}
9088
String path;
9189
if (mCol != null) {
@@ -141,7 +139,6 @@ public Object[] download() throws UnknownHttpResponseException {
141139

142140

143141
@Override
144-
@SuppressWarnings("deprecation") // tracking HTTP transport change in github already
145142
public Object[] upload() throws UnknownHttpResponseException {
146143
// make sure it's ok before we try to upload
147144
mCon.publishProgress(R.string.sync_check_upload_file);
@@ -154,19 +151,19 @@ public Object[] upload() throws UnknownHttpResponseException {
154151
// apply some adjustments, then upload
155152
mCol.beforeUpload();
156153
String filePath = mCol.getPath();
157-
org.apache.http.HttpResponse ret;
154+
Response ret;
158155
mCon.publishProgress(R.string.sync_uploading_message);
159156
try {
160157
ret = super.req("upload", new FileInputStream(filePath));
161-
if (ret == null) {
158+
if (ret == null || ret.body() == null) {
162159
return null;
163160
}
164-
int status = ret.getStatusLine().getStatusCode();
161+
int status = ret.code();
165162
if (status != 200) {
166163
// error occurred
167-
return new Object[] { "error", status, ret.getStatusLine().getReasonPhrase() };
164+
return new Object[] { "error", status, ret.message() };
168165
} else {
169-
return new Object[] { super.stream2String(ret.getEntity().getContent()) };
166+
return new Object[] { super.stream2String(ret.body().byteStream()) };
170167
}
171168
} catch (IllegalStateException | IOException e) {
172169
throw new RuntimeException(e);

AnkiDroid/src/main/java/com/ichi2/libanki/sync/HttpSyncer.java

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/***************************************************************************************
22
* Copyright (c) 2012 Norbert Nagold <norbert.nagold@gmail.com> *
33
* Copyright (c) 2012 Kostas Spyropoulos <inigo.aldana@gmail.com> *
4-
* Copyright (c) 2014 Timothy Rae <perceptualchaos2@gmail.com>
4+
* Copyright (c) 2014 Timothy Rae <perceptualchaos2@gmail.com> *
5+
* Copyright (c) 2019 Mike Hardy <github@mikehardy.net> *
56
* *
67
* This program is free software; you can redistribute it and/or modify it under *
78
* the terms of the GNU General Public License as published by the Free Software *
@@ -30,7 +31,7 @@
3031
import com.ichi2.libanki.Utils;
3132
import com.ichi2.utils.VersionUtils;
3233

33-
import org.apache.commons.httpclient.contrib.ssl.EasySSLSocketFactory;
34+
import org.apache.http.entity.AbstractHttpEntity;
3435
import org.json.JSONException;
3536
import org.json.JSONObject;
3637

@@ -52,10 +53,16 @@
5253
import java.util.Locale;
5354
import java.util.Map;
5455
import java.util.Random;
56+
import java.util.concurrent.TimeUnit;
5557
import java.util.zip.GZIPOutputStream;
5658

5759
import javax.net.ssl.SSLException;
5860

61+
import okhttp3.MediaType;
62+
import okhttp3.OkHttpClient;
63+
import okhttp3.Request;
64+
import okhttp3.RequestBody;
65+
import okhttp3.Response;
5966
import timber.log.Timber;
6067

6168
/**
@@ -65,11 +72,12 @@
6572
* - 502: ankiweb down
6673
* - 503/504: server too busy
6774
*/
68-
@SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes","PMD.NPathComplexity",
69-
"deprecation"}) // tracking HTTP transport change in github already
75+
@SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes","PMD.NPathComplexity"})
7076
public class HttpSyncer {
7177

7278
private static final String BOUNDARY = "Anki-sync-boundary";
79+
private static final MediaType ANKI_POST_TYPE = MediaType.get("multipart/form-data; boundary=" + BOUNDARY);
80+
7381
public static final String ANKIWEB_STATUS_OK = "OK";
7482

7583
public volatile long bytesSent = 0;
@@ -95,40 +103,40 @@ public HttpSyncer(String hkey, Connection con) {
95103
}
96104

97105

98-
public void assertOk(org.apache.http.HttpResponse resp) throws UnknownHttpResponseException {
106+
public void assertOk(Response resp) throws UnknownHttpResponseException {
99107
// Throw RuntimeException if HTTP error
100108
if (resp == null) {
101109
throw new UnknownHttpResponseException("Null HttpResponse", -2);
102110
}
103-
int resultCode = resp.getStatusLine().getStatusCode();
111+
int resultCode = resp.code();
104112
if (!(resultCode == 200 || resultCode == 403)) {
105-
String reason = resp.getStatusLine().getReasonPhrase();
113+
String reason = resp.message();
106114
throw new UnknownHttpResponseException(reason, resultCode);
107115
}
108116
}
109117

110118

111-
public org.apache.http.HttpResponse req(String method) throws UnknownHttpResponseException {
119+
public Response req(String method) throws UnknownHttpResponseException {
112120
return req(method, null);
113121
}
114122

115123

116-
public org.apache.http.HttpResponse req(String method, InputStream fobj) throws UnknownHttpResponseException {
124+
public Response req(String method, InputStream fobj) throws UnknownHttpResponseException {
117125
return req(method, fobj, 6);
118126
}
119127

120128

121-
public org.apache.http.HttpResponse req(String method, int comp, InputStream fobj) throws UnknownHttpResponseException {
129+
public Response req(String method, int comp, InputStream fobj) throws UnknownHttpResponseException {
122130
return req(method, fobj, comp);
123131
}
124132

125133

126-
public org.apache.http.HttpResponse req(String method, InputStream fobj, int comp) throws UnknownHttpResponseException {
134+
public Response req(String method, InputStream fobj, int comp) throws UnknownHttpResponseException {
127135
return req(method, fobj, comp, null);
128136
}
129137

130138

131-
private org.apache.http.HttpResponse req(String method, InputStream fobj, int comp, JSONObject registerData) throws UnknownHttpResponseException {
139+
private Response req(String method, InputStream fobj, int comp, JSONObject registerData) throws UnknownHttpResponseException {
132140
File tmpFileBuffer = null;
133141
try {
134142
String bdry = "--" + BOUNDARY;
@@ -185,31 +193,33 @@ private org.apache.http.HttpResponse req(String method, InputStream fobj, int co
185193
} else {
186194
url = syncURL() + method;
187195
}
188-
org.apache.http.client.methods.HttpPost httpPost = new org.apache.http.client.methods.HttpPost(url);
189-
org.apache.http.HttpEntity entity = new ProgressByteEntity(tmpFileBuffer);
190196

191-
// body
192-
httpPost.setEntity(entity);
193-
httpPost.setHeader("Content-type", "multipart/form-data; boundary=" + BOUNDARY);
197+
// FIXME need to implement a Request subclass that emits progress as the stream is drained e.g. https://stackoverflow.com/a/26376724/9910298
198+
Request.Builder requestBuilder = new Request.Builder();
199+
requestBuilder.url(url);
200+
requestBuilder.post(RequestBody.create(ANKI_POST_TYPE, tmpFileBuffer));
201+
Request httpPost = requestBuilder.build();
194202

195203
// HttpParams
196-
org.apache.http.params.HttpParams params = new org.apache.http.params.BasicHttpParams();
197-
params.setParameter(org.apache.http.conn.params.ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
198-
params.setParameter(org.apache.http.conn.params.ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new org.apache.http.conn.params.ConnPerRouteBean(30));
199-
params.setParameter(org.apache.http.params.CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
200-
params.setParameter(org.apache.http.params.CoreProtocolPNames.USER_AGENT, "AnkiDroid-" + VersionUtils.getPkgVersionName());
201-
org.apache.http.params.HttpProtocolParams.setVersion(params, org.apache.http.HttpVersion.HTTP_1_1);
202-
org.apache.http.params.HttpConnectionParams.setSoTimeout(params, Connection.CONN_TIMEOUT);
203-
204-
// Registry
205-
org.apache.http.conn.scheme.SchemeRegistry registry = new org.apache.http.conn.scheme.SchemeRegistry();
206-
registry.register(new org.apache.http.conn.scheme.Scheme("http", org.apache.http.conn.scheme.PlainSocketFactory.getSocketFactory(), 80));
207-
registry.register(new org.apache.http.conn.scheme.Scheme("https", new EasySSLSocketFactory(), 443));
208-
org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager cm = new org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager(params, registry);
209-
204+
// org.apache.http.params.HttpParams params = new org.apache.http.params.BasicHttpParams();
205+
// params.setParameter(org.apache.http.conn.params.ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
206+
// params.setParameter(org.apache.http.conn.params.ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new org.apache.http.conn.params.ConnPerRouteBean(30));
207+
// params.setParameter(org.apache.http.params.CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
208+
// params.setParameter(org.apache.http.params.CoreProtocolPNames.USER_AGENT, "AnkiDroid-" + VersionUtils.getPkgVersionName());
209+
// org.apache.http.params.HttpProtocolParams.setVersion(params, org.apache.http.HttpVersion.HTTP_1_1);
210210
try {
211-
org.apache.http.client.HttpClient httpClient = new org.apache.http.impl.client.DefaultHttpClient(cm, params);
212-
org.apache.http.HttpResponse httpResponse = httpClient.execute(httpPost);
211+
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
212+
213+
clientBuilder.followRedirects(true)
214+
.followSslRedirects(true)
215+
.retryOnConnectionFailure(true)
216+
.cache(null)
217+
218+
.connectTimeout(Connection.CONN_TIMEOUT, TimeUnit.SECONDS)
219+
.writeTimeout(Connection.CONN_TIMEOUT, TimeUnit.SECONDS)
220+
.readTimeout(Connection.CONN_TIMEOUT, TimeUnit.SECONDS);
221+
OkHttpClient httpClient = new OkHttpClient();
222+
Response httpResponse = httpClient.newCall(httpPost).execute();
213223
// we assume badAuthRaises flag from Anki Desktop always False
214224
// so just throw new RuntimeException if response code not 200 or 403
215225
assertOk(httpResponse);
@@ -295,7 +305,7 @@ private void publishProgress() {
295305
}
296306

297307

298-
public org.apache.http.HttpResponse hostKey(String arg1, String arg2) throws UnknownHttpResponseException {
308+
public Response hostKey(String arg1, String arg2) throws UnknownHttpResponseException {
299309
return null;
300310
}
301311

@@ -324,7 +334,7 @@ public void abort() throws UnknownHttpResponseException {
324334
}
325335

326336

327-
public org.apache.http.HttpResponse meta() throws UnknownHttpResponseException {
337+
public Response meta() throws UnknownHttpResponseException {
328338
return null;
329339
}
330340

@@ -348,7 +358,7 @@ public void applyChunk(JSONObject sech) throws UnknownHttpResponseException {
348358
// do nothing
349359
}
350360

351-
public class ProgressByteEntity extends org.apache.http.entity.AbstractHttpEntity {
361+
public class ProgressByteEntity extends AbstractHttpEntity {
352362

353363
private InputStream mInputStream;
354364
private long mLength;

AnkiDroid/src/main/java/com/ichi2/libanki/sync/RemoteMediaServer.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@
4242
import java.util.Locale;
4343
import java.util.zip.ZipFile;
4444

45+
import okhttp3.Response;
4546
import timber.log.Timber;
4647

47-
@SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes","PMD.MethodNamingConventions",
48-
"deprecation"}) // tracking HTTP transport change in github already
48+
@SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes","PMD.MethodNamingConventions"})
4949
public class RemoteMediaServer extends HttpSyncer {
5050

5151
private Collection mCol;
@@ -77,12 +77,12 @@ public JSONObject begin() throws UnknownHttpResponseException, MediaSyncExceptio
7777
mPostVars.put("v",
7878
String.format(Locale.US, "ankidroid,%s,%s", VersionUtils.getPkgVersionName(), Utils.platDesc()));
7979

80-
org.apache.http.HttpResponse resp = super.req("begin", super.getInputStream(Utils.jsonToString(new JSONObject())));
81-
JSONObject jresp = new JSONObject(super.stream2String(resp.getEntity().getContent()));
80+
Response resp = super.req("begin", super.getInputStream(Utils.jsonToString(new JSONObject())));
81+
JSONObject jresp = new JSONObject(super.stream2String(resp.body().byteStream()));
8282
JSONObject ret = _dataOnly(jresp, JSONObject.class);
8383
mSKey = ret.getString("sk");
8484
return ret;
85-
} catch (JSONException | IOException e) {
85+
} catch (JSONException | NullPointerException e) {
8686
throw new RuntimeException(e);
8787
}
8888
}
@@ -94,11 +94,11 @@ public JSONArray mediaChanges(int lastUsn) throws UnknownHttpResponseException,
9494
mPostVars = new HashMap<>();
9595
mPostVars.put("sk", mSKey);
9696

97-
org.apache.http.HttpResponse resp = super.req("mediaChanges",
97+
Response resp = super.req("mediaChanges",
9898
super.getInputStream(Utils.jsonToString(new JSONObject().put("lastUsn", lastUsn))));
99-
JSONObject jresp = new JSONObject(super.stream2String(resp.getEntity().getContent()));
99+
JSONObject jresp = new JSONObject(super.stream2String(resp.body().byteStream()));
100100
return _dataOnly(jresp, JSONArray.class);
101-
} catch (JSONException | IOException e) {
101+
} catch (JSONException | NullPointerException e) {
102102
throw new RuntimeException(e);
103103
}
104104
}
@@ -112,16 +112,16 @@ public JSONArray mediaChanges(int lastUsn) throws UnknownHttpResponseException,
112112
*/
113113
public ZipFile downloadFiles(List<String> top) throws UnknownHttpResponseException {
114114
try {
115-
org.apache.http.HttpResponse resp;
115+
Response resp;
116116
resp = super.req("downloadFiles",
117117
super.getInputStream(Utils.jsonToString(new JSONObject().put("files", new JSONArray(top)))));
118118
String zipPath = mCol.getPath().replaceFirst("collection\\.anki2$", "tmpSyncFromServer.zip");
119119
// retrieve contents and save to file on disk:
120-
super.writeToFile(resp.getEntity().getContent(), zipPath);
120+
super.writeToFile(resp.body().byteStream(), zipPath);
121121
return new ZipFile(new File(zipPath), ZipFile.OPEN_READ | ZipFile.OPEN_DELETE);
122122
} catch (JSONException e) {
123123
throw new RuntimeException(e);
124-
} catch (IOException e) {
124+
} catch (IOException | NullPointerException e) {
125125
Timber.e(e, "Failed to download requested media files");
126126
throw new RuntimeException(e);
127127
}
@@ -131,10 +131,10 @@ public ZipFile downloadFiles(List<String> top) throws UnknownHttpResponseExcepti
131131
public JSONArray uploadChanges(File zip) throws UnknownHttpResponseException, MediaSyncException {
132132
try {
133133
// no compression, as we compress the zip file instead
134-
org.apache.http.HttpResponse resp = super.req("uploadChanges", new FileInputStream(zip), 0);
135-
JSONObject jresp = new JSONObject(super.stream2String(resp.getEntity().getContent()));
134+
Response resp = super.req("uploadChanges", new FileInputStream(zip), 0);
135+
JSONObject jresp = new JSONObject(super.stream2String(resp.body().byteStream()));
136136
return _dataOnly(jresp, JSONArray.class);
137-
} catch (JSONException | IOException e) {
137+
} catch (JSONException | IOException | NullPointerException e) {
138138
throw new RuntimeException(e);
139139
}
140140
}
@@ -143,11 +143,11 @@ public JSONArray uploadChanges(File zip) throws UnknownHttpResponseException, Me
143143
// args: local
144144
public String mediaSanity(int lcnt) throws UnknownHttpResponseException, MediaSyncException {
145145
try {
146-
org.apache.http.HttpResponse resp = super.req("mediaSanity",
146+
Response resp = super.req("mediaSanity",
147147
super.getInputStream(Utils.jsonToString(new JSONObject().put("local", lcnt))));
148-
JSONObject jresp = new JSONObject(super.stream2String(resp.getEntity().getContent()));
148+
JSONObject jresp = new JSONObject(super.stream2String(resp.body().byteStream()));
149149
return _dataOnly(jresp, String.class);
150-
} catch (JSONException | IOException e) {
150+
} catch (JSONException | NullPointerException e) {
151151
throw new RuntimeException(e);
152152
}
153153
}

0 commit comments

Comments
 (0)