source: josm/trunk/src/org/openstreetmap/josm/tools/HttpClient.java@ 9168

Last change on this file since 9168 was 9168, checked in by simon04, 8 years ago

see #12231 - Uniform access to HTTP resources

File size: 9.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.BufferedOutputStream;
7import java.io.BufferedReader;
8import java.io.IOException;
9import java.io.InputStream;
10import java.io.InputStreamReader;
11import java.io.OutputStream;
12import java.net.HttpURLConnection;
13import java.net.URL;
14import java.nio.charset.StandardCharsets;
15import java.util.Map;
16import java.util.concurrent.ConcurrentHashMap;
17import java.util.zip.GZIPInputStream;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.data.Version;
21
22/**
23 * Provides a uniform access for a HTTP/HTTPS server. This class should be used in favour of {@link HttpURLConnection}.
24 */
25public class HttpClient {
26
27 private URL url;
28 private final String requestMethod;
29 private int connectTimeout = Main.pref.getInteger("socket.timeout.connect", 15) * 1000;
30 private int readTimeout = Main.pref.getInteger("socket.timeout.read", 30) * 1000;
31 private String accept;
32 private String contentType;
33 private String acceptEncoding = "gzip";
34 private long contentLength;
35 private byte[] requestBody;
36 private long ifModifiedSince;
37 private final Map<String, String> headers = new ConcurrentHashMap<>();
38 private int maxRedirects = Main.pref.getInteger("socket.maxredirects", 5);
39
40 private HttpClient(URL url, String requestMethod) {
41 this.url = url;
42 this.requestMethod = requestMethod;
43 }
44
45 public Response connect() throws IOException {
46 final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
47 connection.setRequestProperty("User-Agent", Version.getInstance().getFullAgentString());
48 connection.setConnectTimeout(connectTimeout);
49 connection.setReadTimeout(readTimeout);
50 if (accept != null) {
51 connection.setRequestProperty("Accept", accept);
52 }
53 if (contentType != null) {
54 connection.setRequestProperty("Content-Type", contentType);
55 }
56 if (acceptEncoding != null) {
57 connection.setRequestProperty("Accept-Encoding", acceptEncoding);
58 }
59 if (contentLength > 0) {
60 connection.setRequestProperty("Content-Length", String.valueOf(contentLength));
61 }
62 if ("PUT".equals(requestMethod) || "POST".equals(requestMethod) || "DELETE".equals(requestMethod)) {
63 connection.setDoOutput(true);
64 try (OutputStream out = new BufferedOutputStream(connection.getOutputStream())) {
65 out.write(requestBody);
66 }
67 }
68 if (ifModifiedSince > 0) {
69 connection.setIfModifiedSince(ifModifiedSince);
70 }
71 for (Map.Entry<String, String> header : headers.entrySet()) {
72 connection.setRequestProperty(header.getKey(), header.getValue());
73 }
74
75 boolean successfulConnection = false;
76 try {
77 try {
78 connection.connect();
79 } catch (IOException e) {
80 //noinspection ThrowableResultOfMethodCallIgnored
81 Main.addNetworkError(url, Utils.getRootCause(e));
82 throw e;
83 }
84 if (isRedirect(connection.getResponseCode())) {
85 final String redirectLocation = connection.getHeaderField("Location");
86 if (redirectLocation == null) {
87 /* I18n: argument is HTTP response code */
88 String msg = tr("Unexpected response from HTTP server. Got {0} response without ''Location'' header." +
89 " Can''t redirect. Aborting.", connection.getResponseCode());
90 throw new IOException(msg);
91 } else if (maxRedirects > 0) {
92 url = new URL(redirectLocation);
93 maxRedirects--;
94 Main.info(tr("Download redirected to ''{0}''", redirectLocation));
95 return connect();
96 } else {
97 String msg = tr("Too many redirects to the download URL detected. Aborting.");
98 throw new IOException(msg);
99 }
100 }
101 Response response = new Response(connection);
102 successfulConnection = true;
103 return response;
104 } finally {
105 if (!successfulConnection) {
106 connection.disconnect();
107 }
108 }
109 }
110
111 /**
112 * A wrapper for the HTTP response.
113 */
114 public static class Response {
115 private final HttpURLConnection connection;
116 private final int responseCode;
117
118 private Response(HttpURLConnection connection) throws IOException {
119 this.connection = connection;
120 this.responseCode = connection.getResponseCode();
121 }
122
123 /**
124 * Returns an input stream that reads from this HTTP connection, or,
125 * error stream if the connection failed but the server sent useful data.
126 *
127 * @see HttpURLConnection#getInputStream()
128 * @see HttpURLConnection#getErrorStream()
129 */
130 public InputStream getContent() throws IOException {
131 InputStream in;
132 try {
133 in = connection.getInputStream();
134 } catch (IOException ioe) {
135 in = connection.getErrorStream();
136 }
137 return "gzip".equalsIgnoreCase(getContentEncoding()) ? new GZIPInputStream(in) : in;
138 }
139
140 /**
141 * Returns {@link #getContent()} wrapped in a buffered reader
142 */
143 public BufferedReader getContentReader() throws IOException {
144 return new BufferedReader(new InputStreamReader(getContent(), StandardCharsets.UTF_8));
145 }
146
147 /**
148 * Gets the response code from this HTTP connection.
149 *
150 * @see HttpURLConnection#getResponseCode()
151 */
152 public int getResponseCode() {
153 return responseCode;
154 }
155
156 /**
157 * Returns the {@code Content-Encoding} header.
158 */
159 public String getContentEncoding() {
160 return connection.getContentEncoding();
161 }
162
163 /**
164 * Returns the {@code Content-Type} header.
165 */
166 public String getContentType() {
167 return connection.getHeaderField("Content-Type");
168 }
169
170 /**
171 * @see HttpURLConnection#disconnect()
172 */
173 public void disconnect() {
174 connection.disconnect();
175 }
176 }
177
178 /**
179 * Creates a new instance for the given URL and a {@code GET} request
180 *
181 * @param url the URL
182 * @return a new instance
183 */
184 public static HttpClient create(URL url) {
185 return create(url, "GET");
186 }
187
188 /**
189 * Creates a new instance for the given URL and a {@code GET} request
190 *
191 * @param url the URL
192 * @param requestMethod the HTTP request method to perform when calling
193 * @return a new instance
194 */
195 public static HttpClient create(URL url, String requestMethod) {
196 return new HttpClient(url, requestMethod);
197 }
198
199 /**
200 * @return {@code this}
201 * @see HttpURLConnection#setConnectTimeout(int)
202 */
203 public HttpClient setConnectTimeout(int connectTimeout) {
204 this.connectTimeout = connectTimeout;
205 return this;
206 }
207
208 /**
209 * @return {@code this}
210 * @see HttpURLConnection#setReadTimeout(int) (int)
211 */
212
213 public HttpClient setReadTimeout(int readTimeout) {
214 this.readTimeout = readTimeout;
215 return this;
216 }
217
218 /**
219 * Sets the {@code Accept} header.
220 *
221 * @return {@code this}
222 */
223 public HttpClient setAccept(String accept) {
224 this.accept = accept;
225 return this;
226 }
227
228 /**
229 * Sets the {@code Content-Type} header.
230 *
231 * @return {@code this}
232 */
233 public HttpClient setContentType(String contentType) {
234 this.contentType = contentType;
235 return this;
236 }
237
238 /**
239 * Sets the {@code Accept-Encoding} header.
240 *
241 * @return {@code this}
242 */
243 public HttpClient setAcceptEncoding(String acceptEncoding) {
244 this.acceptEncoding = acceptEncoding;
245 return this;
246 }
247
248 /**
249 * Sets the {@code Content-Length} header for {@code PUT}/{@code POST} requests.
250 *
251 * @return {@code this}
252 */
253 public HttpClient setContentLength(long contentLength) {
254 this.contentLength = contentLength;
255 return this;
256 }
257
258 /**
259 * Sets the request body for {@code PUT}/{@code POST} requests.
260 *
261 * @return {@code this}
262 */
263 public HttpClient setRequestBody(byte[] requestBody) {
264 this.requestBody = requestBody;
265 return this;
266 }
267
268 /**
269 * Sets the {@code If-Modified-Since} header.
270 *
271 * @return {@code this}
272 */
273 public HttpClient setIfModifiedSince(long ifModifiedSince) {
274 this.ifModifiedSince = ifModifiedSince;
275 return this;
276 }
277
278 /**
279 * Sets the maximum number of redirections to follow.
280 *
281 * @return {@code this}
282 */
283 public HttpClient setMaxRedirects(int maxRedirects) {
284 this.maxRedirects = maxRedirects;
285 return this;
286 }
287
288 /**
289 * Sets an arbitrary HTTP header.
290 *
291 * @return {@code this}
292 */
293 public HttpClient setHeader(String key, String value) {
294 this.headers.put(key, value);
295 return this;
296 }
297
298 /**
299 * Sets arbitrary HTTP headers.
300 *
301 * @return {@code this}
302 */
303 public HttpClient setHeaders(Map<String, String> headers) {
304 this.headers.putAll(headers);
305 return this;
306 }
307
308 private static boolean isRedirect(final int statusCode) {
309 switch (statusCode) {
310 case HttpURLConnection.HTTP_MOVED_PERM: // 301
311 case HttpURLConnection.HTTP_MOVED_TEMP: // 302
312 case HttpURLConnection.HTTP_SEE_OTHER: // 303
313 case 307: // TEMPORARY_REDIRECT:
314 case 308: // PERMANENT_REDIRECT:
315 return true;
316 default:
317 return false;
318 }
319 }
320
321}
Note: See TracBrowser for help on using the repository browser.