001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.rest.client; 018 019import static org.apache.juneau.httppart.HttpPartType.*; 020 021import java.lang.reflect.*; 022import java.text.*; 023import java.util.*; 024import java.util.logging.*; 025 026import org.apache.http.*; 027import org.apache.http.message.*; 028import org.apache.http.params.*; 029import org.apache.http.util.*; 030import org.apache.juneau.*; 031import org.apache.juneau.assertions.*; 032import org.apache.juneau.common.utils.*; 033import org.apache.juneau.http.header.*; 034import org.apache.juneau.httppart.*; 035import org.apache.juneau.httppart.bean.*; 036import org.apache.juneau.internal.*; 037import org.apache.juneau.parser.*; 038import org.apache.juneau.rest.client.assertion.*; 039 040/** 041 * Represents a response from a remote REST resource. 042 * 043 * <p> 044 * Instances of this class are created by calling the {@link RestRequest#run()} method. 045 * 046 * <h5 class='section'>Example:</h5> 047 * <p class='bjava'> 048 * <jc>// Create a request and response, automatically closing both.</jc> 049 * <jk>try</jk> ( 050 * <jv>RestRequest</jv> <jv>req</jv> = <jv>client</jv>.get(<js>"/myResource"</js>); 051 * <jv>RestResponse</jv> <jv>res</jv> = <jv>req</jv>.run() 052 * ) { 053 * String <jv>body</jv> = <jv>res</jv>.getContent().asString(); 054 * } 055 * </p> 056 * 057 * <p> 058 * Alternatively, you can rely on {@link RestRequest#close()} to automatically close the response: 059 * 060 * <p class='bjava'> 061 * <jc>// Only specify RestRequest - it will close the response automatically.</jc> 062 * <jk>try</jk> (<jv>RestRequest</jv> <jv>req</jv> = <jv>client</jv>.get(<js>"/myResource"</js>)) { 063 * String <jv>body</jv> = <jv>req</jv>.run().getContent().asString(); 064 * } 065 * </p> 066 * 067 * <h5 class='section'>Notes:</h5><ul> 068 * <li class='note'>This class implements {@link AutoCloseable} and can be used in try-with-resources blocks. 069 * The {@link #close()} method allows unchecked exceptions to propagate for debuggability, 070 * while catching and logging checked exceptions to follow AutoCloseable best practices. 071 * </ul> 072 * 073 * <h5 class='section'>See Also:</h5><ul> 074 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestClientBasics">juneau-rest-client Basics</a> 075 * </ul> 076 */ 077public class RestResponse implements HttpResponse, AutoCloseable { 078 079 private final RestClient client; 080 private final RestRequest request; 081 private final HttpResponse response; 082 private final Parser parser; 083 private ResponseContent responseContent; 084 private boolean isClosed; 085 private HeaderList headers; 086 087 private Map<HttpPartParser,HttpPartParserSession> partParserSessions = new IdentityHashMap<>(); 088 private HttpPartParserSession partParserSession; 089 090 /** 091 * Constructor. 092 * 093 * @param client The RestClient that created this response. 094 * @param request The REST request. 095 * @param response The HTTP response. Can be <jk>null</jk>. 096 * @param parser The overridden parser passed into {@link RestRequest#parser(Parser)}. 097 */ 098 protected RestResponse(RestClient client, RestRequest request, HttpResponse response, Parser parser) { 099 this.client = client; 100 this.request = request; 101 this.parser = parser; 102 this.response = response == null ? new BasicHttpResponse(null, 0, null) : response; 103 this.responseContent = new ResponseContent(client, request, this, parser); 104 this.headers = HeaderList.of(this.response.getAllHeaders()); 105 } 106 107 /** 108 * Returns the request object that created this response object. 109 * 110 * @return The request object that created this response object. 111 */ 112 public RestRequest getRequest() { 113 return request; 114 } 115 116 //------------------------------------------------------------------------------------------------------------------ 117 // Setters 118 //------------------------------------------------------------------------------------------------------------------ 119 120 /** 121 * Consumes the response body. 122 * 123 * <p> 124 * This is equivalent to closing the input stream. 125 * 126 * <p> 127 * Any exceptions thrown during close are logged but not propagated. 128 * 129 * @return This object. 130 */ 131 public RestResponse consume() { 132 close(); 133 return this; 134 } 135 136 //------------------------------------------------------------------------------------------------------------------ 137 // Status line 138 //------------------------------------------------------------------------------------------------------------------ 139 140 /** 141 * Returns the status code of the response. 142 * 143 * Shortcut for calling <code>getStatusLine().getStatusCode()</code>. 144 * 145 * @return The status code of the response. 146 */ 147 public int getStatusCode() { 148 return getStatusLine().getStatusCode(); 149 } 150 151 /** 152 * Returns the status line reason phrase of the response. 153 * 154 * Shortcut for calling <code>getStatusLine().getReasonPhrase()</code>. 155 * 156 * @return The status line reason phrase of the response. 157 */ 158 public String getReasonPhrase() { 159 return getStatusLine().getReasonPhrase(); 160 } 161 162 //------------------------------------------------------------------------------------------------------------------ 163 // Status line assertions 164 //------------------------------------------------------------------------------------------------------------------ 165 166 /** 167 * Provides the ability to perform fluent-style assertions on the response {@link StatusLine} object. 168 * 169 * <h5 class='section'>Examples:</h5> 170 * <p class='bjava'> 171 * MyBean <jv>bean</jv> = <jv>client</jv> 172 * .get(<jsf>URI</jsf>) 173 * .run() 174 * .assertStatus().asCode().is(200) 175 * .getContent().as(MyBean.<jk>class</jk>); 176 * </p> 177 * 178 * @return A new fluent assertion object. 179 */ 180 public FluentResponseStatusLineAssertion<RestResponse> assertStatus() { 181 return new FluentResponseStatusLineAssertion<>(getStatusLine(), this); 182 } 183 184 /** 185 * Provides the ability to perform fluent-style assertions on the response status code. 186 * 187 * <h5 class='section'>Examples:</h5> 188 * <p class='bjava'> 189 * MyBean <jv>bean</jv> = <jv>client</jv> 190 * .get(<jsf>URI</jsf>) 191 * .run() 192 * .assertStatus(200) 193 * .getContent().as(MyBean.<jk>class</jk>); 194 * </p> 195 * 196 * @param value The value to assert. 197 * @return A new fluent assertion object. 198 */ 199 public RestResponse assertStatus(int value) { 200 assertStatus().asCode().is(value); 201 return this; 202 } 203 204 //------------------------------------------------------------------------------------------------------------------ 205 // Headers 206 //------------------------------------------------------------------------------------------------------------------ 207 208 /** 209 * Shortcut for calling <code>getHeader(name).asString()</code>. 210 * 211 * @param name The header name. 212 * @return The header value, never <jk>null</jk> 213 */ 214 public Optional<String> getStringHeader(String name) { 215 return getHeader(name).asString(); 216 } 217 218 /** 219 * Shortcut for retrieving the response charset from the <l>Content-Type</l> header. 220 * 221 * @return The response charset. 222 * @throws RestCallException If REST call failed. 223 */ 224 public String getCharacterEncoding() throws RestCallException { 225 Optional<ContentType> ct = getContentType(); 226 String s = null; 227 if (ct.isPresent()) 228 s = getContentType().get().getParameter("charset"); 229 return Utils.isEmpty(s) ? "utf-8" : s; 230 } 231 232 /** 233 * Shortcut for retrieving the response content type from the <l>Content-Type</l> header. 234 * 235 * <p> 236 * This is equivalent to calling <c>getHeader(<js>"Content-Type"</js>).as(ContentType.<jk>class</jk>)</c>. 237 * 238 * @return The response charset. 239 * @throws RestCallException If REST call failed. 240 */ 241 public Optional<ContentType> getContentType() throws RestCallException { 242 return getHeader("Content-Type").as(ContentType.class); 243 } 244 245 /** 246 * Provides the ability to perform fluent-style assertions on the response character encoding. 247 * 248 * <h5 class='section'>Examples:</h5> 249 * <p class='bjava'> 250 * <jc>// Validates that the response content charset is UTF-8.</jc> 251 * <jv>client</jv> 252 * .get(<jsf>URI</jsf>) 253 * .run() 254 * .assertCharset().is(<js>"utf-8"</js>); 255 * </p> 256 * 257 * @return A new fluent assertion object. 258 * @throws RestCallException If REST call failed. 259 */ 260 public FluentStringAssertion<RestResponse> assertCharset() throws RestCallException { 261 return new FluentStringAssertion<>(getCharacterEncoding(), this); 262 } 263 264 /** 265 * Provides the ability to perform fluent-style assertions on a response header. 266 * 267 * <h5 class='section'>Examples:</h5> 268 * <p class='bjava'> 269 * <jc>// Validates the content type header is provided.</jc> 270 * <jv>client</jv> 271 * .get(<jsf>URI</jsf>) 272 * .run() 273 * .assertHeader(<js>"Content-Type"</js>).exists(); 274 * 275 * <jc>// Validates the content type is JSON.</jc> 276 * <jv>client</jv> 277 * .get(<jsf>URI</jsf>) 278 * .run() 279 * .assertHeader(<js>"Content-Type"</js>).is(<js>"application/json"</js>); 280 * 281 * <jc>// Validates the content type is JSON using test predicate.</jc> 282 * <jv>client</jv> 283 * .get(<jsf>URI</jsf>) 284 * .run() 285 * .assertHeader(<js>"Content-Type"</js>).is(<jv>x</jv> -> <jv>x</jv>.equals(<js>"application/json"</js>)); 286 * 287 * <jc>// Validates the content type is JSON by just checking for substring.</jc> 288 * <jv>client</jv> 289 * .get(<jsf>URI</jsf>) 290 * .run() 291 * .assertHeader(<js>"Content-Type"</js>).contains(<js>"json"</js>); 292 * 293 * <jc>// Validates the content type is JSON using regular expression.</jc> 294 * <jv>client</jv> 295 * .get(<jsf>URI</jsf>) 296 * .run() 297 * .assertHeader(<js>"Content-Type"</js>).isPattern(<js>".*json.*"</js>); 298 * 299 * <jc>// Validates the content type is JSON using case-insensitive regular expression.</jc> 300 * <jv>client</jv> 301 * .get(<jsf>URI</jsf>) 302 * .run() 303 * .assertHeader(<js>"Content-Type"</js>).isPattern(<js>".*json.*"</js>, <jsf>CASE_INSENSITIVE</jsf>); 304 * </p> 305 * 306 * <p> 307 * The assertion test returns the original response object allowing you to chain multiple requests like so: 308 * <p class='bjava'> 309 * <jc>// Validates the header and converts it to a bean.</jc> 310 * MediaType <jv>mediaType</jv> = <jv>client</jv> 311 * .get(<jsf>URI</jsf>) 312 * .run() 313 * .assertHeader(<js>"Content-Type"</js>).isNotEmpty() 314 * .assertHeader(<js>"Content-Type"</js>).isPattern(<js>".*json.*"</js>) 315 * .getHeader(<js>"Content-Type"</js>).as(MediaType.<jk>class</jk>); 316 * </p> 317 * 318 * @param name The header name. 319 * @return A new fluent assertion object. 320 */ 321 public FluentResponseHeaderAssertion<RestResponse> assertHeader(String name) { 322 return new FluentResponseHeaderAssertion<>(getHeader(name), this); 323 } 324 325 //------------------------------------------------------------------------------------------------------------------ 326 // Body 327 //------------------------------------------------------------------------------------------------------------------ 328 329 /** 330 * Returns the body of the response. 331 * 332 * This method can be called multiple times returning the same response body each time. 333 * 334 * @return The body of the response. 335 */ 336 public ResponseContent getContent() { 337 return responseContent; 338 } 339 340 /** 341 * Provides the ability to perform fluent-style assertions on this response body. 342 * 343 * <h5 class='section'>Examples:</h5> 344 * <p class='bjava'> 345 * <jc>// Validates the response body equals the text "OK".</jc> 346 * <jv>client</jv> 347 * .get(<jsf>URI</jsf>) 348 * .run() 349 * .assertContent().is(<js>"OK"</js>); 350 * 351 * <jc>// Validates the response body contains the text "OK".</jc> 352 * <jv>client</jv> 353 * .get(<jsf>URI</jsf>) 354 * .run() 355 * .assertContent().isContains(<js>"OK"</js>); 356 * 357 * <jc>// Validates the response body passes a predicate test.</jc> 358 * <jv>client</jv> 359 * .get(<jsf>URI</jsf>) 360 * .run() 361 * .assertContent().is(<jv>x</jv> -> <jv>x</jv>.contains(<js>"OK"</js>)); 362 * 363 * <jc>// Validates the response body matches a regular expression.</jc> 364 * <jv>client</jv> 365 * .get(<jsf>URI</jsf>) 366 * .run() 367 * .assertContent().isPattern(<js>".*OK.*"</js>); 368 * 369 * <jc>// Validates the response body matches a regular expression using regex flags.</jc> 370 * <jv>client</jv> 371 * .get(<jsf>URI</jsf>) 372 * .run() 373 * .assertContent().isPattern(<js>".*OK.*"</js>, <jsf>MULTILINE</jsf> & <jsf>CASE_INSENSITIVE</jsf>); 374 * 375 * <jc>// Validates the response body matches a regular expression in the form of an existing Pattern.</jc> 376 * Pattern <jv>pattern</jv> = Pattern.<jsm>compile</jsm>(<js>".*OK.*"</js>); 377 * <jv>client</jv> 378 * .get(<jsf>URI</jsf>) 379 * .run() 380 * .assertContent().isPattern(<jv>pattern</jv>); 381 * </p> 382 * 383 * <p> 384 * The assertion test returns the original response object allowing you to chain multiple requests like so: 385 * <p class='bjava'> 386 * <jc>// Validates the response body matches a regular expression.</jc> 387 * MyBean <jv>bean</jv> = <jv>client</jv> 388 * .get(<jsf>URI</jsf>) 389 * .run() 390 * .assertContent().isPattern(<js>".*OK.*"</js>); 391 * .assertContent().isNotPattern(<js>".*ERROR.*"</js>) 392 * .getContent().as(MyBean.<jk>class</jk>); 393 * </p> 394 * 395 * <h5 class='section'>Notes:</h5><ul> 396 * <li class='note'> 397 * If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed. 398 * <li class='note'> 399 * When using this method, the body is automatically cached by calling the {@link ResponseContent#cache()}. 400 * <li class='note'> 401 * The input stream is automatically closed after this call. 402 * </ul> 403 * 404 * @return A new fluent assertion object. 405 */ 406 public FluentResponseBodyAssertion<RestResponse> assertContent() { 407 return new FluentResponseBodyAssertion<>(responseContent, this); 408 } 409 410 /** 411 * Provides the ability to perform fluent-style assertions on this response body. 412 * 413 * <p> 414 * A shortcut for calling <c>assertContent().is(<jv>value</jv>)</c>. 415 * 416 * <h5 class='section'>Examples:</h5> 417 * <p class='bjava'> 418 * <jc>// Validates the response body equals the text "OK".</jc> 419 * <jv>client</jv> 420 * .get(<jsf>URI</jsf>) 421 * .run() 422 * .assertContent(<js>"OK"</js>); 423 * </p> 424 * 425 * @param value The value to assert. 426 * @return This object. 427 */ 428 public RestResponse assertContent(String value) { 429 assertContent().is(value); 430 return this; 431 } 432 433 /** 434 * Provides the ability to perform fluent-style assertions on this response body. 435 * 436 * <p> 437 * A shortcut for calling <c>assertContent().asString().isMatches(<jv>value</jv>)</c>. 438 * 439 * @see FluentStringAssertion#isMatches(String) 440 * @param value The value to assert. 441 * @return This object. 442 */ 443 public RestResponse assertContentMatches(String value) { 444 assertContent().asString().isMatches(value); 445 return this; 446 } 447 448 /** 449 * Caches the response body so that it can be read as a stream multiple times. 450 * 451 * This is equivalent to calling the following: 452 * <p class='bjava'> 453 * getContent().cache(); 454 * </p> 455 * 456 * @return The body of the response. 457 */ 458 public RestResponse cacheContent() { 459 responseContent.cache(); 460 return this; 461 } 462 463 @SuppressWarnings("unchecked") 464 <T> T as(ResponseBeanMeta rbm) { 465 Class<T> c = (Class<T>)rbm.getClassMeta().getInnerClass(); 466 final RestClient rc = this.client; 467 return (T)Proxy.newProxyInstance( 468 c.getClassLoader(), 469 new Class[] { c }, 470 (InvocationHandler) (proxy, method, args) -> { 471 ResponseBeanPropertyMeta pm = rbm.getProperty(method.getName()); 472 HttpPartParserSession pp = getPartParserSession(pm.getParser().orElse(rc.getPartParser())); 473 HttpPartSchema schema = pm.getSchema(); 474 HttpPartType pt = pm.getPartType(); 475 String name = pm.getPartName().orElse(null); 476 ClassMeta<?> type = rc.getBeanContext().getClassMeta(method.getGenericReturnType()); 477 if (pt == RESPONSE_HEADER) 478 return getHeader(name).parser(pp).schema(schema).as(type).orElse(null); 479 if (pt == RESPONSE_STATUS) 480 return getStatusCode(); 481 return getContent().schema(schema).as(type); 482 }); 483 } 484 485 /** 486 * Logs a message. 487 * 488 * @param level The log level. 489 * @param t The throwable cause. 490 * @param msg The message with {@link MessageFormat}-style arguments. 491 * @param args The arguments. 492 * @return This object. 493 */ 494 public RestResponse log(Level level, Throwable t, String msg, Object...args) { 495 client.log(level, t, msg, args); 496 return this; 497 } 498 499 /** 500 * Logs a message. 501 * 502 * @param level The log level. 503 * @param msg The message with {@link MessageFormat}-style arguments. 504 * @param args The arguments. 505 * @return This object. 506 */ 507 public RestResponse log(Level level, String msg, Object...args) { 508 client.log(level, msg, args); 509 return this; 510 } 511 512 // ----------------------------------------------------------------------------------------------------------------- 513 // HttpResponse pass-through methods. 514 // ----------------------------------------------------------------------------------------------------------------- 515 516 /** 517 * Obtains the status line of this response. 518 * 519 * The status line can be set using one of the setStatusLine methods, or it can be initialized in a constructor. 520 * 521 * @return The status line, or <jk>null</jk> if not yet set. 522 */ 523 @Override /* HttpResponse */ 524 public ResponseStatusLine getStatusLine() { 525 return new ResponseStatusLine(this, response.getStatusLine()); 526 } 527 528 /** 529 * Sets the status line of this response. 530 * 531 * @param statusline The status line of this response 532 */ 533 @Override /* HttpResponse */ 534 public void setStatusLine(StatusLine statusline) { 535 response.setStatusLine(statusline); 536 } 537 538 /** 539 * Sets the status line of this response. 540 * 541 * <p> 542 * The reason phrase will be determined based on the current locale. 543 * 544 * @param ver The HTTP version. 545 * @param code The status code. 546 */ 547 @Override /* HttpResponse */ 548 public void setStatusLine(ProtocolVersion ver, int code) { 549 response.setStatusLine(ver, code); 550 } 551 552 /** 553 * Sets the status line of this response with a reason phrase. 554 * 555 * @param ver The HTTP version. 556 * @param code The status code. 557 * @param reason The reason phrase, or <jk>null</jk> to omit. 558 */ 559 @Override /* HttpResponse */ 560 public void setStatusLine(ProtocolVersion ver, int code, String reason) { 561 response.setStatusLine(ver, code, reason); 562 } 563 564 /** 565 * Updates the status line of this response with a new status code. 566 * 567 * @param code The HTTP status code. 568 * @throws IllegalStateException If the status line has not be set. 569 */ 570 @Override /* HttpResponse */ 571 public void setStatusCode(int code) { 572 response.setStatusCode(code); 573 } 574 575 /** 576 * Updates the status line of this response with a new reason phrase. 577 * 578 * @param reason The new reason phrase as a single-line string, or <jk>null</jk> to unset the reason phrase. 579 * @throws IllegalStateException If the status line has not be set. 580 */ 581 @Override /* HttpResponse */ 582 public void setReasonPhrase(String reason) { 583 response.setReasonPhrase(reason); 584 } 585 586 /** 587 * Obtains the message entity of this response. 588 * 589 * <p> 590 * The entity is provided by calling setEntity. 591 * 592 * <h5 class='section'>Notes:</h5><ul> 593 * <li class='note'>Unlike the {@link HttpResponse#getEntity()} method, this method never returns a <jk>null</jk> response. 594 * Instead, <c>getContent().isPresent()</c> can be used to determine whether the response has a body. 595 * </ul> 596 * 597 * @return The response entity. Never <jk>null</jk>. 598 */ 599 @Override /* HttpResponse */ 600 public ResponseContent getEntity() { 601 return responseContent; 602 } 603 604 /** 605 * Associates a response entity with this response. 606 * 607 * <h5 class='section'>Notes:</h5><ul> 608 * <li class='note'>If an entity has already been set for this response and it depends on an input stream 609 * ({@link HttpEntity#isStreaming()} returns <jk>true</jk>), it must be fully consumed in order to ensure 610 * release of resources. 611 * </ul> 612 * 613 * @param entity The entity to associate with this response, or <jk>null</jk> to unset. 614 */ 615 @Override /* HttpResponse */ 616 public void setEntity(HttpEntity entity) { 617 response.setEntity(entity); 618 this.responseContent = new ResponseContent(client, request, this, parser); 619 } 620 621 /** 622 * Obtains the locale of this response. 623 * 624 * The locale is used to determine the reason phrase for the status code. 625 * It can be changed using {@link #setLocale(Locale)}. 626 * 627 * @return The locale of this response, never <jk>null</jk>. 628 */ 629 @Override /* HttpResponse */ 630 public Locale getLocale() { 631 return response.getLocale(); 632 } 633 634 /** 635 * Changes the locale of this response. 636 * 637 * @param loc The new locale. 638 */ 639 @Override /* HttpResponse */ 640 public void setLocale(Locale loc) { 641 response.setLocale(loc); 642 } 643 644 /** 645 * Returns the protocol version this message is compatible with. 646 * 647 * @return The protocol version this message is compatible with. 648 */ 649 @Override /* HttpMessage */ 650 public ProtocolVersion getProtocolVersion() { 651 return response.getProtocolVersion(); 652 } 653 654 /** 655 * Checks if a certain header is present in this message. 656 * 657 * <p> 658 * Header values are ignored. 659 * 660 * @param name The header name to check for. 661 * @return <jk>true</jk> if at least one header with this name is present. 662 */ 663 @Override /* HttpMessage */ 664 public boolean containsHeader(String name) { 665 return response.containsHeader(name); 666 } 667 668 /** 669 * Returns all the headers with a specified name of this message. 670 * 671 * Header values are ignored. 672 * <br>Headers are ordered in the sequence they were sent over a connection. 673 * 674 * @param name The name of the headers to return. 675 * @return All the headers with a specified name of this message. 676 */ 677 @Override /* HttpMessage */ 678 public ResponseHeader[] getHeaders(String name) { 679 return headers.stream(name).map(x -> new ResponseHeader(name, request, this, x).parser(getPartParserSession())).toArray(ResponseHeader[]::new); 680 } 681 682 /** 683 * Returns the first header with a specified name of this message. 684 * 685 * <p> 686 * If there is more than one matching header in the message the first element of {@link #getHeaders(String)} is returned. 687 * <p> 688 * This method always returns a value so that you can perform assertions on the result. 689 * 690 * @param name The name of the header to return. 691 * @return The header, never <jk>null</jk>. 692 */ 693 @Override /* HttpMessage */ 694 public ResponseHeader getFirstHeader(String name) { 695 return new ResponseHeader(name, request, this, headers.getFirst(name).orElse(null)).parser(getPartParserSession()); 696 } 697 698 /** 699 * Returns the last header with a specified name of this message. 700 * 701 * <p> 702 * If there is more than one matching header in the message the last element of {@link #getHeaders(String)} is returned. 703 * <p> 704 * This method always returns a value so that you can perform assertions on the result. 705 * 706 * @param name The name of the header to return. 707 * @return The header, never <jk>null</jk>. 708 */ 709 @Override /* HttpMessage */ 710 public ResponseHeader getLastHeader(String name) { 711 return new ResponseHeader(name, request, this, headers.getLast(name).orElse(null)).parser(getPartParserSession()); 712 } 713 714 /** 715 * Returns the response header with the specified name. 716 * 717 * <p> 718 * If more that one header with the given name exists the values will be combined with <js>", "</js> as per <a href='https://tools.ietf.org/html/rfc2616#section-4.2'>RFC 2616 Section 4.2</a>. 719 * 720 * @param name The name of the header to return. 721 * @return The header, never <jk>null</jk>. 722 */ 723 public ResponseHeader getHeader(String name) { 724 return new ResponseHeader(name, request, this, headers.get(name).orElse(null)).parser(getPartParserSession()); 725 } 726 727 /** 728 * Returns all the headers of this message. 729 * 730 * Headers are ordered in the sequence they were sent over a connection. 731 * 732 * @return All the headers of this message. 733 */ 734 @Override /* HttpMessage */ 735 public ResponseHeader[] getAllHeaders() { 736 return headers.stream().map(x -> new ResponseHeader(x.getName(), request, this, x).parser(getPartParserSession())).toArray(ResponseHeader[]::new); 737 } 738 739 /** 740 * Adds a header to this message. 741 * 742 * The header will be appended to the end of the list. 743 * 744 * @param header The header to append. 745 */ 746 @Override /* HttpMessage */ 747 public void addHeader(Header header) { 748 headers.append(header); 749 } 750 751 /** 752 * Adds a header to this message. 753 * 754 * The header will be appended to the end of the list. 755 * 756 * @param name The name of the header. 757 * @param value The value of the header. 758 */ 759 @Override /* HttpMessage */ 760 public void addHeader(String name, String value) { 761 headers.append(name, value); 762 } 763 764 /** 765 * Overwrites the first header with the same name. 766 * 767 * The new header will be appended to the end of the list, if no header with the given name can be found. 768 * 769 * @param header The header to set. 770 */ 771 @Override /* HttpMessage */ 772 public void setHeader(Header header) { 773 headers.set(header); 774 } 775 776 /** 777 * Overwrites the first header with the same name. 778 * 779 * The new header will be appended to the end of the list, if no header with the given name can be found. 780 * 781 * @param name The name of the header. 782 * @param value The value of the header. 783 */ 784 @Override /* HttpMessage */ 785 public void setHeader(String name, String value) { 786 headers.set(name, value); 787 } 788 789 /** 790 * Overwrites all the headers in the message. 791 * 792 * @param headers The array of headers to set. 793 */ 794 @Override /* HttpMessage */ 795 public void setHeaders(Header[] headers) { 796 this.headers = HeaderList.of(headers); 797 } 798 799 /** 800 * Removes a header from this message. 801 * 802 * @param header The header to remove. 803 */ 804 @Override /* HttpMessage */ 805 public void removeHeader(Header header) { 806 headers.remove(header); 807 } 808 809 /** 810 * Removes all headers with a certain name from this message. 811 * 812 * @param name The name of the headers to remove. 813 */ 814 @Override /* HttpMessage */ 815 public void removeHeaders(String name) { 816 headers.remove(name); 817 } 818 819 /** 820 * Returns an iterator of all the headers. 821 * 822 * @return {@link Iterator} that returns {@link Header} objects in the sequence they are sent over a connection. 823 */ 824 @Override /* HttpMessage */ 825 public HeaderIterator headerIterator() { 826 return headers.headerIterator(); 827 } 828 829 /** 830 * Returns an iterator of the headers with a given name. 831 * 832 * @param name The name of the headers over which to iterate, or <jk>null</jk> for all headers. 833 * @return {@link Iterator} that returns {@link Header} objects with the argument name in the sequence they are sent over a connection. 834 */ 835 @Override /* HttpMessage */ 836 public HeaderIterator headerIterator(String name) { 837 return headers.headerIterator(name); 838 } 839 840 /** 841 * Returns the parameters effective for this message as set by {@link #setParams(HttpParams)}. 842 * 843 * @return The parameters effective for this message as set by {@link #setParams(HttpParams)}. 844 * @deprecated Use configuration classes provided <jk>org.apache.http.config</jk> and <jk>org.apache.http.client.config</jk>. 845 */ 846 @Override /* HttpMessage */ 847 @Deprecated 848 public HttpParams getParams() { 849 return response.getParams(); 850 } 851 852 /** 853 * Provides parameters to be used for the processing of this message. 854 * 855 * @param params The parameters. 856 * @deprecated Use configuration classes provided <jk>org.apache.http.config</jk> and <jk>org.apache.http.client.config</jk>. 857 */ 858 @Override /* HttpMessage */ 859 @Deprecated 860 public void setParams(HttpParams params) { 861 response.setParams(params); 862 } 863 864 /** 865 * Closes this response. 866 * 867 * <p> 868 * This method is idempotent and can be called multiple times without side effects. 869 * 870 * <h5 class='section'>Implementation Notes:</h5> 871 * <p> 872 * This implementation represents a compromise between strict AutoCloseable compliance and debuggability: 873 * <ul> 874 * <li>Unchecked exceptions ({@link RuntimeException} and {@link Error}) from interceptors are allowed to propagate. 875 * This ensures programming errors and serious issues are visible during development and testing. 876 * <li>Checked exceptions (including {@link RestCallException}) are caught and logged but not thrown. 877 * This follows AutoCloseable best practices and prevents close exceptions from interfering with 878 * try-with-resources cleanup or masking the original exception. 879 * </ul> 880 */ 881 @Override /* AutoCloseable */ 882 public void close() { 883 if (isClosed) 884 return; 885 isClosed = true; 886 887 try { 888 EntityUtils.consumeQuietly(response.getEntity()); 889 890 if (!request.isLoggingSuppressed() && (request.isDebug() || client.logRequestsPredicate.test(request, this))) { 891 if (client.logRequests == DetailLevel.SIMPLE) { 892 client.log(client.logRequestsLevel, "HTTP {0} {1}, {2}", request.getMethod(), request.getURI(), this.getStatusLine()); 893 } else if (request.isDebug() || client.logRequests == DetailLevel.FULL) { 894 String output = getContent().asString(); 895 StringBuilder sb = new StringBuilder(); 896 sb.append("\n=== HTTP Call (outgoing) ======================================================"); 897 sb.append("\n=== REQUEST ===\n"); 898 sb.append(request.getMethod()).append(" ").append(request.getURI()); 899 sb.append("\n---request headers---"); 900 request.getHeaders().forEach(x -> sb.append("\n\t").append(x)); 901 if (request.hasHttpEntity()) { 902 sb.append("\n---request entity---"); 903 HttpEntity e = request.getHttpEntity(); 904 if (e.getContentType() != null) 905 sb.append("\n\t").append(e.getContentType()); 906 if (e.isRepeatable()) { 907 try { 908 sb.append("\n---request content---\n").append(EntityUtils.toString(e)); 909 } catch (Exception ex) { 910 sb.append("\n---request content exception---\n").append(ex.getMessage()); 911 } 912 } 913 } 914 sb.append("\n=== RESPONSE ===\n").append(getStatusLine()); 915 sb.append("\n---response headers---"); 916 for (Header h : getAllHeaders()) 917 sb.append("\n\t").append(h); 918 sb.append("\n---response content---\n").append(output); 919 sb.append("\n=== END ======================================================================="); 920 client.log(client.logRequestsLevel, sb.toString()); 921 } 922 } 923 924 for (RestCallInterceptor r : request.interceptors) { 925 try { 926 r.onClose(request, this); 927 } catch (RuntimeException | Error e) { 928 // Let unchecked exceptions propagate - these indicate programming errors that should be visible 929 throw e; 930 } catch (Exception e) { 931 // Wrap checked exceptions from interceptors (including RestCallException) 932 throw new RestCallException(this, e, "Interceptor throw exception on close"); 933 } 934 } 935 client.onCallClose(request, this); 936 } catch (RuntimeException | Error e) { 937 // Let unchecked exceptions propagate for debuggability 938 throw e; 939 } catch (Exception e) { 940 // Log checked exceptions but don't throw - follows AutoCloseable best practices 941 client.log(Level.WARNING, e, "Error during RestResponse close"); 942 } 943 } 944 945 //------------------------------------------------------------------------------------------------------------------ 946 // Other methods 947 //------------------------------------------------------------------------------------------------------------------ 948 949 /** 950 * Creates a session of the specified part parser. 951 * 952 * @param parser The parser to create a session for. 953 * @return A session of the specified parser. 954 */ 955 protected HttpPartParserSession getPartParserSession(HttpPartParser parser) { 956 HttpPartParserSession s = partParserSessions.get(parser); 957 if (s == null) { 958 s = parser.getPartSession(); 959 partParserSessions.put(parser, s); 960 } 961 return s; 962 } 963 964 /** 965 * Creates a session of the client-default parat parser. 966 * 967 * @return A session of the specified parser. 968 */ 969 protected HttpPartParserSession getPartParserSession() { 970 if (partParserSession == null) 971 partParserSession = client.getPartParser().getPartSession(); 972 return partParserSession; 973 } 974 975 HttpResponse asHttpResponse() { 976 return response; 977 } 978 979 //----------------------------------------------------------------------------------------------------------------- 980 // Fluent setters 981 //----------------------------------------------------------------------------------------------------------------- 982}