|
1 | 1 | package uniresolver.driver.http; |
2 | 2 |
|
3 | | -import java.io.IOException; |
4 | | -import java.io.UnsupportedEncodingException; |
5 | | -import java.net.URI; |
6 | | -import java.net.URLEncoder; |
7 | | -import java.util.Collections; |
8 | | -import java.util.HashMap; |
9 | | -import java.util.List; |
10 | | -import java.util.Map; |
11 | | -import java.util.regex.Matcher; |
12 | | -import java.util.regex.Pattern; |
13 | | - |
14 | | -import foundation.identity.did.DIDDocument; |
| 3 | +import com.fasterxml.jackson.databind.ObjectMapper; |
| 4 | +import foundation.identity.did.DID; |
| 5 | +import foundation.identity.did.parser.ParserException; |
15 | 6 | import org.apache.http.HttpEntity; |
16 | 7 | import org.apache.http.client.HttpClient; |
17 | 8 | import org.apache.http.client.methods.CloseableHttpResponse; |
18 | 9 | import org.apache.http.client.methods.HttpGet; |
| 10 | +import org.apache.http.entity.ContentType; |
19 | 11 | import org.apache.http.impl.client.HttpClients; |
| 12 | +import org.apache.http.protocol.HTTP; |
20 | 13 | import org.apache.http.util.EntityUtils; |
21 | 14 | import org.slf4j.Logger; |
22 | 15 | import org.slf4j.LoggerFactory; |
23 | | - |
24 | | -import com.fasterxml.jackson.databind.ObjectMapper; |
25 | | - |
26 | 16 | import uniresolver.ResolutionException; |
27 | 17 | import uniresolver.driver.Driver; |
28 | 18 | import uniresolver.result.ResolveResult; |
| 19 | +import uniresolver.util.HttpBindingUtil; |
29 | 20 |
|
30 | | -public class HttpDriver implements Driver { |
| 21 | +import java.io.IOException; |
| 22 | +import java.net.URI; |
| 23 | +import java.net.URLEncoder; |
| 24 | +import java.nio.charset.Charset; |
| 25 | +import java.nio.charset.StandardCharsets; |
| 26 | +import java.util.*; |
| 27 | +import java.util.regex.Matcher; |
| 28 | +import java.util.regex.Pattern; |
31 | 29 |
|
32 | | - public static final String MIME_TYPES = ResolveResult.MIME_TYPE + "," + DIDDocument.MIME_TYPE_JSON_LD + "," + "application/ld+json"; |
| 30 | +public class HttpDriver implements Driver { |
33 | 31 |
|
34 | | - private static Logger log = LoggerFactory.getLogger(HttpDriver.class); |
| 32 | + private static final Logger log = LoggerFactory.getLogger(HttpDriver.class); |
35 | 33 |
|
36 | 34 | private static final ObjectMapper objectMapper = new ObjectMapper(); |
37 | 35 |
|
38 | | - public static final HttpClient DEFAULT_HTTP_CLIENT = HttpClients.createDefault(); |
39 | | - public static final URI DEFAULT_RESOLVE_URI = null; |
40 | | - public static final URI DEFAULT_PROPERTIES_URI = null; |
41 | | - public static final Pattern DEFAULT_PATTERN = null; |
42 | | - public static final List<String> DEFAULT_TEST_IDENTIFIERS = Collections.emptyList(); |
43 | | - |
44 | | - private HttpClient httpClient = DEFAULT_HTTP_CLIENT; |
45 | | - private URI resolveUri = DEFAULT_RESOLVE_URI; |
46 | | - private URI propertiesUri = DEFAULT_PROPERTIES_URI; |
47 | | - private Pattern pattern = DEFAULT_PATTERN; |
48 | | - private List<String> testIdentifiers = DEFAULT_TEST_IDENTIFIERS; |
| 36 | + private HttpClient httpClient = HttpClients.createDefault(); |
| 37 | + private URI resolveUri = null; |
| 38 | + private URI propertiesUri = null; |
| 39 | + private Pattern pattern = null; |
| 40 | + private List<String> testIdentifiers = Collections.emptyList(); |
49 | 41 |
|
50 | 42 | public HttpDriver() { |
51 | 43 |
|
52 | 44 | } |
53 | 45 |
|
54 | 46 | @Override |
55 | | - public ResolveResult resolve(String identifier) throws ResolutionException { |
| 47 | + public ResolveResult resolveRepresentation(DID did, Map<String, Object> resolutionOptions) throws ResolutionException { |
56 | 48 |
|
57 | 49 | if (this.getPattern() == null || this.getResolveUri() == null) return null; |
58 | 50 |
|
59 | | - // match identifier |
| 51 | + // match DID |
60 | 52 |
|
61 | | - String matchedIdentifier = null; |
| 53 | + DID matchedDid = null; |
62 | 54 |
|
63 | 55 | if (this.getPattern() != null) { |
64 | 56 |
|
65 | | - Matcher matcher = this.getPattern().matcher(identifier); |
| 57 | + Matcher matcher = this.getPattern().matcher(did.getDidString()); |
66 | 58 |
|
67 | 59 | if (! matcher.matches()) { |
68 | | - |
69 | | - if (log.isDebugEnabled()) log.debug("Skipping identifier " + identifier + " - does not match pattern " + this.getPattern()); |
| 60 | + if (log.isDebugEnabled()) log.debug("Skipping identifier " + did + " - does not match pattern " + this.getPattern()); |
70 | 61 | return null; |
71 | 62 | } else { |
72 | | - |
73 | | - if (log.isDebugEnabled()) log.debug("Identifier " + identifier + " matches pattern " + this.getPattern() + " with " + matcher.groupCount() + " groups"); |
| 63 | + if (log.isDebugEnabled()) log.debug("Identifier " + did + " matches pattern " + this.getPattern() + " with " + matcher.groupCount() + " groups"); |
74 | 64 | } |
75 | 65 |
|
76 | 66 | if (matcher.groupCount() > 0) { |
77 | 67 |
|
78 | | - identifier = ""; |
79 | | - for (int i=1; i<=matcher.groupCount(); i++) if (matcher.group(i) != null) identifier += matcher.group(i); |
| 68 | + String matchedDidString = ""; |
| 69 | + for (int i=1; i<=matcher.groupCount(); i++) if (matcher.group(i) != null) matchedDidString += matcher.group(i); |
| 70 | + try { |
| 71 | + matchedDid = DID.fromString(matchedDidString); |
| 72 | + } catch (ParserException ex) { |
| 73 | + throw new ResolutionException("Cannot parse matched DID " + matchedDidString + ": " + ex.getMessage(), ex); |
| 74 | + } |
80 | 75 | } |
81 | 76 | } |
82 | 77 |
|
83 | | - if (matchedIdentifier == null) matchedIdentifier = identifier; |
84 | | - |
85 | | - if (log.isDebugEnabled()) log.debug("Matched identifier: " + matchedIdentifier); |
| 78 | + if (matchedDid == null) matchedDid = did; |
| 79 | + if (log.isDebugEnabled()) log.debug("Matched DID: " + matchedDid); |
86 | 80 |
|
87 | | - // URL-encode identifier |
| 81 | + // URL-encode DID |
88 | 82 |
|
89 | | - String urlEncodedIdentifier; |
90 | | - |
91 | | - try { |
| 83 | + String urlEncodedDid = URLEncoder.encode(matchedDid.getDidString(), StandardCharsets.UTF_8); |
92 | 84 |
|
93 | | - urlEncodedIdentifier = URLEncoder.encode(matchedIdentifier, "UTF-8"); |
94 | | - } catch (UnsupportedEncodingException ex) { |
95 | | - |
96 | | - throw new ResolutionException(ex.getMessage(), ex); |
97 | | - } |
98 | | - |
99 | | - // prepare HTTP request |
| 85 | + // set HTTP URI |
100 | 86 |
|
101 | 87 | String uriString = this.getResolveUri().toString(); |
102 | 88 |
|
103 | 89 | if (uriString.contains("$1")) { |
104 | 90 |
|
105 | | - uriString = uriString.replace("$1", matchedIdentifier); |
| 91 | + uriString = uriString.replace("$1", matchedDid.getDidString()); |
106 | 92 | } else if (uriString.contains("$2")) { |
107 | 93 |
|
108 | | - uriString = uriString.replace("$2", urlEncodedIdentifier); |
| 94 | + uriString = uriString.replace("$2", urlEncodedDid); |
109 | 95 | } else { |
110 | 96 |
|
111 | 97 | if (! uriString.endsWith("/")) uriString += "/"; |
112 | | - uriString += matchedIdentifier; |
| 98 | + uriString += matchedDid.getDidString(); |
113 | 99 | } |
114 | 100 |
|
| 101 | + // set Accept header |
| 102 | + |
| 103 | + String accept = (String) resolutionOptions.get("accept"); |
| 104 | + if (accept == null) throw new ResolutionException("No 'accept' provided in 'resolutionOptions' for resolveRepresentation()."); |
| 105 | + |
| 106 | + List<String> acceptMediaTypes = Arrays.asList(ResolveResult.MEDIA_TYPE, accept); |
| 107 | + String acceptMediaTypesString = String.join(",", acceptMediaTypes); |
| 108 | + |
| 109 | + // prepare HTTP request |
| 110 | + |
115 | 111 | HttpGet httpGet = new HttpGet(URI.create(uriString)); |
116 | | - httpGet.addHeader("Accept", MIME_TYPES); |
| 112 | + httpGet.addHeader("Accept", acceptMediaTypesString); |
117 | 113 |
|
118 | | - // execute HTTP request |
| 114 | + // execute HTTP request and read response |
119 | 115 |
|
120 | | - ResolveResult resolveResult; |
| 116 | + ResolveResult resolveResult = null; |
121 | 117 |
|
122 | | - if (log.isDebugEnabled()) log.debug("Request for identifier " + identifier + " to: " + uriString); |
| 118 | + if (log.isDebugEnabled()) log.debug("Driver request for DID " + did + " to: " + uriString); |
123 | 119 |
|
124 | 120 | try (CloseableHttpResponse httpResponse = (CloseableHttpResponse) this.getHttpClient().execute(httpGet)) { |
125 | 121 |
|
| 122 | + // execute HTTP request |
| 123 | + |
| 124 | + HttpEntity httpEntity = httpResponse.getEntity(); |
126 | 125 | int statusCode = httpResponse.getStatusLine().getStatusCode(); |
127 | 126 | String statusMessage = httpResponse.getStatusLine().getReasonPhrase(); |
| 127 | + ContentType contentType = ContentType.get(httpResponse.getEntity()); |
| 128 | + Charset charset = contentType != null ? contentType.getCharset() : HTTP.DEF_CONTENT_CHARSET; |
128 | 129 |
|
129 | | - if (log.isDebugEnabled()) log.debug("Response status from " + uriString + ": " + statusCode + " " + statusMessage); |
| 130 | + if (log.isDebugEnabled()) log.debug("Driver response status from " + uriString + ": " + statusCode + " " + statusMessage); |
| 131 | + if (log.isDebugEnabled()) log.debug("Driver response content type from " + uriString + ": " + contentType + " / " + charset); |
130 | 132 |
|
131 | | - if (statusCode == 404) return null; |
| 133 | + // read result |
132 | 134 |
|
133 | | - HttpEntity httpEntity = httpResponse.getEntity(); |
134 | | - String httpBody = EntityUtils.toString(httpEntity); |
| 135 | + byte[] httpBodyBytes = EntityUtils.toByteArray(httpEntity); |
| 136 | + String httpBodyString = new String(httpBodyBytes, charset); |
135 | 137 | EntityUtils.consume(httpEntity); |
136 | 138 |
|
137 | | - if (log.isDebugEnabled()) log.debug("Response body from " + uriString + ": " + httpBody); |
| 139 | + if (log.isDebugEnabled()) log.debug("Driver response body from " + uriString + ": " + httpBodyString); |
138 | 140 |
|
139 | | - if (httpResponse.getStatusLine().getStatusCode() > 200) { |
| 141 | + if (contentType != null && isResolveContentType(contentType)) { |
| 142 | + resolveResult = HttpBindingUtil.fromHttpBodyResolveResult(httpBodyString); |
| 143 | + } |
140 | 144 |
|
141 | | - if (log.isWarnEnabled()) log.warn("Cannot retrieve RESOLVE RESULT for " + identifier + " from " + uriString + ": " + httpBody); |
142 | | - throw new ResolutionException(httpBody); |
| 145 | + if (statusCode == 404 && resolveResult == null) { |
| 146 | + resolveResult = ResolveResult.makeErrorResult(ResolveResult.Error.notFound, statusCode + " " + statusMessage + " (" + httpBodyString + ")"); |
143 | 147 | } |
144 | 148 |
|
145 | | - try { |
| 149 | + if (statusCode == 406 && resolveResult == null) { |
| 150 | + resolveResult = ResolveResult.makeErrorResult(ResolveResult.Error.representationNotSupported, statusCode + " " + statusMessage + " (" + httpBodyString + ")"); |
| 151 | + } |
146 | 152 |
|
147 | | - resolveResult = ResolveResult.fromJson(httpBody); |
148 | | - } catch (Exception ex) { |
| 153 | + if (statusCode != 200 && resolveResult == null) { |
| 154 | + resolveResult = ResolveResult.makeErrorResult(ResolveResult.Error.internalError, "Cannot retrieve result for " + did + ": " + statusCode + " " + statusMessage + " (" + httpBodyString + ")"); |
| 155 | + if (log.isWarnEnabled()) log.warn("Driver cannot retrieve result for " + did + " from " + uriString + ": " + resolveResult.getError() + " - " + resolveResult.getErrorMessage()); |
| 156 | + } |
149 | 157 |
|
150 | | - if (log.isWarnEnabled()) log.warn("No RESOLVE RESULT. Maybe DID DOCUMENT: " + httpBody + " (" + ex.getMessage()); |
151 | | - resolveResult = ResolveResult.build(DIDDocument.fromJson(httpBody)); |
| 158 | + if (resolveResult != null && resolveResult.isErrorResult()) { |
| 159 | + throw new ResolutionException(resolveResult); |
152 | 160 | } |
153 | | - } catch (IOException ex) { |
154 | 161 |
|
155 | | - throw new ResolutionException("Cannot retrieve RESOLVE RESULT for " + identifier + " from " + uriString + ": " + ex.getMessage(), ex); |
| 162 | + if (resolveResult == null) { |
| 163 | + resolveResult = HttpBindingUtil.fromHttpBodyDidDocument(httpBodyBytes, contentType); |
| 164 | + } |
| 165 | + } catch (ResolutionException ex) { |
| 166 | + |
| 167 | + throw ex; |
| 168 | + } catch (Exception ex) { |
| 169 | + |
| 170 | + throw new ResolutionException("Driver cannot retrieve resolve result for " + did + " from " + uriString + ": " + ex.getMessage(), ex); |
156 | 171 | } |
157 | 172 |
|
158 | | - if (log.isDebugEnabled()) log.debug("Retrieved RESOLVE RESULT for " + identifier + " (" + uriString + "): " + resolveResult); |
| 173 | + if (log.isDebugEnabled()) log.debug("Driver retrieved resolve result for " + did + " (" + uriString + "): " + resolveResult); |
159 | 174 |
|
160 | 175 | // done |
161 | 176 |
|
@@ -251,72 +266,69 @@ public List<String> testIdentifiers() throws ResolutionException { |
251 | 266 | return this.getTestIdentifiers(); |
252 | 267 | } |
253 | 268 |
|
| 269 | + /* |
| 270 | + * Helper methods |
| 271 | + */ |
| 272 | + |
| 273 | + private static final ContentType RESOLVE_RESULT_CONTENT_TYPE = ContentType.parse(ResolveResult.MEDIA_TYPE); |
| 274 | + |
| 275 | + private static boolean isResolveContentType(ContentType contentType) { |
| 276 | + return RESOLVE_RESULT_CONTENT_TYPE.getMimeType().equals(contentType.getMimeType()) && RESOLVE_RESULT_CONTENT_TYPE.getParameter("profile").equals(contentType.getParameter("profile")); |
| 277 | + } |
| 278 | + |
254 | 279 | /* |
255 | 280 | * Getters and setters |
256 | 281 | */ |
257 | 282 |
|
258 | 283 | public HttpClient getHttpClient() { |
259 | | - |
260 | 284 | return this.httpClient; |
261 | 285 | } |
262 | 286 |
|
263 | 287 | public void setHttpClient(HttpClient httpClient) { |
264 | | - |
265 | 288 | this.httpClient = httpClient; |
266 | 289 | } |
267 | 290 |
|
268 | 291 | public URI getResolveUri() { |
269 | | - |
270 | 292 | return this.resolveUri; |
271 | 293 | } |
272 | 294 |
|
273 | 295 | public void setResolveUri(URI resolveUri) { |
274 | | - |
275 | 296 | this.resolveUri = resolveUri; |
276 | 297 | } |
277 | 298 |
|
278 | 299 | public void setResolveUri(String resolveUri) { |
279 | | - |
280 | 300 | this.resolveUri = URI.create(resolveUri); |
281 | 301 | } |
282 | 302 |
|
283 | 303 | public URI getPropertiesUri() { |
284 | | - |
285 | 304 | return this.propertiesUri; |
286 | 305 | } |
287 | 306 |
|
288 | 307 | public void setPropertiesUri(URI propertiesUri) { |
289 | | - |
290 | 308 | this.propertiesUri = propertiesUri; |
291 | 309 | } |
292 | 310 |
|
293 | 311 | public void setPropertiesUri(String propertiesUri) { |
294 | | - |
295 | 312 | this.propertiesUri = URI.create(propertiesUri); |
296 | 313 | } |
297 | 314 |
|
298 | 315 | public Pattern getPattern() { |
299 | | - |
300 | 316 | return this.pattern; |
301 | 317 | } |
302 | 318 |
|
303 | 319 | public void setPattern(Pattern pattern) { |
304 | | - |
305 | 320 | this.pattern = pattern; |
306 | 321 | } |
307 | 322 |
|
308 | 323 | public void setPattern(String pattern) { |
309 | | - |
310 | 324 | this.pattern = Pattern.compile(pattern); |
311 | 325 | } |
312 | 326 |
|
313 | 327 | public List<String> getTestIdentifiers() { |
314 | | - |
315 | 328 | return this.testIdentifiers; |
316 | 329 | } |
317 | 330 |
|
318 | 331 | public void setTestIdentifiers(List<String> testIdentifiers) { |
319 | | - |
320 | 332 | this.testIdentifiers = testIdentifiers; |
321 | 333 | } |
322 | 334 | } |
0 commit comments