1 package org.metricshub.http;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import java.io.ByteArrayOutputStream;
24 import java.io.File;
25 import java.io.FileNotFoundException;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.net.Authenticator;
31 import java.net.HttpURLConnection;
32 import java.net.InetSocketAddress;
33 import java.net.MalformedURLException;
34 import java.net.Proxy;
35 import java.net.URL;
36 import java.nio.charset.Charset;
37 import java.nio.charset.StandardCharsets;
38 import java.security.KeyManagementException;
39 import java.security.NoSuchAlgorithmException;
40 import java.util.Arrays;
41 import java.util.Map;
42 import java.util.regex.Matcher;
43 import java.util.regex.Pattern;
44 import java.util.zip.GZIPInputStream;
45 import java.util.zip.InflaterInputStream;
46 import javax.net.ssl.HostnameVerifier;
47 import javax.net.ssl.HttpsURLConnection;
48 import javax.net.ssl.SSLContext;
49 import javax.net.ssl.SSLSession;
50 import javax.net.ssl.SSLSocketFactory;
51 import javax.net.ssl.TrustManager;
52 import javax.net.ssl.X509TrustManager;
53
54
55
56
57
58 public class HttpClient {
59 static {
60
61 System.setProperty("jdk.tls.acknowledgeCloseNotify", "true");
62 }
63
64
65
66
67 public static final String DEFAULT_USER_AGENT =
68 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393 org.metricshub.http";
69 private static final int MAX_CONTENT_LENGTH = 50 * 1024 * 1024;
70 private static final int BUFFER_SIZE = 64 * 1024;
71 private static final Charset UTF8_CHARSET = StandardCharsets.UTF_8;
72 private static final Pattern CHARSET_REGEX = Pattern.compile("charset=\\s*\"?([^; \"]+)", Pattern.CASE_INSENSITIVE);
73
74
75
76
77 private static final HostnameVerifier LOUSY_HOSTNAME_VERIFIER = (String urlHostName, SSLSession session) -> true;
78
79
80
81
82 private static final TrustManager[] LOUSY_TRUST_MANAGER = new TrustManager[] {
83 new X509TrustManager() {
84 @Override
85 public java.security.cert.X509Certificate[] getAcceptedIssuers() {
86 return null;
87 }
88
89 @Override
90 public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
91
92 @Override
93 public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
94 }
95 };
96
97
98
99
100 private static final SSLSocketFactory BASE_SOCKET_FACTORY;
101
102
103
104
105 private static final String[] DEFAULT_SSL_PROTOCOLS;
106
107 static {
108 SSLContext sc = null;
109 try {
110 sc = SSLContext.getInstance("SSL");
111 sc.init(null, LOUSY_TRUST_MANAGER, new java.security.SecureRandom());
112 } catch (NoSuchAlgorithmException | KeyManagementException e) {}
113 BASE_SOCKET_FACTORY = sc.getSocketFactory();
114 DEFAULT_SSL_PROTOCOLS = sc.getDefaultSSLParameters().getProtocols();
115 }
116
117
118
119
120
121
122
123
124 private static InputStream getDecodedStream(HttpURLConnection httpURL) {
125 String contentEncoding = httpURL.getContentEncoding();
126
127 try {
128
129 if ("gzip".equalsIgnoreCase(contentEncoding)) {
130 return new GZIPInputStream(httpURL.getInputStream());
131 } else if ("deflate".equalsIgnoreCase(contentEncoding)) {
132 return new InflaterInputStream(httpURL.getInputStream());
133 } else {
134 return httpURL.getInputStream();
135 }
136 } catch (IOException e) {
137
138 return httpURL.getErrorStream();
139 }
140 }
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162 public static HttpResponse sendRequest(
163 String url,
164 String method,
165 String[] specifiedSslProtocolArray,
166 String username,
167 char[] password,
168 String proxyServer,
169 int proxyPort,
170 String proxyUsername,
171 char[] proxyPassword,
172 String userAgent,
173 Map<String, String> addHeaderMap,
174 String body,
175 int timeout,
176 String downloadToPath
177 ) throws IOException {
178
179 boolean useProxy = proxyServer != null && !proxyServer.isEmpty();
180
181
182 HttpURLConnection httpURL;
183 if (!useProxy) {
184 httpURL = (HttpURLConnection) new URL(url).openConnection();
185 } else {
186 Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyServer, proxyPort));
187 httpURL = (HttpURLConnection) new URL(url).openConnection(proxy);
188 }
189
190
191
192 File downloadToFile = null;
193 if (downloadToPath != null && !downloadToPath.isEmpty()) {
194
195 downloadToFile = new File(downloadToPath);
196
197
198 File parentDirectory = downloadToFile.getParentFile();
199 if (parentDirectory != null && !parentDirectory.exists()) {
200
201 try {
202
203 parentDirectory.mkdirs();
204 } catch (SecurityException e) {
205 throw new IOException("Couldn't create the necessary directories for " + downloadToPath);
206 }
207 }
208 }
209
210
211
212
213
214
215 if (httpURL instanceof HttpsURLConnection) {
216
217
218 ((HttpsURLConnection) httpURL).setHostnameVerifier(LOUSY_HOSTNAME_VERIFIER);
219
220
221 if (specifiedSslProtocolArray == null || specifiedSslProtocolArray.length == 0) {
222
223 ((HttpsURLConnection) httpURL).setSSLSocketFactory(BASE_SOCKET_FACTORY);
224 } else {
225
226 String[] protocolsToEnable = Arrays
227 .stream(specifiedSslProtocolArray)
228 .filter(p -> p != null && !"SSLv2Hello".equalsIgnoreCase(p))
229 .filter(p -> Arrays.stream(DEFAULT_SSL_PROTOCOLS).anyMatch(d -> d.equalsIgnoreCase(p)))
230 .toArray(String[]::new);
231
232
233 SSLSocketFactory overridenSocketFactory = new ProtocolOverridingSSLSocketFactory(
234 BASE_SOCKET_FACTORY,
235 protocolsToEnable
236 );
237 ((HttpsURLConnection) httpURL).setSSLSocketFactory(overridenSocketFactory);
238 }
239 }
240
241
242 httpURL.setRequestMethod(method);
243 httpURL.setDefaultUseCaches(false);
244 httpURL.setDoOutput(true);
245 httpURL.setDoInput(true);
246 httpURL.setConnectTimeout(timeout * 1000);
247 httpURL.setReadTimeout(timeout * 1000);
248 httpURL.setAllowUserInteraction(false);
249 httpURL.setInstanceFollowRedirects(true);
250
251
252 if (userAgent == null || userAgent.isEmpty()) {
253 userAgent = DEFAULT_USER_AGENT;
254 }
255 httpURL.addRequestProperty("User-Agent", userAgent);
256
257
258 if (addHeaderMap != null) {
259 addHeaderMap.forEach((header, value) -> {
260 if (header != null && value != null && !header.isEmpty() && !value.isEmpty()) {
261 httpURL.addRequestProperty(header, value);
262 }
263 });
264 }
265
266
267 ThreadSafeNoCacheAuthenticator.setCredentials(username, password, proxyUsername, proxyPassword);
268 Authenticator.setDefault(ThreadSafeNoCacheAuthenticator.getInstance());
269
270
271 try {
272 httpURL.connect();
273
274
275 if (body != null && !body.isEmpty()) {
276 try (OutputStream os = httpURL.getOutputStream()) {
277 os.write(body.getBytes(UTF8_CHARSET));
278 }
279 }
280
281
282 HttpResponse response = new HttpResponse();
283
284
285
286 response.setStatusCode(httpURL.getResponseCode());
287
288
289 httpURL
290 .getHeaderFields()
291 .forEach((header, valueList) -> valueList.forEach(value -> response.appendHeader(header, value)));
292
293
294 if (downloadToFile != null) {
295
296
297
298
299
300
301
302 if (downloadToFile.isDirectory()) {
303 String tempFilename = httpURL.getURL().getPath();
304 String filename = tempFilename.substring(tempFilename.lastIndexOf('/'));
305 downloadToPath = new File(downloadToFile, filename).getPath();
306 }
307
308
309 try (
310 FileOutputStream fileStream = new FileOutputStream(downloadToPath);
311 InputStream httpStream = getDecodedStream(httpURL)
312 ) {
313 byte[] tempBuf = new byte[BUFFER_SIZE];
314 int readBytes;
315 while ((readBytes = httpStream.read(tempBuf)) != -1) {
316 fileStream.write(tempBuf, 0, readBytes);
317 }
318 }
319
320
321
322 response.appendBody(downloadToPath);
323
324
325 return response;
326 }
327
328
329
330
331 int contentLength = httpURL.getContentLength();
332
333
334 if (contentLength > MAX_CONTENT_LENGTH) {
335 throw new IOException("Content is too large (" + contentLength + " bytes > " + MAX_CONTENT_LENGTH + " bytes)");
336 }
337
338
339 Charset charset = UTF8_CHARSET;
340 String contentType = httpURL.getContentType();
341 if (contentType != null) {
342 Matcher charsetMatcher = CHARSET_REGEX.matcher(contentType);
343 if (charsetMatcher.find()) {
344 charset = Charset.forName(charsetMatcher.group(1));
345 }
346 }
347
348
349 ByteArrayOutputStream bodyBytes = contentLength > 0
350 ? new ByteArrayOutputStream(contentLength)
351 : new ByteArrayOutputStream();
352
353 byte[] buffer = new byte[BUFFER_SIZE];
354 int totalBytesCount = 0;
355 int bytesCount;
356
357 try (InputStream httpStream = getDecodedStream(httpURL)) {
358 while (httpStream != null && (bytesCount = httpStream.read(buffer)) != -1) {
359 bodyBytes.write(buffer, 0, bytesCount);
360 totalBytesCount += bytesCount;
361 if (totalBytesCount > MAX_CONTENT_LENGTH) {
362 throw new IOException("Content is too large (maximum " + MAX_CONTENT_LENGTH + " bytes)");
363 }
364 }
365 }
366 response.appendBody(new String(bodyBytes.toByteArray(), charset));
367
368
369 return response;
370 } finally {
371
372 httpURL.disconnect();
373
374
375 ThreadSafeNoCacheAuthenticator.clearCredentials();
376 }
377 }
378 }