LEFT | RIGHT |
| 1 /* |
| 2 * Copyright (c) 2010 Google Inc. |
| 3 * |
| 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not u
se this file except |
| 5 * in compliance with the License. You may obtain a copy of the License at |
| 6 * |
| 7 * http://www.apache.org/licenses/LICENSE-2.0 |
| 8 * |
| 9 * Unless required by applicable law or agreed to in writing, software distribut
ed under the License |
| 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY K
IND, either express |
| 11 * or implied. See the License for the specific language governing permissions a
nd limitations under |
| 12 * the License. |
| 13 */ |
| 14 |
| 15 package com.google.api.client.http; |
| 16 |
| 17 import com.google.api.client.util.LoggingInputStream; |
| 18 import com.google.api.client.util.StringUtils; |
| 19 import com.google.common.base.Preconditions; |
| 20 |
| 21 import java.io.ByteArrayOutputStream; |
| 22 import java.io.IOException; |
| 23 import java.io.InputStream; |
| 24 import java.io.OutputStream; |
| 25 import java.util.logging.Level; |
| 26 import java.util.logging.Logger; |
| 27 import java.util.regex.Matcher; |
| 28 import java.util.regex.Pattern; |
| 29 import java.util.zip.GZIPInputStream; |
| 30 |
| 31 /** |
1 * HTTP response. | 32 * HTTP response. |
| 33 * |
| 34 * <p> |
| 35 * Callers should call {@link #disconnect} when the HTTP response object is no l
onger needed. |
| 36 * However, {@link #disconnect} does not have to be called if the response strea
m is properly |
| 37 * closed. Example usage: |
| 38 * </p> |
| 39 * |
| 40 * <pre> |
| 41 HttpResponse response = request.execute(); |
| 42 try { |
| 43 // process the HTTP response object |
| 44 } finally { |
| 45 response.disconnect(); |
| 46 } |
| 47 * </pre> |
2 * | 48 * |
3 * <p> | 49 * <p> |
4 * Implementation is not thread-safe. | 50 * Implementation is not thread-safe. |
5 * </p> | 51 * </p> |
6 * | 52 * |
| 53 * @since 1.0 |
| 54 * @author Yaniv Inbar |
| 55 */ |
| 56 public final class HttpResponse { |
| 57 |
| 58 /** Content-Type parameter pattern. */ |
| 59 private static final Pattern CONTENT_TYPE_PARAM_PATTERN = |
| 60 Pattern.compile(";\\s*(\\S[^=]*)=([^;]*[^;\\p{Space}])"); |
| 61 |
| 62 /** HTTP response content or {@code null} before {@link #getContent()}. */ |
| 63 private InputStream content; |
| 64 |
| 65 /** Content encoding or {@code null}. */ |
| 66 private final String contentEncoding; |
| 67 |
| 68 /** Content type or {@code null} for none. */ |
| 69 private final String contentType; |
| 70 |
| 71 /** HTTP headers. */ |
| 72 private final HttpHeaders headers; |
| 73 |
| 74 /** Low-level HTTP response. */ |
| 75 LowLevelHttpResponse response; |
| 76 |
| 77 /** Status code. */ |
| 78 private final int statusCode; |
| 79 |
| 80 /** Status message or {@code null}. */ |
| 81 private final String statusMessage; |
| 82 |
| 83 /** HTTP transport. */ |
| 84 private final HttpTransport transport; |
| 85 |
| 86 /** HTTP request. */ |
| 87 private final HttpRequest request; |
| 88 |
| 89 /** |
| 90 * Determines the limit to the content size that will be logged during {@link
#getContent()}. |
| 91 * |
| 92 * <p> |
| 93 * Content will only be logged if {@link #isLoggingEnabled} is {@code true}. |
| 94 * </p> |
| 95 * |
| 96 * <p> |
| 97 * If the content size is greater than this limit then it will not be logged. |
| 98 * </p> |
| 99 * |
| 100 * <p> |
| 101 * Can be set to {@code 0} to disable content logging. This is useful for exam
ple if content has |
| 102 * sensitive data such as authentication information. |
| 103 * </p> |
| 104 * |
| 105 * <p> |
| 106 * Defaults to {@link HttpRequest#getContentLoggingLimit()}. |
| 107 * </p> |
| 108 */ |
| 109 private int contentLoggingLimit; |
| 110 |
| 111 /** |
| 112 * Determines whether logging should be enabled on this response. |
| 113 * |
| 114 * <p> |
| 115 * Defaults to {@link HttpRequest#isLoggingEnabled()}. |
| 116 * </p> |
| 117 */ |
| 118 private boolean loggingEnabled; |
| 119 |
| 120 /** Signals whether the content has been read from the input stream. */ |
| 121 private boolean contentRead; |
| 122 |
| 123 HttpResponse(HttpRequest request, LowLevelHttpResponse response) { |
| 124 this.request = request; |
| 125 transport = request.getTransport(); |
| 126 headers = request.getResponseHeaders(); |
| 127 contentLoggingLimit = request.getContentLoggingLimit(); |
| 128 loggingEnabled = request.isLoggingEnabled(); |
| 129 this.response = response; |
| 130 contentType = response.getContentType(); |
| 131 contentEncoding = response.getContentEncoding(); |
| 132 int code = response.getStatusCode(); |
| 133 statusCode = code; |
| 134 String message = response.getReasonPhrase(); |
| 135 statusMessage = message; |
| 136 Logger logger = HttpTransport.LOGGER; |
| 137 boolean loggable = loggingEnabled && logger.isLoggable(Level.CONFIG); |
| 138 StringBuilder logbuf = null; |
| 139 if (loggable) { |
| 140 logbuf = new StringBuilder(); |
| 141 logbuf.append("-------------- RESPONSE --------------").append(StringUtils
.LINE_SEPARATOR); |
| 142 String statusLine = response.getStatusLine(); |
| 143 if (statusLine != null) { |
| 144 logbuf.append(statusLine); |
| 145 } else { |
| 146 logbuf.append(code); |
| 147 if (message != null) { |
| 148 logbuf.append(' ').append(message); |
| 149 } |
| 150 } |
| 151 logbuf.append(StringUtils.LINE_SEPARATOR); |
| 152 } |
| 153 |
| 154 // headers |
| 155 headers.fromHttpResponse(response, loggable ? logbuf : null); |
| 156 |
| 157 // log from buffer |
| 158 if (loggable) { |
| 159 logger.config(logbuf.toString()); |
| 160 } |
| 161 } |
| 162 |
| 163 /** |
| 164 * Returns the limit to the content size that will be logged during {@link #ge
tContent()}. |
| 165 * |
| 166 * <p> |
| 167 * Content will only be logged if {@link #isLoggingEnabled} is {@code true}. |
| 168 * </p> |
| 169 * |
| 170 * <p> |
| 171 * If the content size is greater than this limit then it will not be logged. |
| 172 * </p> |
| 173 * |
| 174 * <p> |
| 175 * Can be set to {@code 0} to disable content logging. This is useful for exam
ple if content has |
| 176 * sensitive data such as authentication information. |
| 177 * </p> |
| 178 * |
| 179 * <p> |
| 180 * Defaults to {@link HttpRequest#getContentLoggingLimit()}. |
| 181 * </p> |
| 182 * |
| 183 * <p> |
| 184 * Upgrade warning: prior to version 1.9, the default was {@code 100,000} byte
s, but now it is |
| 185 * {@link HttpRequest#getContentLoggingLimit()}. |
| 186 * </p> |
| 187 * |
| 188 * @since 1.7 |
| 189 */ |
| 190 public int getContentLoggingLimit() { |
| 191 return contentLoggingLimit; |
| 192 } |
| 193 |
| 194 /** |
| 195 * Set the limit to the content size that will be logged during {@link #getCon
tent()}. |
| 196 * |
| 197 * <p> |
| 198 * Content will only be logged if {@link #isLoggingEnabled} is {@code true}. |
| 199 * </p> |
| 200 * |
| 201 * <p> |
| 202 * If the content size is greater than this limit then it will not be logged. |
| 203 * </p> |
| 204 * |
| 205 * <p> |
| 206 * Can be set to {@code 0} to disable content logging. This is useful for exam
ple if content has |
| 207 * sensitive data such as authentication information. |
| 208 * </p> |
| 209 * |
| 210 * <p> |
| 211 * Defaults to {@link HttpRequest#getContentLoggingLimit()}. |
| 212 * </p> |
| 213 * |
| 214 * <p> |
| 215 * Upgrade warning: prior to version 1.9, the default was {@code 100,000} byte
s, but now it is |
| 216 * {@link HttpRequest#getContentLoggingLimit()}. |
| 217 * </p> |
| 218 * |
| 219 * @since 1.7 |
| 220 */ |
| 221 public HttpResponse setContentLoggingLimit(int contentLoggingLimit) { |
| 222 Preconditions.checkArgument( |
| 223 contentLoggingLimit >= 0, "The content logging limit must be non-negativ
e."); |
| 224 this.contentLoggingLimit = contentLoggingLimit; |
| 225 return this; |
| 226 } |
| 227 |
| 228 /** |
| 229 * Returns whether logging should be enabled on this response. |
| 230 * |
| 231 * <p> |
| 232 * Defaults to {@link HttpRequest#isLoggingEnabled()}. |
| 233 * </p> |
| 234 * |
| 235 * @since 1.9 |
| 236 */ |
| 237 public boolean isLoggingEnabled() { |
| 238 return loggingEnabled; |
| 239 } |
| 240 |
| 241 /** |
| 242 * Sets whether logging should be enabled on this response. |
| 243 * |
| 244 * <p> |
| 245 * Defaults to {@link HttpRequest#isLoggingEnabled()}. |
| 246 * </p> |
| 247 * |
| 248 * @since 1.9 |
| 249 */ |
| 250 public HttpResponse setLoggingEnabled(boolean loggingEnabled) { |
| 251 this.loggingEnabled = loggingEnabled; |
| 252 return this; |
| 253 } |
| 254 |
| 255 /** |
| 256 * Returns the content encoding or {@code null} for none. |
| 257 * |
| 258 * @since 1.5 |
| 259 */ |
| 260 public String getContentEncoding() { |
| 261 return contentEncoding; |
| 262 } |
| 263 |
| 264 /** |
| 265 * Returns the content type or {@code null} for none. |
| 266 * |
| 267 * @since 1.5 |
| 268 */ |
| 269 public String getContentType() { |
| 270 return contentType; |
| 271 } |
| 272 |
| 273 /** |
| 274 * Returns the HTTP response headers. |
| 275 * |
| 276 * @since 1.5 |
| 277 */ |
| 278 public HttpHeaders getHeaders() { |
| 279 return headers; |
| 280 } |
| 281 |
| 282 /** |
| 283 * Returns whether received a successful HTTP status code {@code >= 200 && < 3
00} (see |
| 284 * {@link #getStatusCode()}). |
| 285 * |
| 286 * @since 1.5 |
| 287 */ |
| 288 public boolean isSuccessStatusCode() { |
| 289 return HttpStatusCodes.isSuccess(statusCode); |
| 290 } |
| 291 |
| 292 /** |
| 293 * Returns the HTTP status code or {@code 0} for none. |
| 294 * |
| 295 * @since 1.5 |
| 296 */ |
| 297 public int getStatusCode() { |
| 298 return statusCode; |
| 299 } |
| 300 |
| 301 /** |
| 302 * Returns the HTTP status message or {@code null} for none. |
| 303 * |
| 304 * @since 1.5 |
| 305 */ |
| 306 public String getStatusMessage() { |
| 307 return statusMessage; |
| 308 } |
| 309 |
| 310 /** |
| 311 * Returns the HTTP transport. |
| 312 * |
| 313 * @since 1.5 |
| 314 */ |
| 315 public HttpTransport getTransport() { |
| 316 return transport; |
| 317 } |
| 318 |
| 319 /** |
| 320 * Returns the HTTP request. |
| 321 * |
| 322 * @since 1.5 |
| 323 */ |
| 324 public HttpRequest getRequest() { |
| 325 return request; |
| 326 } |
| 327 |
| 328 /** |
| 329 * Returns the content of the HTTP response. |
| 330 * <p> |
| 331 * The result is cached, so subsequent calls will be fast. |
| 332 * <p> |
| 333 * Callers should call {@link InputStream#close} after the returned {@link Inp
utStream} is no |
| 334 * longer needed. Example usage: |
| 335 * |
| 336 * <pre> |
| 337 InputStream is = response.getContent(); |
| 338 try { |
| 339 // Process the input stream.. |
| 340 } finally { |
| 341 is.close(); |
| 342 } |
| 343 * </pre> |
| 344 * <p> |
| 345 * {@link HttpResponse#disconnect} does not have to be called if the content i
s closed. |
| 346 * |
| 347 * @return input stream content of the HTTP response or {@code null} for none |
| 348 * @throws IOException I/O exception |
| 349 */ |
| 350 public InputStream getContent() throws IOException { |
| 351 if (!contentRead) { |
| 352 InputStream lowLevelResponseContent = this.response.getContent(); |
| 353 if (lowLevelResponseContent != null) { |
| 354 // Flag used to indicate if an exception is thrown before the content is
successfully |
| 355 // processed. |
| 356 boolean contentProcessed = false; |
| 357 try { |
| 358 // gzip encoding (wrap content with GZipInputStream) |
| 359 String contentEncoding = this.contentEncoding; |
| 360 if (contentEncoding != null && contentEncoding.contains("gzip")) { |
| 361 lowLevelResponseContent = new GZIPInputStream(lowLevelResponseConten
t); |
| 362 } |
| 363 // logging (wrap content with LoggingInputStream) |
| 364 Logger logger = HttpTransport.LOGGER; |
| 365 if (loggingEnabled && logger.isLoggable(Level.CONFIG)) { |
| 366 lowLevelResponseContent = new LoggingInputStream( |
| 367 lowLevelResponseContent, logger, Level.CONFIG, contentLoggingLim
it); |
| 368 } |
| 369 content = lowLevelResponseContent; |
| 370 contentProcessed = true; |
| 371 } finally { |
| 372 if (!contentProcessed) { |
| 373 lowLevelResponseContent.close(); |
| 374 } |
| 375 } |
| 376 } |
| 377 contentRead = true; |
| 378 } |
| 379 return content; |
| 380 } |
| 381 |
| 382 /** |
| 383 * Writes the content of the HTTP response into the given destination output s
tream. |
| 384 * |
| 385 * <p> |
| 386 * Sample usage: <code> |
| 387 HttpRequest request = |
| 388 requestFactory.buildGetRequest(new GenericUrl( |
| 389 "https://www.google.com/images/srpr/logo3w.png")); |
| 390 OutputStream outputStream = |
| 391 new FileOutputStream(new File ("/tmp/logo3w.png")); |
| 392 try { |
| 393 HttpResponse response = request.execute(); |
| 394 response.download(outputStream); |
| 395 } finally { |
| 396 outputStream.close(); |
| 397 } |
| 398 * </code> |
| 399 * </p> |
| 400 * |
| 401 * <p> |
| 402 * This method closes the content of the HTTP response from {@link #getContent
()}. |
| 403 * </p> |
| 404 * |
| 405 * <p> |
| 406 * This method does not close the given output stream. |
| 407 * </p> |
| 408 * |
| 409 * @param outputStream destination output stream |
| 410 * @throws IOException I/O exception |
| 411 * @since 1.9 |
| 412 */ |
| 413 public void download(OutputStream outputStream) throws IOException { |
| 414 InputStream inputStream = getContent(); |
| 415 AbstractInputStreamContent.copy(inputStream, outputStream); |
| 416 } |
| 417 |
| 418 /** |
| 419 * Closes the content of the HTTP response from {@link #getContent()}, ignorin
g any content. |
| 420 */ |
| 421 public void ignore() throws IOException { |
| 422 InputStream content = getContent(); |
| 423 if (content != null) { |
| 424 content.close(); |
| 425 } |
| 426 } |
| 427 |
| 428 /** |
| 429 * Close the HTTP response content and disconnect using {@link LowLevelHttpRes
ponse#disconnect()}. |
| 430 * |
| 431 * <p> |
| 432 * Upgrade warning: since version 1.10 {@link #disconnect} now closes the HTTP
response content |
| 433 * input stream. This was not done by this method prior to version 1.10. |
| 434 * </p> |
| 435 * |
| 436 * @since 1.4 |
| 437 */ |
| 438 public void disconnect() throws IOException { |
| 439 ignore(); |
| 440 response.disconnect(); |
| 441 } |
| 442 |
| 443 /** |
| 444 * Returns the HTTP response content parser to use for the content type of thi
s HTTP response or |
| 445 * {@code null} for none. |
| 446 */ |
| 447 public HttpParser getParser() { |
| 448 return request.getParser(contentType); |
| 449 } |
| 450 |
| 451 /** |
| 452 * Parses the content of the HTTP response from {@link #getContent()} and read
s it into a data |
| 453 * class of key/value pairs using the parser returned by {@link #getParser()}
. |
| 454 * |
| 455 * @return parsed data class or {@code null} for no content |
| 456 * @throws IOException I/O exception |
| 457 * @throws IllegalArgumentException if no parser is defined for the given cont
ent type or if there |
| 458 * is no content type defined in the HTTP response |
| 459 */ |
| 460 public <T> T parseAs(Class<T> dataClass) throws IOException { |
| 461 HttpParser parser = getParser(); |
| 462 if (parser == null) { |
| 463 Preconditions.checkArgument(contentType != null, "Missing Content-Type hea
der in response"); |
| 464 throw new IllegalArgumentException("No parser defined for Content-Type: "
+ contentType); |
| 465 } |
| 466 return parser.parse(this, dataClass); |
| 467 } |
| 468 |
| 469 /** |
| 470 * Parses the content of the HTTP response from {@link #getContent()} and read
s it into a string. |
| 471 * |
| 472 * <p> |
| 473 * Since this method returns {@code ""} for no content, a simpler check for no
content is to check |
| 474 * if {@link #getContent()} is {@code null}. |
| 475 * </p> |
| 476 * |
| 477 * <p> |
| 478 * Warning: in prior version 1.9 the maximum amount of content parsed for un-G
Zipped content was |
| 479 * set by the Content-Length header, but now instead all content is read. Also
, prior version |
| 480 * assumed the charset was {@code "UTF-8"}, but now it follows the specificati
on by parsing the |
| 481 * "charset" parameter of the Content-Type header or {@code "ISO-8859-1"} if m
issing. |
| 482 * </p> |
| 483 * |
| 484 * @return parsed string or {@code ""} for no content |
| 485 * @throws IOException I/O exception |
| 486 */ |
| 487 public String parseAsString() throws IOException { |
| 488 InputStream content = getContent(); |
| 489 if (content == null) { |
| 490 return ""; |
| 491 } |
| 492 ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| 493 AbstractInputStreamContent.copy(content, out); |
| 494 return out.toString(parseCharset(getContentType())); |
| 495 } |
| 496 |
| 497 /** Parses the "charset" parameter from the Content-Type header. */ |
| 498 static String parseCharset(String contentType) { |
| 499 if (contentType != null) { |
| 500 Matcher m = CONTENT_TYPE_PARAM_PATTERN.matcher(contentType); |
| 501 while (m.find()) { |
| 502 if ("charset".equalsIgnoreCase(m.group(1))) { |
| 503 return m.group(2); |
| 504 } |
| 505 } |
| 506 } |
| 507 return "ISO-8859-1"; |
| 508 } |
| 509 } |
LEFT | RIGHT |