forked from kamax-matrix/matrix-java-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAMatrixHttpClient.java
296 lines (245 loc) · 10.7 KB
/
AMatrixHttpClient.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
/*
* matrix-java-sdk - Matrix Client SDK for Java
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.matrix.client;
import com.google.gson.Gson;
import com.google.gson.JsonParser;
import io.kamax.matrix.MatrixErrorInfo;
import io.kamax.matrix._MatrixID;
import io.kamax.matrix.hs._MatrixHomeserver;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.entity.EntityBuilder;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class AMatrixHttpClient implements _MatrixClientRaw {
private Logger log = LoggerFactory.getLogger(AMatrixHttpClient.class);
protected MatrixClientContext context;
protected Gson gson = new Gson();
protected JsonParser jsonParser = new JsonParser();
private CloseableHttpClient client = HttpClients.createDefault();
private Pattern accessTokenUrlPattern = Pattern.compile("\\?access_token=(?<token>[^&]*)");
public AMatrixHttpClient(MatrixClientContext context) {
this.context = context;
}
@Override
public MatrixClientContext getContext() {
return context;
}
@Override
public _MatrixHomeserver getHomeserver() {
return context.getHs();
}
@Override
public Optional<String> getAccessToken() {
return Optional.ofNullable(context.getToken());
}
@Override
public String getAccessTokenOrThrow() {
return getAccessToken()
.orElseThrow(() -> new IllegalStateException("This method can only be used with a valid token."));
}
@Override
public _MatrixID getUser() {
return context.getUser();
}
protected String execute(HttpRequestBase request) {
return execute(new MatrixHttpRequest(request));
}
protected String execute(MatrixHttpRequest matrixRequest) {
log(matrixRequest.getHttpRequest());
try (CloseableHttpResponse response = client.execute(matrixRequest.getHttpRequest())) {
String body = getBody(response.getEntity());
int responseStatus = response.getStatusLine().getStatusCode();
if (responseStatus == 200) {
log.debug("Request successfully executed.");
} else if (matrixRequest.getIgnoredErrorCodes().contains(responseStatus)) {
log.debug("Error code ignored: " + responseStatus);
return "";
} else {
MatrixErrorInfo info = createErrorInfo(body, responseStatus);
body = handleError(matrixRequest, responseStatus, info);
}
return body;
} catch (IOException e) {
throw new MatrixClientRequestException(e);
}
}
/**
* Default handling of errors. Can be overwritten by a custom implementation in inherited classes.
*
* @param matrixRequest
* @param responseStatus
* @param info
* @return body of the response of a repeated call of the request, else this methods throws a
* MatrixClientRequestException
*/
protected String handleError(MatrixHttpRequest matrixRequest, int responseStatus, MatrixErrorInfo info) {
String message = String.format("Request failed with status code: %s", responseStatus);
if (responseStatus == 429) {
return handleRateLimited(matrixRequest, info);
}
throw new MatrixClientRequestException(info, message);
}
/**
* Default handling of rate limited calls. Can be overwritten by a custom implementation in inherited classes.
*
* @param matrixRequest
* @param info
* @return body of the response of a repeated call of the request, else this methods throws a
* MatrixClientRequestException
*/
protected String handleRateLimited(MatrixHttpRequest matrixRequest, MatrixErrorInfo info) {
throw new MatrixClientRequestException(info, "Request was rate limited.");
// TODO Add default handling of rate limited call, i.e. repeated call after given time interval.
// 1. Wait for timeout
// 2. return execute(request)
}
protected MatrixHttpContentResult executeContentRequest(MatrixHttpRequest matrixRequest) {
log(matrixRequest.getHttpRequest());
try (CloseableHttpResponse response = client.execute(matrixRequest.getHttpRequest())) {
HttpEntity entity = response.getEntity();
int responseStatus = response.getStatusLine().getStatusCode();
MatrixHttpContentResult result = new MatrixHttpContentResult(response);
if (responseStatus == 200) {
log.debug("Request successfully executed.");
if (entity == null) {
log.debug("No data received.");
} else if (entity.getContentType() == null) {
log.debug("No content type was given.");
}
} else if (matrixRequest.getIgnoredErrorCodes().contains(responseStatus)) {
log.debug("Error code ignored: " + responseStatus);
} else {
String body = getBody(entity);
MatrixErrorInfo info = createErrorInfo(body, responseStatus);
result = handleErrorContentRequest(matrixRequest, responseStatus, info);
}
return result;
} catch (IOException e) {
throw new MatrixClientRequestException(e);
}
}
protected MatrixHttpContentResult handleErrorContentRequest(MatrixHttpRequest matrixRequest, int responseStatus,
MatrixErrorInfo info) {
String message = String.format("Request failed with status code: %s", responseStatus);
if (responseStatus == 429) {
return handleRateLimitedContentRequest(matrixRequest, info);
}
throw new MatrixClientRequestException(info, message);
}
protected MatrixHttpContentResult handleRateLimitedContentRequest(MatrixHttpRequest matrixRequest,
MatrixErrorInfo info) {
throw new MatrixClientRequestException(info, "Request was rate limited.");
// TODO Add default handling of rate limited call, i.e. repeated call after given time interval.
// 1. Wait for timeout
// 2. return execute(request)
}
protected Optional<String> extractAsStringFromBody(String body, String jsonObjectName) {
if (StringUtils.isNotEmpty(body)) {
return Optional.of(new JsonParser().parse(body).getAsJsonObject().get(jsonObjectName).getAsString());
}
return Optional.empty();
}
private String getBody(HttpEntity entity) throws IOException {
Charset charset = ContentType.getOrDefault(entity).getCharset();
return IOUtils.toString(entity.getContent(), charset);
}
private MatrixErrorInfo createErrorInfo(String body, int responseStatus) {
MatrixErrorInfo info = gson.fromJson(body, MatrixErrorInfo.class);
log.debug("Request returned with an error. Status code: {}, errcode: {}, error: {}", responseStatus,
info.getErrcode(), info.getError());
return info;
}
private void log(HttpRequestBase req) {
String reqUrl = req.getURI().toASCIIString();
Matcher m = accessTokenUrlPattern.matcher(reqUrl);
if (m.find()) {
StringBuilder b = new StringBuilder();
b.append(reqUrl.substring(0, m.start("token")));
b.append("<redacted>");
b.append(reqUrl.substring(m.end("token"), reqUrl.length()));
reqUrl = b.toString();
}
log.debug("Doing {} {}", req.getMethod(), reqUrl);
}
protected URIBuilder getPathBuilder(String module, String version, String action) {
URIBuilder builder = context.getHs().getClientEndpoint();
builder.setPath(builder.getPath() + "/_matrix/" + module + "/" + version + action);
if (context.isVirtualUser()) {
builder.setParameter("user_id", context.getUser().getId());
}
return builder;
}
protected URIBuilder getClientPathBuilder(String action) {
return getPathBuilder("client", "r0", action);
}
protected URIBuilder getMediaPathBuilder(String action) {
return getPathBuilder("media", "v1", action);
}
protected URI getClientPathWithAccessToken(String action) {
try {
URIBuilder builder = getClientPathBuilder(action);
builder.setParameter("access_token", getAccessTokenOrThrow());
return builder.build();
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
protected URI getClientPath(String action) {
try {
return getClientPathBuilder(action).build();
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
protected URI getMediaPathWithAccessToken(String action) {
try {
URIBuilder builder = getMediaPathBuilder(action);
builder.setParameter("access_token", getAccessTokenOrThrow());
return builder.build();
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
protected URI getMediaPath(String action) {
try {
return getMediaPathBuilder(action).build();
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
protected HttpEntity getJsonEntity(Object o) {
return EntityBuilder.create().setText(gson.toJson(o)).setContentType(ContentType.APPLICATION_JSON).build();
}
}