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.classic; 018 019import static org.apache.juneau.commons.httppart.HttpPartType.*; 020import static org.apache.juneau.commons.utils.ClassUtils.*; 021import static org.apache.juneau.commons.utils.CollectionUtils.*; 022import static org.apache.juneau.commons.utils.IoUtils.*; 023import static org.apache.juneau.commons.utils.Shorts.*; 024import static org.apache.juneau.http.classic.HttpEntities.*; 025import static org.apache.juneau.http.classic.HttpHeaders.*; 026import static org.apache.juneau.rest.RestSharedConstants.*; 027import static org.apache.juneau.rest.client.classic.RestOperation.*; 028 029import java.io.*; 030import java.lang.reflect.*; 031import java.net.*; 032import java.util.*; 033import java.util.concurrent.*; 034import java.util.function.*; 035import java.util.logging.*; 036 037import org.apache.http.*; 038import org.apache.http.ParseException; 039import org.apache.http.client.*; 040import org.apache.http.client.config.*; 041import org.apache.http.client.entity.*; 042import org.apache.http.client.methods.*; 043import org.apache.http.client.protocol.HttpClientContext; 044import org.apache.http.client.utils.*; 045import org.apache.http.concurrent.*; 046import org.apache.http.entity.BasicHttpEntity; 047import org.apache.http.params.*; 048import org.apache.http.protocol.*; 049import org.apache.http.util.*; 050import org.apache.juneau.commons.bean.*; 051import org.apache.juneau.commons.collections.*; 052import org.apache.juneau.commons.httppart.*; 053import org.apache.juneau.commons.reflect.*; 054import org.apache.juneau.http.classic.*; 055import org.apache.juneau.http.classic.HttpHeaders; 056import org.apache.juneau.http.classic.entity.*; 057import org.apache.juneau.http.classic.header.*; 058import org.apache.juneau.http.classic.header.ContentType; 059import org.apache.juneau.http.classic.part.*; 060import org.apache.juneau.http.classic.resource.*; 061import org.apache.juneau.http.remote.*; 062import org.apache.juneau.rest.client.RedirectSecurity; 063import org.apache.juneau.rest.client.classic.remote.*; 064import org.apache.juneau.marshall.*; 065import org.apache.juneau.marshall.bson.*; 066import org.apache.juneau.marshall.html.*; 067import org.apache.juneau.marshall.httppart.*; 068import org.apache.juneau.marshall.jcs.*; 069import org.apache.juneau.marshall.json.*; 070import org.apache.juneau.marshall.json5.*; 071import org.apache.juneau.marshall.marshaller.*; 072import org.apache.juneau.marshall.marshaller.Uon; 073import org.apache.juneau.marshall.msgpack.*; 074import org.apache.juneau.marshall.oapi.*; 075import org.apache.juneau.marshall.parser.*; 076import org.apache.juneau.marshall.plaintext.*; 077import org.apache.juneau.marshall.serializer.*; 078import org.apache.juneau.marshall.stream.*; 079import org.apache.juneau.marshall.uon.*; 080import org.apache.juneau.marshall.urlencoding.*; 081import org.apache.juneau.marshall.xml.*; 082 083/** 084 * Represents a request to a remote REST resource. 085 * 086 * <p> 087 * Instances of this class are created by the various creator methods on the {@link RestClient} class. 088 * 089 * <h5 class='section'>Example:</h5> 090 * <p class='bjava'> 091 * <jc>// Create a request and automatically close it.</jc> 092 * <jk>try</jk> (<jv>RestRequest</jv> <jv>req</jv> = <jv>client</jv>.get(<js>"/myResource"</js>)) { 093 * <jv>req</jv> 094 * .queryData(<js>"foo"</js>, <js>"bar"</js>) 095 * .run() 096 * .assertStatus().asCode().is(200); 097 * } 098 * </p> 099 * 100 * <p> 101 * The {@link #close()} method will automatically close any associated {@link RestResponse} if one was created via {@link #run()}. 102 * 103 * <h5 class='section'>Notes:</h5><ul> 104 * <li class='warn'>This class is not thread safe. 105 * <li class='note'>This class implements {@link AutoCloseable} and can be used in try-with-resources blocks. 106 * The {@link #close()} method allows unchecked exceptions to propagate for debuggability, 107 * while catching and logging checked exceptions to follow AutoCloseable best practices. 108 * </ul> 109 * 110 * <h5 class='section'>See Also:</h5><ul> 111 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestClient">juneau-rest-client-classic</a> 112 * </ul> 113 */ 114@SuppressWarnings({ 115 "java:S1133", // Intentional deprecation retained for backward compatibility until the documented removal; the reminder is not actionable now. 116 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 117 "java:S3776", // Cognitive complexity acceptable for request-building and dispatch logic. 118 "java:S3740", // Raw Class/Supplier types used for fluent REST request building where response type is unknown at construction time 119 "java:S6539", // Monster class; RestRequest is intentionally a single per-request fluent builder aggregating headers/query/form/body/serialization config 120 "resource", // Resource management handled externally 121 "unchecked" // toMap() casts a value already verified to be a Map to Map<Object,Object>. 122}) 123public class RestRequest extends MarshallingSession implements HttpUriRequest, Configurable, AutoCloseable { 124 125 private static final String HEADER_ACCEPT = org.apache.juneau.http.header.Accept.NAME; 126 private static final String HEADER_CONTENT_TYPE = org.apache.juneau.http.header.ContentType.NAME; 127 128 // URI path separator (always "/" per RFC 3986) 129 private static final String URI_PATH_SEPARATOR = "/"; 130 131 private class SimpleFormData extends SimplePart { 132 SimpleFormData(NameValuePair x) { 133 super(x, client.isSkipEmptyFormData()); 134 } 135 } 136 137 private class SimpleHeader extends SimplePart implements Header { 138 139 SimpleHeader(NameValuePair x) { 140 super(x, client.isSkipEmptyHeaderData()); 141 } 142 143 @Override 144 public HeaderElement[] getElements() throws ParseException { return new HeaderElement[0]; } 145 } 146 147 private class SimplePart implements NameValuePair { 148 final String name; 149 final String value; 150 151 SimplePart(NameValuePair x, boolean skipIfEmpty) { 152 name = x.getName(); 153 if (x instanceof SerializedHeader x2) { 154 value = x2.copyWith(getPartSerializerSession(), null).getValue(); 155 } else if (x instanceof SerializedPart x2) { 156 value = x2.copyWith(getPartSerializerSession(), null).getValue(); 157 } else { 158 var v = x.getValue(); 159 value = (ie(v) && skipIfEmpty) ? null : v; 160 } 161 } 162 163 @Override 164 public String getName() { return name; } 165 166 @Override 167 public String getValue() { return value; } 168 169 boolean isValid() { 170 return !(ie(name) || value == null); 171 } 172 } 173 174 private class SimplePath extends SimplePart { 175 SimplePath(NameValuePair x) { 176 super(x, false); 177 } 178 } 179 180 private class SimpleQuery extends SimplePart { 181 SimpleQuery(NameValuePair x) { 182 super(x, client.isSkipEmptyQueryData()); 183 } 184 } 185 186 private static final ContentType TEXT_PLAIN = ContentType.TEXT_PLAIN; 187 188 private static boolean isHeaderArray(Object o) { 189 if (! isArray(o)) 190 return false; 191 return Header.class.isAssignableFrom(o.getClass().getComponentType()); 192 } 193 194 private static boolean isNameValuePairArray(Object o) { 195 if (! isArray(o)) 196 return false; 197 return NameValuePair.class.isAssignableFrom(o.getClass().getComponentType()); 198 } 199 200 private static Map<Object,Object> toMap(Object o) { 201 return (Map<Object,Object>)o; 202 } 203 204 final RestClient client; // The client that created this call. 205 private final HttpRequestBase request; // The request. 206 private boolean ignoreErrors; 207 private boolean suppressLogging; 208 private HttpContext context; 209 // Set for the request of an SSRF-guard-active @Remote call (by RestClient, or by run() while the calling thread runs 210 // a guarded call); it then sends every hop with the SsrfGuardMarker on its context. Volatile: the request's 211 // HttpClient may run on another thread. 212 private volatile boolean ssrfGuarded; 213 private HttpHost target; 214 private HttpPartSchema contentSchema; 215 private HttpPartSerializerSession partSerializerSession; 216 private final HeaderList headerData; 217 private List<Class<? extends Throwable>> rethrow; 218 List<RestCallInterceptor> interceptors = list(); // Used for intercepting and altering requests. 219 private Object content; 220 private Parser parser; 221 private final PartList formData; 222 private final PartList pathData; 223 private final PartList queryData; 224 private Predicate<Integer> errorCodes; 225 private RestResponse response; // The response. 226 private Serializer serializer; 227 private final URIBuilder uriBuilder; 228 229 private final Map<HttpPartSerializer,HttpPartSerializerSession> partSerializerSessions = new IdentityHashMap<>(); 230 231 /** 232 * Constructs a REST call with the specified method name. 233 * 234 * @param client The client that created this request. 235 * <br>Cannot be <jk>null</jk>. 236 * @param uri The target URI. 237 * <br>Cannot be <jk>null</jk>. 238 * @param method The HTTP method name (uppercase). 239 * <br>Cannot be <jk>null</jk>. 240 * @param hasBody Whether this method has a body. 241 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 242 */ 243 protected RestRequest(RestClient client, URI uri, String method, boolean hasBody) throws RestCallException { 244 super(reqnn("client", client).getMarshallingContext().createSession()); 245 this.client = client; 246 this.request = createInnerRequest(reqnn("method", method), reqnn("uri", uri), hasBody); 247 this.errorCodes = client.errorCodes; 248 this.formData = client.createFormData(); 249 this.headerData = client.createHeaderData(); 250 this.ignoreErrors = client.ignoreErrors; 251 this.pathData = client.createPathData(); 252 this.queryData = client.createQueryData(); 253 this.uriBuilder = new URIBuilder(request.getURI()); 254 } 255 256 /** 257 * Aborts this http request. Any active execution of this method should return immediately. 258 * 259 * If the request has not started, it will abort after the next execution. 260 * <br>Aborting this request will cause all subsequent executions with this request to fail. 261 */ 262 @Override /* Overridden from HttpUriRequest */ 263 public void abort() throws UnsupportedOperationException { 264 request.abort(); 265 } 266 267 /** 268 * Sets the value for the <c>Accept</c> request header. 269 * 270 * <p> 271 * This overrides the media type specified on the parser, but is overridden by calling 272 * <code>header(<js>"Accept"</js>, value);</code> 273 * 274 * @param value The new header value. 275 * <br>Can be <jk>null</jk> (header will not be set). 276 * @return This object. 277 * @throws RestCallException Invalid input. 278 */ 279 public RestRequest accept(String value) throws RestCallException { 280 return header(Accept.of(value)); 281 } 282 283 /** 284 * Sets the value for the <c>Accept-Charset</c> request header. 285 * 286 * <p> 287 * This is a shortcut for calling <code>header(<js>"Accept-Charset"</js>, value);</code> 288 * 289 * @param value The new header value. 290 * <br>Can be <jk>null</jk> (header will not be set). 291 * @return This object. 292 * @throws RestCallException Invalid input. 293 */ 294 public RestRequest acceptCharset(String value) throws RestCallException { 295 return header(AcceptCharset.of(value)); 296 } 297 298 /** 299 * Adds a header to this message. 300 * 301 * The header will be appended to the end of the list. 302 * 303 * <h5 class='section'>Notes:</h5><ul> 304 * <li class='note'>{@link #header(Header)} is an equivalent method and the preferred method for fluent-style coding. 305 * </ul> 306 * 307 * @param header The header to append. 308 * <br>Can be <jk>null</jk> (ignored). 309 */ 310 @Override /* Overridden from HttpMessage */ 311 public void addHeader(Header header) { 312 headerData.append(header); 313 } 314 315 /** 316 * Adds a header to this message. 317 * 318 * The header will be appended to the end of the list. 319 * 320 * <h5 class='section'>Notes:</h5><ul> 321 * <li class='note'>{@link #header(String,Object)} is an equivalent method and the preferred method for fluent-style coding. 322 * </ul> 323 * 324 * @param name The name of the header. 325 * <br>Cannot be <jk>null</jk> or empty (header will not be created). 326 * @param value The value of the header. 327 * <br>Can be <jk>null</jk>. 328 */ 329 @Override /* Overridden from HttpMessage */ 330 public void addHeader(String name, String value) { 331 headerData.append(stringHeader(name, value)); 332 } 333 334 /** 335 * Sets {@link Cancellable} for the ongoing operation. 336 * 337 * @param cancellable The cancellable object. 338 * <br>Can be <jk>null</jk> (no cancellation will be available). 339 * @return This object. 340 */ 341 public RestRequest cancellable(Cancellable cancellable) { 342 request.setCancellable(cancellable); 343 return this; 344 } 345 346 /** 347 * Closes this request and its associated response (if one was created). 348 * 349 * <p> 350 * This method is idempotent and can be called multiple times without side effects. 351 * 352 * <h5 class='section'>Implementation Notes:</h5> 353 * <p> 354 * This implementation represents a compromise between strict AutoCloseable compliance and debuggability: 355 * <ul> 356 * <li>Unchecked exceptions ({@link RuntimeException} and {@link Error}) from the response close are allowed to propagate. 357 * This ensures programming errors and serious issues are visible during development and testing. 358 * <li>Checked exceptions (including {@link RestCallException}) are caught and logged but not thrown. 359 * This follows AutoCloseable best practices and prevents close exceptions from interfering with 360 * try-with-resources cleanup or masking the original exception. 361 * </ul> 362 */ 363 @Override /* Overridden from AutoCloseable */ 364 public void close() { 365 try { 366 if (nn(response)) { 367 response.close(); 368 } 369 } catch (RuntimeException e) { 370 // Let unchecked exceptions propagate for debuggability 371 throw e; 372 } catch (Exception e) { 373 // Log checked exceptions but don't throw - follows AutoCloseable best practices 374 client.log(Level.WARNING, e, "Error closing RestResponse"); 375 } 376 } 377 378 /** 379 * Same as {@link #run()} but immediately calls {@link RestResponse#consume()} to clean up the response. 380 * 381 * <p> 382 * Use this method if you're only interested in the status line of the response and not the response entity. 383 * Attempts to call any of the methods on the response object that retrieve the body (e.g. {@link ResponseContent#asReader()} 384 * will cause a {@link RestCallException} to be thrown. 385 * 386 * <h5 class='section'>Notes:</h5><ul> 387 * <li class='note'>You do not need to execute {@link InputStream#close()} on the response body to consume the response. 388 * </ul> 389 * 390 * <h5 class='section'>Example:</h5> 391 * <p class='bjava'> 392 * <jc>// Get the response code. 393 * // No need to call close() on the RestResponse object.</jc> 394 * <jk>int</jk> <jv>rc</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).complete().getResponseCode(); 395 * </p> 396 * 397 * @return The response object. 398 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 399 */ 400 public RestResponse complete() throws RestCallException { 401 return run().consume(); 402 } 403 404 /** 405 * Used in combination with {@link #cancellable(Cancellable)}. 406 * 407 * @deprecated Since 10.0, for removal. 408 * @return This object. 409 */ 410 @Deprecated(since = "10.0", forRemoval = true) 411 public RestRequest completed() { 412 request.completed(); 413 return this; 414 } 415 416 /** 417 * Same as {@link #complete()} but allows you to run the call asynchronously. 418 * 419 * <h5 class='section'>Example:</h5> 420 * <p class='bjava'> 421 * Future<RestResponse> <jv>future</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).completeFuture(); 422 * 423 * <jc>// Do some other stuff</jc> 424 * 425 * <jk>int</jk> <jv>rc</jv> = <jv>future</jv>.get().getResponseStatus(); 426 * </p> 427 * 428 * <h5 class='section'>Notes:</h5><ul> 429 * <li class='note'>Use the {@link RestClient.Builder#executorService(ExecutorService, boolean)} method to customize the 430 * executor service used for creating {@link Future Futures}. 431 * <li class='note'>You do not need to execute {@link InputStream#close()} on the response body to consume the response. 432 * </ul> 433 * 434 * @return The HTTP status code. 435 * @throws RestCallException If the executor service was not defined. 436 */ 437 public Future<RestResponse> completeFuture() throws RestCallException { 438 return client.getExecutorService().submit(this::complete); 439 } 440 441 /** 442 * Sets the actual request configuration. 443 * 444 * @param value The new value. 445 * <br>Can be <jk>null</jk> (default configuration will be used). 446 * @return This object. 447 */ 448 public RestRequest config(RequestConfig value) { 449 request.setConfig(value); 450 return this; 451 } 452 453 /** 454 * Checks if a certain header is present in this message. 455 * 456 * Header values are ignored. 457 * 458 * @param name The header name to check for. 459 * <br>Cannot be <jk>null</jk>. 460 * @return <jk>true</jk> if at least one header with this name is present. 461 */ 462 @Override /* Overridden from HttpMessage */ 463 public boolean containsHeader(String name) { 464 return headerData.contains(name); 465 } 466 467 /** 468 * Sets the body of this request. 469 * 470 * @param value 471 * The input to be sent to the REST resource (only valid for PUT/POST/PATCH) requests. 472 * <br>Can be of the following types: 473 * <ul class='spaced-list'> 474 * <li> 475 * {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource. 476 * <li> 477 * {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource. 478 * <li> 479 * {@link HttpResource} - Raw contents will be serialized to remote resource. Additional headers and media type will be set on request. 480 * <li> 481 * {@link HttpEntity}/{@link BasicHttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient. 482 * <li> 483 * {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the 484 * {@link RestClient}. 485 * <li> 486 * {@link PartList} - Converted to a URL-encoded FORM post. 487 * <li> 488 * A {@link Supplier} of anything on this list. 489 * </ul> 490 * <br>Can be <jk>null</jk> (no body will be sent). 491 * @return This object. 492 */ 493 public RestRequest content(Object value) { 494 content = value; 495 return this; 496 } 497 498 /** 499 * Sets the body of this request. 500 * 501 * @param input 502 * The input to be sent to the REST resource (only valid for PUT/POST/PATCH) requests. 503 * <br>Can be of the following types: 504 * <ul class='spaced-list'> 505 * <li> 506 * {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource. 507 * <li> 508 * {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource. 509 * <li> 510 * {@link HttpResource} - Raw contents will be serialized to remote resource. Additional headers and media type will be set on request. 511 * <li> 512 * {@link HttpEntity}/{@link BasicHttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient. 513 * <li> 514 * {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the 515 * {@link RestClient}. 516 * <li> 517 * {@link PartList} - Converted to a URL-encoded FORM post. 518 * <li> 519 * A {@link Supplier} of anything on this list. 520 * </ul> 521 * @param schema The schema object that defines the format of the output. 522 * <ul> 523 * <li>If <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 524 * <li>Only used if serializer is schema-aware (e.g. {@link OpenApiSerializer}). 525 * </ul> 526 * @return This object. 527 */ 528 public RestRequest content(Object input, HttpPartSchema schema) { 529 this.content = input; 530 this.contentSchema = schema; 531 return this; 532 } 533 534 /** 535 * Sets the body of this request as straight text bypassing the serializer. 536 * 537 * <p class='bjava'> 538 * <jv>client</jv> 539 * .put(<js>"/foo"</js>) 540 * .content(<jk>new</jk> StringReader(<js>"foo"</js>)) 541 * .contentType(<js>"text/foo"</js>) 542 * .run(); 543 * 544 * <jv>client</jv> 545 * .put(<js>"/foo"</js>) 546 * .bodyString(<js>"foo"</js>) 547 * .run(); 548 * </p> 549 * 550 * <p> 551 * Note that this is different than the following which will serialize <l>foo</l> as a JSON string <l>"foo"</l>. 552 * <p class='bjava'> 553 * <jv>client</jv> 554 * .put(<js>"/foo"</js>) 555 * .json() 556 * .content(<js>"foo"</js>) 557 * .run(); 558 * </p> 559 * 560 * @param input 561 * The input to be sent to the REST resource (only valid for PUT/POST/PATCH) requests. 562 * @return This object. 563 * @throws RestCallException If a retry was attempted, but the entity was not repeatable. 564 */ 565 public RestRequest contentString(Object input) throws RestCallException { 566 return content(input == null ? null : new StringReader(s(input))); 567 } 568 569 /** 570 * Sets the value for the <c>Content-Type</c> request header. 571 * 572 * <p> 573 * This overrides the media type specified on the serializer, but is overridden by calling 574 * <code>header(<js>"Content-Type"</js>, value);</code> 575 * 576 * @param value The new header value. 577 * <br>Can be <jk>null</jk> (header will not be set). 578 * @return This object. 579 * @throws RestCallException Invalid input. 580 */ 581 public RestRequest contentType(String value) throws RestCallException { 582 return header(ContentType.of(value)); 583 } 584 585 /** 586 * Override the context to use for the execution. 587 * 588 * @param context The context to use for the execution, or <jk>null</jk> to use the default context. 589 * @return This object. 590 */ 591 public RestRequest context(HttpContext context) { 592 this.context = context; 593 return this; 594 } 595 596 /** 597 * Sets <c>Debug: value</c> header on this request. 598 * 599 * @return This object. 600 * @throws RestCallException Invalid input. 601 */ 602 public RestRequest debug() throws RestCallException { 603 header("Debug", true); 604 return this; 605 } 606 607 /** 608 * Allows you to override what status codes are considered error codes that would result in a {@link RestCallException}. 609 * 610 * <p> 611 * The default error code predicate is: <code>x -> x >= 400</code>. 612 * 613 * @param value The new predicate for calculating error codes. 614 * <br>Cannot be <jk>null</jk>. 615 * @return This object. 616 */ 617 public RestRequest errorCodes(Predicate<Integer> value) { 618 errorCodes = reqnn("value", value); 619 return this; 620 } 621 622 /** 623 * Appends multiple form-data parameters to the request. 624 * 625 * <h5 class='section'>Example:</h5> 626 * <p class='bjava'> 627 * <jc>// Appends two form-data parameters to the request.</jc> 628 * <jv>client</jv> 629 * .get(<jsf>URI</jsf>) 630 * .formData( 631 * BasicStringPart.<jsm>of</jsm>(<js>"foo"</js>, <js>"bar"</js>), 632 * BasicBooleanPart.<jsm>of</jsm>(<js>"baz"</js>, <jk>true</jk>) 633 * ) 634 * .run(); 635 * </p> 636 * 637 * @param parts 638 * The parameters to set. 639 * <jk>null</jk> values are ignored. 640 * @return This object. 641 */ 642 public RestRequest formData(NameValuePair...parts) { 643 formData.append(parts); 644 return this; 645 } 646 647 /** 648 * Adds a form-data parameter to the request body. 649 * 650 * <h5 class='section'>Example:</h5> 651 * <p class='bjava'> 652 * <jc>// Adds form data parameter "foo=bar".</jc> 653 * <jv>client</jv> 654 * .formPost(<jsf>URI</jsf>) 655 * .formData(<js>"foo"</js>, <js>"bar"</js>) 656 * .run(); 657 * </p> 658 * 659 * @param name 660 * The parameter name. 661 * <br>Cannot be <jk>null</jk> or empty (parameter will not be created). 662 * @param value 663 * The parameter value. 664 * <br>Non-string values are converted to strings using the {@link HttpPartSerializer} defined on the client. 665 * <br>Can be <jk>null</jk>. 666 * @return This object. 667 */ 668 public RestRequest formData(String name, Object value) { 669 formData.append(createPart(FORMDATA, name, value)); 670 return this; 671 } 672 673 /** 674 * Appends multiple form-data parameters to the request from properties defined on a Java bean. 675 * 676 * <h5 class='section'>Example:</h5> 677 * <p class='bjava'> 678 * <jk>public class</jk> MyFormData { 679 * <jk>public</jk> String getFooBar() { <jk>return</jk> <js>"baz"</js>; } 680 * <jk>public</jk> Integer getQux() { <jk>return</jk> 123; } 681 * } 682 * 683 * <jc>// Appends form data "fooBar=baz&qux=123".</jc> 684 * <jv>client</jv> 685 * .get(<jsf>URI</jsf>) 686 * .formDataBean(<jk>new</jk> MyFormData()) 687 * .run(); 688 * </p> 689 * 690 * @param value The bean containing the properties to set as form-data parameter values. 691 * <br>Cannot be <jk>null</jk>. 692 * @return This object. 693 */ 694 public RestRequest formDataBean(Object value) { 695 req(isBean(reqnn("value", value)), "Object passed into formDataBean(Object) is not a bean."); 696 var b = formData; 697 toBeanMap(value).forEach((k, v) -> b.append(createPart(FORMDATA, k, v))); 698 return this; 699 } 700 701 /** 702 * Adds form-data parameters as the entire body of the request. 703 * 704 * <h5 class='section'>Example:</h5> 705 * <p class='bjava'> 706 * <jc>// Creates form data "foo=bar&baz=qux".</jc> 707 * <jv>client</jv> 708 * .formPost(<jsf>URI</jsf>) 709 * .formDataCustom(<js>"foo=bar&baz=qux"</js>) 710 * .run(); 711 * 712 * <jc>// Creates form data "foo=bar&baz=qux" using StringEntity.</jc> 713 * <jv>client</jv> 714 * .formPost(<jsf>URI</jsf>) 715 * .formDataCustom(<jk>new</jk> StringEntity(<js>"foo=bar&baz=qux"</js>,<js>"application/x-www-form-urlencoded"</js>)) 716 * .run(); 717 * 718 * <jc>// Creates form data "foo=bar&baz=qux" using StringEntity and body().</jc> 719 * <jv>client</jv> 720 * .formPost(<jsf>URI</jsf>) 721 * .content(<jk>new</jk> StringEntity(<js>"foo=bar&baz=qux"</js>,<js>"application/x-www-form-urlencoded"</js>)) 722 * .run(); 723 * </p> 724 * 725 * @param value The parameter value. 726 * <br>Can be any of the following types: 727 * <ul class='spaced-list'> 728 * <li> 729 * {@link CharSequence} 730 * <li> 731 * {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource. 732 * <li> 733 * {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource. 734 * <li> 735 * {@link HttpResource} - Raw contents will be serialized to remote resource. Additional headers and media type will be set on request. 736 * <li> 737 * {@link HttpEntity}/{@link BasicHttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient. 738 * <li> 739 * {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the 740 * {@link RestClient}. 741 * <li> 742 * {@link PartList} - Converted to a URL-encoded FORM post. 743 * </ul> 744 * <br>Can be <jk>null</jk> (no body will be sent). 745 * @return This object. 746 */ 747 public RestRequest formDataCustom(Object value) { 748 header(ContentType.APPLICATION_FORM_URLENCODED); 749 content(value instanceof CharSequence ? new StringReader(value.toString()) : value); 750 return this; 751 } 752 753 /** 754 * Adds form-data parameters to the request body using free-form key/value pairs. 755 * 756 * <h5 class='section'>Example:</h5> 757 * <p class='bjava'> 758 * <jc>// Creates form data "key1=val1&key2=val2".</jc> 759 * <jv>client</jv> 760 * .formPost(<jsf>URI</jsf>) 761 * .formDataPairs(<js>"key1"</js>,<js>"val1"</js>,<js>"key2"</js>,<js>"val2"</js>) 762 * .run(); 763 * </p> 764 * 765 * @param pairs The form-data key/value pairs. 766 * <ul> 767 * <li>Values can be any POJO. 768 * <li>Values converted to a string using the configured part serializer. 769 * </ul> 770 * <br>Cannot be <jk>null</jk>. 771 * <br>Must have an even number of elements (key/value pairs). 772 * <br>Null keys are ignored (parameter will not be created). 773 * <br>Null values are allowed. 774 * @return This object. 775 * @throws RestCallException Invalid input. 776 */ 777 public RestRequest formDataPairs(String...pairs) throws RestCallException { 778 req(pairs.length % 2 == 0, "Odd number of parameters passed into formDataPairs(String...)"); 779 var b = formData; 780 for (var i = 0; i < pairs.length; i += 2) 781 b.append(pairs[i], pairs[i + 1]); 782 return this; 783 } 784 785 /** 786 * Returns all the headers of this message. 787 * 788 * Headers are ordered in the sequence they will be sent over a connection. 789 * 790 * @return All the headers of this message 791 */ 792 @Override /* Overridden from HttpMessage */ 793 public Header[] getAllHeaders() { return headerData.getAll(); } 794 795 /** 796 * Returns the actual request configuration. 797 * 798 * @return The actual request configuration. 799 */ 800 @Override /* Overridden from Configurable */ 801 public RequestConfig getConfig() { return request.getConfig(); } 802 803 /** 804 * Returns the first header with a specified name of this message. 805 * 806 * Header values are ignored. 807 * <br>If there is more than one matching header in the message the first element of {@link #getHeaders(String)} is returned. 808 * <br>If there is no matching header in the message <jk>null</jk> is returned. 809 * 810 * @param name The name of the header to return. 811 * <br>Cannot be <jk>null</jk>. 812 * @return The first header whose name property equals name or <jk>null</jk> if no such header could be found. 813 */ 814 @Override /* Overridden from HttpMessage */ 815 public Header getFirstHeader(String name) { 816 return headerData.getFirst(name).orElse(null); 817 } 818 819 /** 820 * Returns the form data for the request. 821 * 822 * @return An immutable list of form data to send on the request. 823 */ 824 public PartList getFormData() { return formData; } 825 826 /** 827 * Returns the header data for the request. 828 * 829 * @return An immutable list of headers to send on the request. 830 */ 831 public HeaderList getHeaders() { return headerData; } 832 833 /** 834 * Returns all the headers with a specified name of this message. 835 * 836 * Header values are ignored. 837 * <br>Headers are ordered in the sequence they will be sent over a connection. 838 * 839 * @param name The name of the headers to return. 840 * <br>Cannot be <jk>null</jk>. 841 * @return The headers whose name property equals name. 842 */ 843 @Override /* Overridden from HttpMessage */ 844 public Header[] getHeaders(String name) { 845 return headerData.getAll(name); 846 } 847 848 /** 849 * Returns the body of this request. 850 * 851 * @return The body of this request, or <jk>null</jk> if it doesn't have a body. 852 */ 853 public HttpEntity getHttpEntity() { return hasHttpEntity() ? ((HttpEntityEnclosingRequestBase)request).getEntity() : null; } 854 855 /** 856 * Returns the last header with a specified name of this message. 857 * 858 * Header values are ignored. 859 * <br>If there is more than one matching header in the message the last element of {@link #getHeaders(String)} is returned. 860 * <br>If there is no matching header in the message, <jk>null</jk> is returned. 861 * 862 * @param name The name of the header to return. 863 * <br>Cannot be <jk>null</jk>. 864 * @return The last header whose name property equals name or <jk>null</jk> if no such header could be found. 865 */ 866 @Override /* Overridden from HttpMessage */ 867 public Header getLastHeader(String name) { 868 return headerData.getLast(name).orElse(null); 869 } 870 871 /** 872 * Returns the HTTP method this request uses, such as GET, PUT, POST, or other. 873 * 874 * @return The HTTP method this request uses, such as GET, PUT, POST, or other. 875 */ 876 @Override /* Overridden from HttpUriRequest */ 877 public String getMethod() { return request.getMethod(); } 878 879 /** 880 * Returns the parameters effective for this message as set by {@link #setParams(HttpParams)}. 881 * 882 * @return The parameters effective for this message as set by {@link #setParams(HttpParams)}. 883 * @deprecated Use constructor parameters of configuration API provided by HttpClient. 884 */ 885 @Override /* Overridden from HttpMessage */ 886 @Deprecated(since = "10.0", forRemoval = true) 887 public HttpParams getParams() { return request.getParams(); } 888 889 /** 890 * Returns the path data for the request. 891 * 892 * @return An immutable list of path data to send on the request. 893 */ 894 public PartList getPathData() { return pathData; } 895 896 /** 897 * Returns the protocol version this message is compatible with. 898 * 899 * @return The protocol version. 900 */ 901 @Override /* Overridden from HttpMessage */ 902 public ProtocolVersion getProtocolVersion() { return request.getProtocolVersion(); } 903 904 /** 905 * Returns the query data for the request. 906 * 907 * @return An immutable list of query data to send on the request. 908 */ 909 public PartList getQueryData() { return queryData; } 910 911 /** 912 * Returns the request line of this request. 913 * 914 * @return The request line. 915 */ 916 @Override /* Overridden from HttpRequest */ 917 public RequestLine getRequestLine() { return request.getRequestLine(); } 918 919 /** 920 * A shortcut for calling <c>run().getContent().as(<js>type</js>)</c>. 921 * 922 * @param type The object type to create. 923 * <br>Cannot be <jk>null</jk>. 924 * @param <T> The object type to create. 925 * @see ResponseContent#as(Class) 926 * @return The response content as a simple string. 927 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 928 */ 929 public <T> T getResponse(Class<T> type) throws RestCallException { 930 return run().getContent().as(type); 931 } 932 933 /** 934 * A shortcut for calling <c>run().getContent().as(<js>type</js>,<js>args</js>)</c>. 935 * 936 * @param <T> 937 * The object type to create. 938 * @param type 939 * The object type to create. 940 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 941 * <br>Cannot be <jk>null</jk>. 942 * @param args 943 * The type arguments of the class if it's a collection or map. 944 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 945 * <br>Ignored if the main type is not a map or collection. 946 * <br>Can contain <jk>null</jk> values (ignored). 947 * @see ResponseContent#as(Type,Type...) 948 * @return The response content as a simple string. 949 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 950 */ 951 public <T> T getResponse(Type type, Type...args) throws RestCallException { 952 return run().getContent().as(type, args); 953 } 954 955 /** 956 * A shortcut for calling <c>run().getContent().asString()</c>. 957 * 958 * @return The response content as a simple string. 959 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 960 */ 961 public String getResponseAsString() throws RestCallException { return run().getContent().asString(); } 962 963 /** 964 * Returns the original request URI. 965 * 966 * <h5 class='section'>Notes:</h5><ul> 967 * <li class='note'>URI remains unchanged in the course of request execution and is not updated if the request is redirected to another location. 968 * </ul> 969 * 970 * @return The original request URI. 971 */ 972 @Override /* Overridden from HttpUriRequest */ 973 public URI getURI() { return request.getURI(); } 974 975 /** 976 * Returns <jk>true</jk> if this request has a body. 977 * 978 * @return <jk>true</jk> if this request has a body. 979 */ 980 public boolean hasHttpEntity() { 981 return request instanceof HttpEntityEnclosingRequestBase; 982 } 983 984 /** 985 * Appends a header to the request. 986 * 987 * <h5 class='section'>Example:</h5> 988 * <p class='bjava'> 989 * <jc>// Adds header "Foo: bar".</jc> 990 * <jv>client</jv> 991 * .get(<jsf>URI</jsf>) 992 * .header(Accept.<jsf>TEXT_XML</jsf>) 993 * .run(); 994 * </p> 995 * 996 * @param part 997 * The parameter to set. 998 * <jk>null</jk> values are ignored. 999 * @return This object. 1000 */ 1001 public RestRequest header(Header part) { 1002 return headers(part); 1003 } 1004 1005 /** 1006 * Appends a header to the request. 1007 * 1008 * <h5 class='section'>Example:</h5> 1009 * <p class='bjava'> 1010 * <jc>// Adds header "Foo: bar".</jc> 1011 * <jv>client</jv> 1012 * .get(<jsf>URI</jsf>) 1013 * .header(<js>"Foo"</js>, <js>"bar"</js>) 1014 * .run(); 1015 * </p> 1016 * 1017 * @param name 1018 * The parameter name. 1019 * @param value 1020 * The parameter value. 1021 * <br>Non-string values are converted to strings using the {@link HttpPartSerializer} defined on the client. 1022 * @return This object. 1023 */ 1024 public RestRequest header(String name, Object value) { 1025 headerData.append(createHeader(name, value)); 1026 return this; 1027 } 1028 1029 /** 1030 * Returns an iterator of all the headers. 1031 * 1032 * @return Iterator that returns {@link Header} objects in the sequence they are sent over a connection. 1033 */ 1034 @Override /* Overridden from HttpMessage */ 1035 public HeaderIterator headerIterator() { 1036 return headerData.headerIterator(); 1037 } 1038 1039 /** 1040 * Returns an iterator of the headers with a given name. 1041 * 1042 * @param name the name of the headers over which to iterate, or <jk>null</jk> for all headers. 1043 * @return Iterator that returns {@link Header} objects with the argument name in the sequence they are sent over a connection. 1044 */ 1045 @Override /* Overridden from HttpMessage */ 1046 public HeaderIterator headerIterator(String name) { 1047 return headerData.headerIterator(name); 1048 } 1049 1050 /** 1051 * Appends multiple headers to the request using freeform key/value pairs. 1052 * 1053 * <h5 class='section'>Example:</h5> 1054 * <p class='bjava'> 1055 * <jc>// Adds headers "Foo: bar" and "Baz: qux".</jc> 1056 * <jv>client</jv> 1057 * .get(<jsf>URI</jsf>) 1058 * .headerPairs(<js>"Foo"</js>,<js>"bar"</js>,<js>"Baz"</js>,<js>"qux"</js>) 1059 * .run(); 1060 * </p> 1061 * 1062 * @param pairs The header key/value pairs. 1063 * <br>Cannot be <jk>null</jk>. 1064 * <br>Must have an even number of elements (key/value pairs). 1065 * <br>Null keys are ignored (header will not be created). 1066 * <br>Null values are allowed. 1067 * @return This object. 1068 */ 1069 public RestRequest headerPairs(String...pairs) { 1070 req(pairs.length % 2 == 0, "Odd number of parameters passed into headerPairs(String...)"); 1071 var b = headerData; 1072 for (var i = 0; i < pairs.length; i += 2) 1073 b.append(pairs[i], pairs[i + 1]); 1074 return this; 1075 } 1076 1077 /** 1078 * Appends multiple headers to the request. 1079 * 1080 * <h5 class='section'>Example:</h5> 1081 * <p class='bjava'> 1082 * <jc>// Appends two headers to the request.</jc> 1083 * <jv>client</jv> 1084 * .get(<jsf>URI</jsf>) 1085 * .headers( 1086 * BasicHeader.<jsm>of</jsm>(<js>"Foo"</js>, <js>"bar"</js>), 1087 * Accept.<jsf>TEXT_XML</jsf> 1088 * ) 1089 * .run(); 1090 * </p> 1091 * 1092 * @param parts 1093 * The parameters to set. 1094 * <jk>null</jk> values are ignored. 1095 * @return This object. 1096 */ 1097 public RestRequest headers(Header...parts) { 1098 headerData.append(parts); 1099 return this; 1100 } 1101 1102 /** 1103 * Appends multiple headers to the request from properties defined on a Java bean. 1104 * 1105 * <p> 1106 * Uses {@link PropertyNamerDUCS} for resolving the header names from property names. 1107 * 1108 * <h5 class='section'>Example:</h5> 1109 * <p class='bjava'> 1110 * <ja>@Marshalled</ja> 1111 * <jk>public class</jk> MyHeaders { 1112 * <jk>public</jk> String getFooBar() { <jk>return</jk> <js>"baz"</js>; } 1113 * <jk>public</jk> Integer getQux() { <jk>return</jk> 123; } 1114 * } 1115 * 1116 * <jc>// Appends headers "Foo-Bar: baz" and "Qux: 123".</jc> 1117 * <jv>client</jv> 1118 * .get(<jsf>URI</jsf>) 1119 * .headersBean(<jk>new</jk> MyHeaders()) 1120 * .run(); 1121 * </p> 1122 * 1123 * @param value The bean containing the properties to set as header values. 1124 * <br>Cannot be <jk>null</jk>. 1125 * @return This object. 1126 */ 1127 public RestRequest headersBean(Object value) { 1128 req(isBean(reqnn("value", value)), "Object passed into headersBean(Object) is not a bean."); 1129 var b = headerData; 1130 toBeanMap(value, PropertyNamerDUCS.INSTANCE).forEach((k, v) -> b.append(createHeader(k, v))); 1131 return this; 1132 } 1133 1134 /** 1135 * Convenience method for specifying HTML as the marshalling transmission media type for this request only. 1136 * 1137 * <p> 1138 * POJOs are converted to HTML without any sort of doc wrappers. 1139 * 1140 * <p> 1141 * {@link HtmlSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 1142 * <ul> 1143 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 1144 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1145 * </ul> 1146 * <p> 1147 * {@link HtmlParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 1148 * <ul> 1149 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or 1150 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1151 * </ul> 1152 * <p> 1153 * <c>Accept</c> request header will be set to <js>"text/html"</js> unless overridden 1154 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 1155 * <p> 1156 * <c>Content-Type</c> request header will be set to <js>"text/html"</js> unless overridden 1157 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 1158 * <p> 1159 * Identical to calling <c>serializer(HtmlSerializer.<jk>class</jk>).parser(HtmlParser.<jk>class</jk>)</c>. 1160 * 1161 * @return This object. 1162 */ 1163 public RestRequest html() { 1164 return serializer(HtmlSerializer.class).parser(HtmlParser.class); 1165 } 1166 1167 /** 1168 * Convenience method for specifying HTML DOC as the marshalling transmission media type for this request only. 1169 * 1170 * <p> 1171 * POJOs are converted to fully renderable HTML pages. 1172 * 1173 * <p> 1174 * {@link HtmlDocSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 1175 * <ul> 1176 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 1177 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1178 * </ul> 1179 * <p> 1180 * {@link HtmlParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 1181 * <ul> 1182 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or 1183 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1184 * </ul> 1185 * <p> 1186 * <c>Accept</c> request header will be set to <js>"text/html"</js> unless overridden 1187 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 1188 * <p> 1189 * <c>Content-Type</c> request header will be set to <js>"text/html"</js> unless overridden 1190 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 1191 * <p> 1192 * Identical to calling <c>serializer(HtmlDocSerializer.<jk>class</jk>).parser(HtmlParser.<jk>class</jk>)</c>. 1193 * 1194 * @return This object. 1195 */ 1196 public RestRequest htmlDoc() { 1197 return serializer(HtmlDocSerializer.class).parser(HtmlParser.class); 1198 } 1199 1200 /** 1201 * Convenience method for specifying Stripped HTML DOC as the marshalling transmission media type for this request only. 1202 * 1203 * <p> 1204 * Same as {@link #htmlDoc()} but without the header and body tags and page title and description. 1205 * 1206 * <p> 1207 * {@link HtmlStrippedDocSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 1208 * <ul> 1209 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 1210 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1211 * </ul> 1212 * <p> 1213 * {@link HtmlParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 1214 * <ul> 1215 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or 1216 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1217 * </ul> 1218 * <p> 1219 * <c>Accept</c> request header will be set to <js>"text/html+stripped"</js> unless overridden 1220 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 1221 * <p> 1222 * <c>Content-Type</c> request header will be set to <js>"text/html+stripped"</js> unless overridden 1223 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 1224 * <p> 1225 * Identical to calling <c>serializer(HtmlStrippedDocSerializer.<jk>class</jk>).parser(HtmlParser.<jk>class</jk>)</c>. 1226 * 1227 * @return This object. 1228 */ 1229 public RestRequest htmlStrippedDoc() { 1230 return serializer(HtmlStrippedDocSerializer.class).parser(HtmlParser.class); 1231 } 1232 1233 /** 1234 * Prevent {@link RestCallException RestCallExceptions} from being thrown when HTTP status 400+ is encountered. 1235 * 1236 * <p> 1237 * This overrides the <l>ignoreErrors</l> property on the client. 1238 * 1239 * @return This object. 1240 */ 1241 public RestRequest ignoreErrors() { 1242 this.ignoreErrors = true; 1243 return this; 1244 } 1245 1246 /** 1247 * Add one or more interceptors for this call only. 1248 * 1249 * @param interceptors The interceptors to add to this call. 1250 * <br>Cannot contain <jk>null</jk> values. 1251 * @return This object. 1252 * @throws RestCallException If init method on interceptor threw an exception. 1253 */ 1254 public RestRequest interceptors(RestCallInterceptor...interceptors) throws RestCallException { 1255 reqnns("interceptors", interceptors); 1256 try { 1257 for (var i : interceptors) { 1258 this.interceptors.add(i); 1259 i.onInit(this); 1260 } 1261 } catch (RuntimeException | RestCallException e) { 1262 throw e; 1263 } catch (Exception e) { 1264 throw new RestCallException(null, e, "Interceptor threw an exception on init."); 1265 } 1266 1267 return this; 1268 } 1269 1270 @Override /* Overridden from HttpUriRequest */ 1271 public boolean isAborted() { return request.isAborted(); } 1272 1273 /** 1274 * Returns <jk>true</jk> if debug mode is currently enabled. 1275 */ 1276 @Override 1277 public boolean isDebug() { return headerData.get("Debug", Boolean.class).orElse(false); } 1278 1279 /** 1280 * Convenience method for specifying JSON as the marshalling transmission media type for this request only. 1281 * 1282 * <p> 1283 * {@link JsonSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 1284 * <ul> 1285 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 1286 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1287 * </ul> 1288 * <p> 1289 * {@link JsonParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 1290 * <ul> 1291 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or 1292 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1293 * </ul> 1294 * <p> 1295 * <c>Accept</c> request header will be set to <js>"application/json"</js> unless overridden 1296 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 1297 * <p> 1298 * <c>Content-Type</c> request header will be set to <js>"application/json"</js> unless overridden 1299 * {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 1300 * <p> 1301 * Identical to calling <c>serializer(JsonSerializer.<jk>class</jk>).parser(JsonParser.<jk>class</jk>)</c>. 1302 * 1303 * @return This object. 1304 */ 1305 public RestRequest json() { 1306 return serializer(JsonSerializer.class).parser(JsonParser.class); 1307 } 1308 1309 /** 1310 * Convenience method for specifying Simplified JSON as the marshalling transmission media type for this request only. 1311 * 1312 * <p> 1313 * Simplified JSON is typically useful for automated tests because you can do simple string comparison of results 1314 * without having to escape lots of quotes. 1315 * 1316 * <p> 1317 * {@link Json5Serializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 1318 * <ul> 1319 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 1320 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1321 * </ul> 1322 * <p> 1323 * {@link Json5Parser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 1324 * <ul> 1325 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or 1326 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1327 * </ul> 1328 * <p> 1329 * <c>Accept</c> request header will be set to <js>"application/json5"</js> unless overridden 1330 * by {@link #header(String,Object)} or {@link #accept(String)}, or per-request via {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 1331 * <p> 1332 * <c>Content-Type</c> request header will be set to <js>"application/json5"</js> unless overridden 1333 * by {@link #header(String,Object)} or {@link #contentType(String)}, or per-request via {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 1334 * <p> 1335 * A server that only handles JSON may reply <c>406 Not Acceptable</c> or <c>415 Unsupported Media Type</c> to these 1336 * JSON5 media types. 1337 * <p> 1338 * Can be combined with other marshaller setters such as {@link #xml()} to provide support for multiple languages. 1339 * <ul> 1340 * <li>When multiple languages are supported, the <c>Accept</c> and <c>Content-Type</c> headers control which marshallers are used, or uses the 1341 * last-enabled language if the headers are not set. 1342 * </ul> 1343 * <p> 1344 * Identical to calling <c>serializer(Json5Serializer.<jk>class</jk>).parser(Json5Parser.<jk>class</jk>)</c>. 1345 * 1346 * @return This object. 1347 */ 1348 public RestRequest json5() { 1349 return serializer(Json5Serializer.class).parser(Json5Parser.class); 1350 } 1351 1352 /** 1353 * Convenience method for specifying JCS (JSON Canonicalization Scheme, RFC 8785) as the marshalling transmission media type for this request only. 1354 * 1355 * <p> 1356 * {@link JcsSerializer} will be used to serialize POJOs to request bodies. 1357 * {@link JsonParser} will be used to parse POJOs from response bodies (JCS output is valid JSON). 1358 * <p> 1359 * <c>Accept</c> and <c>Content-Type</c> will be set to <js>"application/jcs+json"</js>. 1360 * <p> 1361 * Identical to calling <c>serializer(JcsSerializer.<jk>class</jk>).parser(JsonParser.<jk>class</jk>)</c>. 1362 * 1363 * @return This object. 1364 */ 1365 public RestRequest jcs() { 1366 return serializer(JcsSerializer.class).parser(JsonParser.class); 1367 } 1368 1369 /** 1370 * Logs a message. 1371 * 1372 * @param level The log level. 1373 * <br>Cannot be <jk>null</jk>. 1374 * @param msg The message with {@link String#format(String, Object...) String.format}-style (<c>%s</c>) arguments. 1375 * <br>Cannot be <jk>null</jk>. 1376 * @param args The arguments. 1377 * <br>Can contain <jk>null</jk> values. 1378 * @return This object. 1379 */ 1380 public RestRequest log(Level level, String msg, Object...args) { 1381 client.log(level, msg, args); 1382 return this; 1383 } 1384 1385 /** 1386 * Logs a message. 1387 * 1388 * @param level The log level. 1389 * <br>Cannot be <jk>null</jk>. 1390 * @param t The throwable cause. 1391 * <br>Can be <jk>null</jk> (no exception details are logged). 1392 * @param msg The message with {@link String#format(String, Object...) String.format}-style (<c>%s</c>) arguments. 1393 * <br>Cannot be <jk>null</jk>. 1394 * @param args The arguments. 1395 * <br>Can contain <jk>null</jk> values. 1396 * @return This object. 1397 */ 1398 public RestRequest log(Level level, Throwable t, String msg, Object...args) { 1399 client.log(level, t, msg, args); 1400 return this; 1401 } 1402 1403 /** 1404 * Shortcut for setting the <c>Accept</c> and <c>Content-Type</c> headers on a request. 1405 * 1406 * @param value The new header values. 1407 * <br>Can be <jk>null</jk> (headers will not be set). 1408 * @return This object. 1409 * @throws RestCallException Invalid input. 1410 */ 1411 public RestRequest mediaType(String value) throws RestCallException { 1412 return header(Accept.of(value)).header(ContentType.of(value)); 1413 } 1414 1415 /** 1416 * Convenience method for specifying BSON as the marshalling transmission media type for this request only. 1417 * 1418 * <p> 1419 * Identical to calling <c>serializer(BsonSerializer.<jk>class</jk>).parser(BsonParser.<jk>class</jk>)</c>. 1420 * 1421 * @return This object. 1422 */ 1423 public RestRequest bson() { 1424 return serializer(BsonSerializer.class).parser(BsonParser.class); 1425 } 1426 1427 /** 1428 * Convenience method for specifying MessagePack as the marshalling transmission media type for this request only. 1429 * 1430 * <p> 1431 * MessagePack is a binary equivalent to JSON that takes up considerably less space than JSON. 1432 * 1433 * <p> 1434 * {@link MsgPackSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 1435 * <ul> 1436 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 1437 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1438 * </ul> 1439 * <p> 1440 * {@link MsgPackParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 1441 * <ul> 1442 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or 1443 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1444 * </ul> 1445 * <p> 1446 * <c>Accept</c> request header will be set to <js>"application/msgpack"</js> unless overridden 1447 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 1448 * <p> 1449 * <c>Content-Type</c> request header will be set to <js>"application/msgpack"</js> unless overridden 1450 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 1451 * <p> 1452 * Identical to calling <c>serializer(MsgPackSerializer.<jk>class</jk>).parser(MsgPackParser.<jk>class</jk>)</c>. 1453 * 1454 * @return This object. 1455 */ 1456 public RestRequest msgPack() { 1457 return serializer(MsgPackSerializer.class).parser(MsgPackParser.class); 1458 } 1459 1460 /** 1461 * When called, <c>No-Trace: true</c> is added to requests. 1462 * 1463 * <p> 1464 * This gives the opportunity for the servlet to not log errors on invalid requests. 1465 * This is useful for testing purposes when you don't want your log file to show lots of errors that are simply the 1466 * results of testing. 1467 * 1468 * @return This object. 1469 * @throws RestCallException Invalid input. 1470 */ 1471 public RestRequest noTrace() throws RestCallException { 1472 return header(NoTrace.TRUE); 1473 } 1474 1475 /** 1476 * Convenience method for specifying OpenAPI as the marshalling transmission media type for this request only. 1477 * 1478 * <p> 1479 * OpenAPI is a language that allows serialization to formats that use {@link HttpPartSchema} objects to describe their structure. 1480 * 1481 * <p> 1482 * {@link OpenApiSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 1483 * <ul> 1484 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 1485 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1486 * <li>Typically the {@link RestRequest#content(Object, HttpPartSchema)} method will be used to specify the body of the request with the 1487 * schema describing it's structure. 1488 * </ul> 1489 * <p> 1490 * {@link OpenApiParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 1491 * <ul> 1492 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or 1493 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1494 * <li>Typically the {@link ResponseContent#schema(HttpPartSchema)} method will be used to specify the structure of the response body. 1495 * </ul> 1496 * <p> 1497 * <c>Accept</c> request header will be set to <js>"text/openapi"</js> unless overridden 1498 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 1499 * <p> 1500 * <c>Content-Type</c> request header will be set to <js>"text/openapi"</js> unless overridden 1501 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 1502 * <p> 1503 * Identical to calling <c>serializer(OpenApiSerializer.<jk>class</jk>).parser(OpenApiParser.<jk>class</jk>)</c>. 1504 * 1505 * @return This object. 1506 */ 1507 public RestRequest openApi() { 1508 return serializer(OpenApiSerializer.class).parser(OpenApiParser.class); 1509 } 1510 1511 /** 1512 * Specifies the parser to use on the response body. 1513 * 1514 * <p> 1515 * Overrides the parsers specified on the {@link RestClient}. 1516 * 1517 * <p> 1518 * The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or 1519 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1520 * 1521 * <p> 1522 * If the <c>Accept</c> header is not set on the request, it will be set to the media type of this parser. 1523 * 1524 * @param parser The parser used to parse POJOs from the body of the HTTP response. 1525 * <br>Cannot be <jk>null</jk>. 1526 * @return This object. 1527 */ 1528 public RestRequest parser(Class<? extends Parser> parser) { 1529 this.parser = client.getInstance(reqnn("parser", parser)); 1530 return this; 1531 } 1532 1533 /** 1534 * Specifies the parser to use on the response body. 1535 * 1536 * <p> 1537 * Overrides the parsers specified on the {@link RestClient}. 1538 * 1539 * <p> 1540 * The parser is not modified by any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or 1541 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1542 * 1543 * <p> 1544 * If the <c>Accept</c> header is not set on the request, it will be set to the media type of this parser. 1545 * 1546 * @param parser The parser used to parse POJOs from the body of the HTTP response. 1547 * <br>Cannot be <jk>null</jk>. 1548 * @return This object. 1549 */ 1550 public RestRequest parser(Parser parser) { 1551 this.parser = reqnn("parser", parser); 1552 return this; 1553 } 1554 1555 /** 1556 * Sets or replaces multiple path parameters on the request. 1557 * 1558 * <h5 class='section'>Example:</h5> 1559 * <p class='bjava'> 1560 * <jc>// Appends two path parameters to the request.</jc> 1561 * <jv>client</jv> 1562 * .get(<jsf>URI</jsf>) 1563 * .pathData( 1564 * BasicStringPart.<jsm>of</jsm>(<js>"foo"</js>, <js>"bar"</js>), 1565 * BasicBooleanPart.<jsm>of</jsm>(<js>"baz"</js>, <jk>true</jk>) 1566 * ) 1567 * .run(); 1568 * </p> 1569 * 1570 * @param parts 1571 * The parameters to set. 1572 * <jk>null</jk> values are ignored. 1573 * @return This object. 1574 */ 1575 public RestRequest pathData(NameValuePair...parts) { 1576 pathData.set(parts); 1577 return this; 1578 } 1579 1580 /** 1581 * Sets or replaces a path parameter of the form <js>"{name}"</js> in the URI. 1582 * 1583 * <h5 class='section'>Example:</h5> 1584 * <p class='bjava'> 1585 * <jc>// Sets path to "/bar".</jc> 1586 * <jv>client</jv> 1587 * .get(<js>"/{foo}"</js>) 1588 * .pathData(<js>"foo"</js>, <js>"bar"</js>) 1589 * .run(); 1590 * </p> 1591 * 1592 * @param name 1593 * The parameter name. 1594 * <br>Cannot be <jk>null</jk> or empty (parameter will not be created). 1595 * @param value 1596 * The parameter value. 1597 * <br>Non-string values are converted to strings using the {@link HttpPartSerializer} defined on the client. 1598 * <br>Can be <jk>null</jk>. 1599 * @return This object. 1600 */ 1601 public RestRequest pathData(String name, Object value) { 1602 pathData.set(createPart(PATH, name, value)); 1603 return this; 1604 } 1605 1606 /** 1607 * Sets multiple path parameters to the request from properties defined on a Java bean. 1608 * 1609 * <h5 class='section'>Example:</h5> 1610 * <p class='bjava'> 1611 * <jk>public class</jk> MyPathVars { 1612 * <jk>public</jk> String getFooBar() { <jk>return</jk> <js>"baz"</js>; } 1613 * <jk>public</jk> Integer getQux() { <jk>return</jk> 123; } 1614 * } 1615 * 1616 * <jc>// Given path "/{fooBar}/{qux}/", gets converted to "/baz/123/".</jc> 1617 * <jv>client</jv> 1618 * .get(<jsf>URI</jsf>) 1619 * .pathDataBean(<jk>new</jk> MyPathVars()) 1620 * .run(); 1621 * </p> 1622 * 1623 * @param value The bean containing the properties to set as path parameter values. 1624 * <br>Cannot be <jk>null</jk>. 1625 * @return This object. 1626 */ 1627 public RestRequest pathDataBean(Object value) { 1628 req(isBean(reqnn("value", value)), "Object passed into pathDataBean(Object) is not a bean."); 1629 var b = pathData; 1630 toBeanMap(value).forEach((k, v) -> b.set(createPart(PATH, k, v))); 1631 return this; 1632 } 1633 1634 /** 1635 * Replaces path parameters of the form <js>"{name}"</js> in the URI using free-form key/value pairs. 1636 * 1637 * <h5 class='section'>Example:</h5> 1638 * <p class='bjava'> 1639 * <jc>// Sets path to "/baz/qux".</jc> 1640 * <jv>client</jv> 1641 * .get(<js>"/{foo}/{bar}"</js>) 1642 * .pathDataPairs( 1643 * <js>"foo"</js>,<js>"baz"</js>, 1644 * <js>"bar"</js>,<js>"qux"</js> 1645 * ) 1646 * .run(); 1647 * </p> 1648 * 1649 * @param pairs The path key/value pairs. 1650 * <ul> 1651 * <li>Values can be any POJO. 1652 * <li>Values converted to a string using the configured part serializer. 1653 * </ul> 1654 * <br>Cannot be <jk>null</jk>. 1655 * <br>Must have an even number of elements (key/value pairs). 1656 * <br>Null keys are ignored (parameter will not be created). 1657 * <br>Null values are allowed. 1658 * @return This object. 1659 */ 1660 public RestRequest pathDataPairs(String...pairs) { 1661 req(pairs.length % 2 == 0, "Odd number of parameters passed into pathDataPairs(String...)"); 1662 var b = pathData; 1663 for (var i = 0; i < pairs.length; i += 2) 1664 b.set(pairs[i], pairs[i + 1]); 1665 return this; 1666 } 1667 1668 /** 1669 * Convenience method for specifying Plain Text as the marshalling transmission media type for this request only. 1670 * 1671 * <p> 1672 * Plain text marshalling typically only works on simple POJOs that can be converted to and from strings using 1673 * swaps, swap methods, etc... 1674 * 1675 * <p> 1676 * {@link PlainTextSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 1677 * <ul> 1678 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 1679 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1680 * </ul> 1681 * <p> 1682 * {@link PlainTextParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 1683 * <ul> 1684 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or 1685 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 1686 * </ul> 1687 * <p> 1688 * <c>Accept</c> request header will be set to <js>"text/plain"</js> unless overridden 1689 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 1690 * <p> 1691 * <c>Content-Type</c> request header will be set to <js>"text/plain"</js> unless overridden 1692 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 1693 * <p> 1694 * Identical to calling <c>serializer(PlainTextSerializer.<jk>class</jk>).parser(PlainTextParser.<jk>class</jk>)</c>. 1695 * 1696 * @return This object. 1697 */ 1698 public RestRequest plainText() { 1699 return serializer(PlainTextSerializer.class).parser(PlainTextParser.class); 1700 } 1701 1702 /** 1703 * Sets the {@code X-Juneau-Serializer-Options} header for this request. 1704 * 1705 * <p> 1706 * The value is a JSON5 object whose keys are serializer session property names and values are the overrides. 1707 * Example: <js>"{escapeSolidus:true,maxIndent:4}"</js>. 1708 * 1709 * @param json5 1710 * Serializer session options as JSON5. Can be <jk>null</jk> (header is not set). 1711 * @return This object. 1712 */ 1713 public RestRequest serializerSessionOptionsHeader(String json5) { 1714 return header(HEADER_JuneauSerializerOptions, json5); 1715 } 1716 1717 /** 1718 * Sets the {@code X-Juneau-Parser-Options} header for this request. 1719 * 1720 * <p> 1721 * The value is a JSON5 object whose keys are parser session property names and values are the overrides. 1722 * 1723 * @param json5 1724 * Parser session options as JSON5. Can be <jk>null</jk> (header is not set). 1725 * @return This object. 1726 */ 1727 public RestRequest parserSessionOptionsHeader(String json5) { 1728 return header(HEADER_JuneauParserOptions, json5); 1729 } 1730 1731 /** 1732 * Convenience for {@link #serializerSessionOptionsHeader(String)} using a map serialized with {@link Json5#of(Object)}. 1733 * 1734 * @param properties 1735 * Property map for serializer session options. Can be <jk>null</jk> (treated as empty). 1736 * @return This object. 1737 * @throws SerializeException If serializing {@code properties} to JSON5 fails. 1738 */ 1739 public RestRequest serializerSessionOptionsHeader(Map<String,?> properties) { 1740 if (isEmpty(properties)) 1741 return this; 1742 return header(HEADER_JuneauSerializerOptions, Json5.of(properties)); 1743 } 1744 1745 /** 1746 * Convenience for {@link #parserSessionOptionsHeader(String)} using a map serialized with {@link Json5#of(Object)}. 1747 * 1748 * @param properties 1749 * Property map for parser session options. Can be <jk>null</jk> (treated as empty). 1750 * @return This object. 1751 * @throws SerializeException If serializing {@code properties} to JSON5 fails. 1752 */ 1753 public RestRequest parserSessionOptionsHeader(Map<String,?> properties) { 1754 if (isEmpty(properties)) 1755 return this; 1756 return header(HEADER_JuneauParserOptions, Json5.of(properties)); 1757 } 1758 1759 /** 1760 * Sets the {@code juneauSerializerOptions} query parameter for this request (UON-encoded map). 1761 * 1762 * <p> 1763 * Example: <js>"(escapeSolidus=true,maxIndent=4)"</js>. 1764 * 1765 * @param uon 1766 * Serializer session options in UON form. Can be <jk>null</jk> (parameter is not set). 1767 * @return This object. 1768 */ 1769 public RestRequest serializerSessionOptionsQuery(String uon) { 1770 return queryData(QUERY_juneauSerializerOptions, uon); 1771 } 1772 1773 /** 1774 * Sets the {@code juneauParserOptions} query parameter for this request (UON-encoded map). 1775 * 1776 * @param uon 1777 * Parser session options in UON form. Can be <jk>null</jk> (parameter is not set). 1778 * @return This object. 1779 */ 1780 public RestRequest parserSessionOptionsQuery(String uon) { 1781 return queryData(QUERY_juneauParserOptions, uon); 1782 } 1783 1784 /** 1785 * Convenience for {@link #serializerSessionOptionsQuery(String)} using {@link Uon#of(Object)}. 1786 * 1787 * @param properties 1788 * Property map for serializer session options. Can be <jk>null</jk> (treated as empty). 1789 * @return This object. 1790 * @throws SerializeException If serializing {@code properties} to UON fails. 1791 */ 1792 public RestRequest serializerSessionOptionsQuery(Map<String,?> properties) { 1793 if (isEmpty(properties)) 1794 return this; 1795 return serializerSessionOptionsQuery(Uon.of(properties)); 1796 } 1797 1798 /** 1799 * Convenience for {@link #parserSessionOptionsQuery(String)} using {@link Uon#of(Object)}. 1800 * 1801 * @param properties 1802 * Property map for parser session options. Can be <jk>null</jk> (treated as empty). 1803 * @return This object. 1804 * @throws SerializeException If serializing {@code properties} to UON fails. 1805 */ 1806 public RestRequest parserSessionOptionsQuery(Map<String,?> properties) { 1807 if (isEmpty(properties)) 1808 return this; 1809 return parserSessionOptionsQuery(Uon.of(properties)); 1810 } 1811 1812 /** 1813 * Sets the protocol version for this request. 1814 * 1815 * @param version The protocol version for this request. 1816 * <br>Can be <jk>null</jk> (default protocol version will be used). 1817 * @return This object. 1818 */ 1819 public RestRequest protocolVersion(ProtocolVersion version) { 1820 request.setProtocolVersion(version); 1821 return this; 1822 } 1823 1824 /** 1825 * Adds a free-form custom query. 1826 * 1827 * <h5 class='section'>Example:</h5> 1828 * <p class='bjava'> 1829 * <jc>// Adds query parameter "foo=bar&baz=qux".</jc> 1830 * <jv>client</jv> 1831 * .get(<jsf>URI</jsf>) 1832 * .queryCustom(<js>"foo=bar&baz=qux"</js>) 1833 * .run(); 1834 * </p> 1835 * 1836 * @param value The parameter value. 1837 * <br>Can be any of the following types: 1838 * <ul> 1839 * <li> 1840 * {@link CharSequence} 1841 * <li> 1842 * {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource. 1843 * <li> 1844 * {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource. 1845 * <li> 1846 * {@link PartList} - Converted to a URL-encoded query. 1847 * </ul> 1848 * <br>Can be <jk>null</jk> (no custom query will be set). 1849 * @return This object. 1850 */ 1851 public RestRequest queryCustom(Object value) { 1852 try { 1853 String q = null; 1854 if (value instanceof Reader value2) 1855 q = read(value2); 1856 else if (value instanceof InputStream value2) 1857 q = read(value2); 1858 else 1859 q = s(value); // Works for NameValuePairs. 1860 uriBuilder.setCustomQuery(q); 1861 } catch (IOException e) { 1862 throw rex(e, "Could not read custom query."); 1863 } 1864 return this; 1865 } 1866 1867 /** 1868 * Appends multiple query parameters to the request. 1869 * 1870 * <h5 class='section'>Example:</h5> 1871 * <p class='bjava'> 1872 * <jc>// Appends two query parameters to the request.</jc> 1873 * <jv>client</jv> 1874 * .get(<jsf>URI</jsf>) 1875 * .queryData( 1876 * BasicStringPart.<jsm>of</jsm>(<js>"foo"</js>, <js>"bar"</js>), 1877 * BasicBooleanPart.<jsm>of</jsm>(<js>"baz"</js>, <jk>true</jk>) 1878 * ) 1879 * .run(); 1880 * </p> 1881 * 1882 * @param parts 1883 * The parameters to set. 1884 * <jk>null</jk> values are ignored. 1885 * @return This object. 1886 */ 1887 public RestRequest queryData(NameValuePair...parts) { 1888 queryData.append(parts); 1889 return this; 1890 } 1891 1892 /** 1893 * Appends a query parameter to the URI of the request. 1894 * 1895 * <h5 class='section'>Example:</h5> 1896 * <p class='bjava'> 1897 * <jc>// Adds query parameter "foo=bar".</jc> 1898 * <jv>client</jv> 1899 * .get(<jsf>URI</jsf>) 1900 * .queryData(<js>"foo"</js>, <js>"bar"</js>) 1901 * .run(); 1902 * </p> 1903 * 1904 * @param name 1905 * The parameter name. 1906 * <br>Cannot be <jk>null</jk> or empty (parameter will not be created). 1907 * @param value 1908 * The parameter value. 1909 * <br>Non-string values are converted to strings using the {@link HttpPartSerializer} defined on the client. 1910 * <br>Can be <jk>null</jk>. 1911 * @return This object. 1912 */ 1913 public RestRequest queryData(String name, Object value) { 1914 queryData.append(createPart(QUERY, name, value)); 1915 return this; 1916 } 1917 1918 /** 1919 * Appends multiple query parameters to the request from properties defined on a Java bean. 1920 * 1921 * <h5 class='section'>Example:</h5> 1922 * <p class='bjava'> 1923 * <jk>public class</jk> MyQuery { 1924 * <jk>public</jk> String getFooBar() { <jk>return</jk> <js>"baz"</js>; } 1925 * <jk>public</jk> Integer getQux() { <jk>return</jk> 123; } 1926 * } 1927 * 1928 * <jc>// Appends query "fooBar=baz&qux=123".</jc> 1929 * <jv>client</jv> 1930 * .get(<jsf>URI</jsf>) 1931 * .queryDataBean(<jk>new</jk> MyQuery()) 1932 * .run(); 1933 * </p> 1934 * 1935 * @param value The bean containing the properties to set as query parameter values. 1936 * <br>Cannot be <jk>null</jk>. 1937 * @return This object. 1938 */ 1939 public RestRequest queryDataBean(Object value) { 1940 req(isBean(reqnn("value", value)), "Object passed into queryDataBean(Object) is not a bean."); 1941 var b = queryData; 1942 toBeanMap(value).forEach((k, v) -> b.append(createPart(QUERY, k, v))); 1943 return this; 1944 } 1945 1946 /** 1947 * Adds query parameters to the URI query using free-form key/value pairs.. 1948 * 1949 * <h5 class='section'>Example:</h5> 1950 * <p class='bjava'> 1951 * <jc>// Adds query parameters "foo=bar&baz=qux".</jc> 1952 * <jv>client</jv> 1953 * .get(<jsf>URI</jsf>) 1954 * .queryDataPairs(<js>"foo"</js>,<js>"bar"</js>,<js>"baz"</js>,<js>"qux"</js>) 1955 * .run(); 1956 * </p> 1957 * 1958 * @param pairs The query key/value pairs. 1959 * <ul> 1960 * <li>Values can be any POJO. 1961 * <li>Values converted to a string using the configured part serializer. 1962 * </ul> 1963 * <br>Cannot be <jk>null</jk>. 1964 * <br>Must have an even number of elements (key/value pairs). 1965 * <br>Null keys are ignored (parameter will not be created). 1966 * <br>Null values are allowed. 1967 * @return This object. 1968 * @throws RestCallException Invalid input. 1969 */ 1970 public RestRequest queryDataPairs(String...pairs) throws RestCallException { 1971 req(pairs.length % 2 == 0, "Odd number of parameters passed into queryDataPairs(String...)"); 1972 var b = queryData; 1973 for (var i = 0; i < pairs.length; i += 2) 1974 b.append(pairs[i], pairs[i + 1]); 1975 return this; 1976 } 1977 1978 /** 1979 * Removes a header from this message. 1980 * 1981 * @param header The header to remove. 1982 * <br>Can be <jk>null</jk> (no-op). 1983 */ 1984 @Override /* Overridden from HttpMessage */ 1985 public void removeHeader(Header header) { 1986 headerData.remove(header); 1987 } 1988 1989 /** 1990 * Removes all headers with a certain name from this message. 1991 * 1992 * @param name The name of the headers to remove. 1993 * <br>Cannot be <jk>null</jk>. 1994 */ 1995 @Override /* Overridden from HttpMessage */ 1996 public void removeHeaders(String name) { 1997 headerData.remove(name); 1998 } 1999 2000 /** 2001 * Rethrow any of the specified exception types if a matching <c>Exception-Name</c> header is found. 2002 * 2003 * <p> 2004 * Rethrown exceptions will be set on the caused-by exception of {@link RestCallException} when 2005 * thrown from the {@link #run()} method. 2006 * 2007 * <p> 2008 * Can be called multiple times to append multiple rethrows. 2009 * 2010 * @param values The classes to rethrow. 2011 * <br>Cannot be <jk>null</jk>. 2012 * <br>Null elements are ignored (only non-null classes that extend {@link Throwable} are added). 2013 * @return This object. 2014 */ 2015 public RestRequest rethrow(Class<?>...values) { 2016 if (rethrow == null) 2017 rethrow = list(); 2018 for (var v : values) { 2019 if (nn(v) && Throwable.class.isAssignableFrom(v)) 2020 rethrow.add((Class<? extends Throwable>)v); 2021 } 2022 return this; 2023 } 2024 2025 /** 2026 * Runs this request and returns the resulting response object. 2027 * 2028 * <h5 class='section'>Example:</h5> 2029 * <p class='bjava'> 2030 * <jk>try</jk> { 2031 * <jk>int</jk> <jv>rc</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).execute().getResponseStatus(); 2032 * <jc>// Succeeded!</jc> 2033 * } <jk>catch</jk> (RestCallException <jv>e</jv>) { 2034 * <jc>// Failed!</jc> 2035 * } 2036 * </p> 2037 * 2038 * <h5 class='section'>Notes:</h5><ul> 2039 * <li class='note'>Calling this method multiple times will return the same original response object. 2040 * <li class='note'>You must close the returned object if you do not consume the response or execute a method that consumes 2041 * the response. 2042 * <li class='note'>If you are only interested in the response code, use the {@link #complete()} method which will automatically 2043 * consume the response so that you don't need to call {@link InputStream#close()} on the response body. 2044 * </ul> 2045 * 2046 * @return The response object. 2047 * @throws RestCallException If an exception or non-200 response code occurred during the connection attempt. 2048 */ 2049 @SuppressWarnings({ 2050 "java:S3776", // Cognitive complexity acceptable for this specific logic 2051 "java:S6541", // Single-threaded context; synchronization unnecessary 2052 "null" // Null handling verified by context or framework 2053 }) 2054 public RestResponse run() throws RestCallException { 2055 if (nn(response)) 2056 throw new RestCallException(response, null, "run() already called."); 2057 2058 try { 2059 2060 queryData.stream().map(SimpleQuery::new).filter(SimplePart::isValid).forEach(x -> uriBuilder.addParameter(x.name, x.value)); 2061 2062 pathData.stream().map(SimplePath::new).forEach(x -> { 2063 var path = uriBuilder.getPath(); 2064 var name = x.name; 2065 var value = x.value; 2066 var pathVar = "{" + name + "}"; 2067 if (path.indexOf(pathVar) == -1 && neq(name, "/*")) 2068 throw isex("Path variable {%s} was not found in path.", name); 2069 if (eq(name, "/*")) 2070 path = path.replaceAll("\\/\\*$", URI_PATH_SEPARATOR + value); 2071 else 2072 path = path.replace(pathVar, String.valueOf(value)); 2073 uriBuilder.setPath(path); 2074 }); 2075 2076 HttpEntityEnclosingRequestBase request2 = request instanceof HttpEntityEnclosingRequestBase request3 ? request3 : null; 2077 request.setURI(uriBuilder.build()); 2078 2079 // Pick the serializer if it hasn't been overridden. 2080 var hl = headerData; 2081 var h = hl.getLast(HEADER_CONTENT_TYPE); 2082 var contentType = h.isPresent() ? h.get().getValue() : null; 2083 var serializer2 = this.serializer; 2084 if (serializer2 == null) 2085 serializer2 = client.getMatchingSerializer(contentType); 2086 if (contentType == null && nn(serializer2)) 2087 contentType = serializer2.getPrimaryMediaType().toString(); 2088 2089 // Pick the parser if it hasn't been overridden. 2090 h = hl.getLast(HEADER_ACCEPT); 2091 var accept = h.isPresent() ? h.get().getValue() : null; 2092 var parser2 = this.parser; 2093 if (parser2 == null) 2094 parser2 = client.getMatchingParser(accept); 2095 if (accept == null && nn(parser2)) 2096 hl.set(Accept.of(parser2.getPrimaryMediaType())); 2097 2098 headerData.stream().map(SimpleHeader::new).filter(SimplePart::isValid).forEach(request::addHeader); 2099 2100 if (request2 == null && content != NO_BODY) 2101 throw new RestCallException(null, null, "Method does not support content entity. Method=%s, URI=%s", getMethod(), getURI()); 2102 2103 if (nn(request2)) { 2104 2105 Object input2 = null; 2106 if (content != NO_BODY) { 2107 input2 = content; 2108 } else { 2109 input2 = new UrlEncodedFormEntity(formData.stream().map(SimpleFormData::new).filter(SimplePart::isValid).toList()); 2110 } 2111 2112 if (input2 instanceof Supplier<?> s) 2113 input2 = s.get(); 2114 2115 HttpEntity entity = null; 2116 if (input2 instanceof PartList input3) 2117 entity = new UrlEncodedFormEntity(input3.stream().map(SimpleFormData::new).filter(SimplePart::isValid).toList()); 2118 else if (input2 instanceof HttpResource input3) { 2119 input3.getHeaders().forEach(request::addHeader); 2120 entity = (HttpEntity)input2; 2121 } else if (input2 instanceof HttpEntity input3) { 2122 if (input3 instanceof SerializedEntity input4) { 2123 entity = input4.copyWith(serializer2, contentSchema); 2124 } else { 2125 entity = input3; 2126 } 2127 } else if (input2 instanceof Reader input3) 2128 entity = readerEntity(input3, getRequestContentType(TEXT_PLAIN)); 2129 else if (input2 instanceof InputStream input3) 2130 entity = streamEntity(input3, -1, getRequestContentType(ContentType.APPLICATION_OCTET_STREAM)); 2131 else if (input2 instanceof RecordStreamBody input3) 2132 entity = streamBodyEntity(input3, serializer2); 2133 else if (nn(serializer2)) 2134 entity = serializedEntity(input2, serializer2, contentSchema).setContentType(contentType); 2135 else { 2136 if (client.hasSerializers()) { 2137 if (contentType == null) 2138 throw new RestCallException(null, null, 2139 "Content-Type not specified on request. Cannot match correct serializer. Use contentType(String) or mediaType(String) to specify transport language."); 2140 throw new RestCallException(null, null, "No matching serializer for media type '%s'", contentType); 2141 } 2142 entity = stringEntity(input2 == null ? "" : MarshallingContext.DEFAULT.getClassMetaForObject(input2).toString(input2), getRequestContentType(TEXT_PLAIN)); 2143 } 2144 2145 request2.setEntity(entity); 2146 } 2147 2148 if (RemoteUrlPolicyState.isActive()) 2149 ssrfGuarded = true; 2150 var httpResponse = ssrfGuarded ? runWithPolicyRedirects() : client.run(target, request, context); 2151 response = client.createResponse(this, httpResponse, parser2); 2152 2153 if (isDebug() || client.logRequests == DetailLevel.FULL) 2154 response.cacheContent(); 2155 2156 for (var rci : interceptors) 2157 rci.onConnect(this, response); 2158 client.onCallConnect(this, response); 2159 2160 var method = getMethod(); 2161 var sc = response.getStatusCode(); 2162 2163 var thrown = response.getHeader("Thrown").asHeader(Thrown.class); 2164 if (thrown.isPresent() && nn(rethrow)) { 2165 var partsOpt = thrown.asParts(); 2166 if (!partsOpt.isPresent() || partsOpt.get().isEmpty()) 2167 return response; 2168 var thrownPart = partsOpt.get().get(0); 2169 var className = thrownPart.getClassName(); 2170 var message = thrownPart.getMessage(); 2171 for (var t : rethrow) { 2172 if (t.getName().equals(className)) { 2173 ConstructorInfo c = null; 2174 var ci = ClassInfo.of(t); 2175 Throwable thrownInstance = null; 2176 c = ci.getPublicConstructor(x -> x.hasParameterTypes(HttpResponse.class)).orElse(null); 2177 if (nn(c)) 2178 thrownInstance = newThrownFromResponse(c); 2179 if (thrownInstance == null) { 2180 c = ci.getPublicConstructor(x -> x.hasParameterTypes(String.class)).orElse(null); 2181 if (nn(c)) { 2182 thrownInstance = c.<Throwable>newInstance(nn(message) ? message : response.getContent().asString()); 2183 } else { 2184 c = ci.getPublicConstructor(x -> x.hasParameterTypes(String.class, Throwable.class)).orElse(null); 2185 if (nn(c)) { 2186 thrownInstance = c.<Throwable>newInstance(nn(message) ? message : response.getContent().asString(), null); 2187 } else { 2188 c = ci.getPublicConstructor(cons -> cons.getParameterCount() == 0).orElse(null); 2189 if (nn(c)) { 2190 thrownInstance = c.<Throwable>newInstance(); 2191 } 2192 } 2193 } 2194 } 2195 if (nn(thrownInstance)) { 2196 if (thrownInstance instanceof Exception thrownInstance2) 2197 throw thrownInstance2; 2198 throw new RestCallException(response, thrownInstance, "Server threw non-Exception: %s", className); 2199 } 2200 } 2201 } 2202 } 2203 2204 if (errorCodes.test(sc) && ! ignoreErrors) { 2205 // Surface the server-reported exception detail from the Thrown header (authoritative and independent of the response body) so callers still see the original failure detail. 2206 throw new RestCallException(response, null, "HTTP method '%s' call to '%s' caused response code '%s, %s'.\nResponse: \n%s%s", method, getURI(), sc, response.getReasonPhrase(), 2207 response.getContent().asAbbreviatedString(1000), getThrownDetailSuffix(thrown)); 2208 } 2209 2210 } catch (RuntimeException | RestCallException e) { 2211 if (nn(response)) 2212 response.close(); 2213 throw e; 2214 } catch (Exception e) { 2215 if (nn(response)) 2216 response.close(); 2217 throw new RestCallException(response, e, "Call failed."); 2218 } 2219 2220 return this.response; 2221 } 2222 2223 // ----------------------------------------------------------------------------------------------------------------- 2224 // SSRF-guard redirect revalidation (active only for a guarded request -- see SsrfGuardMarker) 2225 // ----------------------------------------------------------------------------------------------------------------- 2226 2227 // Marks this as the request of an SSRF-guard-active @Remote call. 2228 void ssrfGuard() { 2229 ssrfGuarded = true; 2230 } 2231 2232 // Whether this is the request of an SSRF-guard-active @Remote call. 2233 boolean isSsrfGuarded() { return ssrfGuarded; } 2234 2235 /** 2236 * Runs this request with Apache HttpClient's own automatic redirect-following disabled (see 2237 * {@code PolicyAwareRedirectStrategy}) and instead follows {@code 3xx} hops in a Juneau-controlled loop that 2238 * re-validates the {@code Location} of every hop against {@link RemoteUrlPolicy} — and, since every hop is 2239 * sent with the {@code SsrfGuardMarker} on its context, every hop leases its connection from the client's dedicated 2240 * pinned pool ({@code SsrfGuardConnectionManager}), which pin-on-connects each hop's resolved address and never hands 2241 * out a connection an ordinary request opened, on whichever thread the client runs it. Only called for a guarded 2242 * request, i.e. for a {@code @Remote} call with the SSRF guard in effect. 2243 */ 2244 private HttpResponse runWithPolicyRedirects() throws IOException { 2245 // A context set with context(...) carries the marker, and the redirect credential guard's origin, for the call only. 2246 var previousMarker = nn(context) ? SsrfGuardMarker.mark(context) : null; 2247 var previousOrigin = nn(context) ? ClassicRedirectCredentialGuard.origin(context) : null; 2248 try { 2249 return runWithPolicyRedirects2(); 2250 } finally { 2251 if (nn(context)) { 2252 context.setAttribute(SsrfGuardMarker.ATTRIBUTE, previousMarker); 2253 ClassicRedirectCredentialGuard.origin(context, previousOrigin); 2254 } 2255 } 2256 } 2257 2258 private HttpResponse runWithPolicyRedirects2() throws IOException { 2259 var currentTarget = target; 2260 HttpRequestBase currentRequest = request; 2261 URI firstOrigin = null; 2262 for (var hop = 0; hop < RemoteUrlPolicy.MAX_REDIRECT_HOPS; hop++) { 2263 // Hop 0's URI was already validated (or is the operator-configured client root, which the resolution 2264 // path intentionally does not re-check -- see RemoteUrlPolicy.requireAllowedUrl) before this request 2265 // was built; only a redirect Location -- a URL discovered at runtime -- needs a fresh pre-check here. 2266 if (hop > 0) 2267 requirePolicyAllowed(currentRequest.getURI()); 2268 // Without a context(...) setting each hop gets a fresh context, as Apache HttpClient gives one to a request 2269 // sent with none: sharing one would carry the authentication state of a hop to the next host. 2270 var hopContext = context; 2271 if (hopContext == null) { 2272 hopContext = new BasicHttpContext(); 2273 SsrfGuardMarker.mark(hopContext); 2274 } 2275 // The redirect credential guard compares every hop with the first hop's origin, as it does on the one context 2276 // of a redirect Apache HttpClient follows itself, and strips what the client's interceptors and default headers 2277 // add on another origin. On hop 0 this clears an origin an earlier call left on a context(...) setting. 2278 ClassicRedirectCredentialGuard.origin(hopContext, firstOrigin); 2279 var httpResponse = client.run(currentTarget, currentRequest, hopContext); 2280 if (hop == 0) 2281 firstOrigin = ClassicRedirectCredentialGuard.origin(hopContext); 2282 var sc = httpResponse.getStatusLine().getStatusCode(); 2283 if (! isRedirectStatus(sc)) 2284 return httpResponse; 2285 var locationHeader = httpResponse.getFirstHeader("Location"); 2286 EntityUtils.consumeQuietly(httpResponse.getEntity()); 2287 if (locationHeader == null) 2288 throw new ClientProtocolException("Redirect response (status " + sc + ") is missing a Location header"); 2289 URI target2; 2290 try { 2291 target2 = RemoteUrlPolicy.resolveRedirectLocation(currentRequest.getURI(), locationHeader.getValue()); 2292 } catch (IllegalArgumentException e) { 2293 throw new ClientProtocolException(e.getMessage(), e); 2294 } 2295 // On a context(...) setting shared by all hops, the authentication state of the target is reset on a hop to 2296 // another origin, as Apache HttpClient's own redirect handling resets it on a change of host: otherwise the 2297 // credentials that answered one host's challenge are sent unasked to the next. The credentials provider is 2298 // still asked per AuthScope, and the auth cache is keyed by host, so neither carries them across. 2299 if (nn(context) && crossesOrigin(currentRequest.getURI(), target2)) 2300 resetTargetAuthState(context); 2301 currentRequest = buildRedirectRequest(currentRequest, target2, sc); 2302 currentTarget = null; // Recomputed by the connection route planner from the request's own absolute URI. 2303 } 2304 throw new ClientProtocolException("Redirect limit exceeded (" + RemoteUrlPolicy.MAX_REDIRECT_HOPS 2305 + " hops) while following a policy-covered @Remote redirect chain, starting at: " + request.getURI()); 2306 } 2307 2308 private void requirePolicyAllowed(URI uri) throws ClientProtocolException { 2309 try { 2310 RemoteUrlPolicy.requireAllowedUrl(uri.toString(), false, client.getAllowedAddressRanges()); 2311 } catch (IllegalArgumentException e) { 2312 throw new ClientProtocolException(e.getMessage(), e); 2313 } 2314 } 2315 2316 private static boolean isRedirectStatus(int statusCode) { 2317 return statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 307 || statusCode == 308; 2318 } 2319 2320 /** 2321 * Constructs the server-thrown exception through its {@code (HttpResponse)} constructor. 2322 * 2323 * @param c The constructor. 2324 * @return The instance, or <jk>null</jk> if the constructor rejected the response's status code. 2325 */ 2326 private Throwable newThrownFromResponse(ConstructorInfo c) { 2327 try { 2328 return c.<Throwable>newInstance(response); 2329 } catch (ExecutableException e) { 2330 // Only a status-code rejection (IllegalArgumentException from the Xxx(HttpResponse) status check) falls through to the remaining constructors; anything else propagates unchanged. 2331 if (! (e.getCause() instanceof IllegalArgumentException)) 2332 throw e; 2333 return null; 2334 } 2335 } 2336 2337 /** 2338 * Builds the request for the next hop of a policy-covered redirect chain, following conventional {@code 3xx} 2339 * semantics: {@code 301}/{@code 302}/{@code 303} rewrite the method to {@code GET} with no body unless the 2340 * original method was already {@code GET}/{@code HEAD}; {@code 307}/{@code 308} preserve the original method 2341 * and entity. A non-{@linkplain HttpEntity#isRepeatable() repeatable} entity cannot be safely re-sent, so such a 2342 * redirect is refused (fail closed) rather than risking a corrupt or partial replay. 2343 * 2344 * <p> 2345 * A hop to another origin (or an {@code https}→{@code http} downgrade) drops the caller-set credential headers 2346 * ({@link RedirectSecurity#stripOnCrossOrigin()}), as {@code ClassicRedirectCredentialGuard} does for a redirect 2347 * Apache HttpClient follows itself: that guard keys the first origin on the context, and here each hop may have a 2348 * context of its own. Once dropped they stay dropped, even if a later hop comes back to the first origin. 2349 */ 2350 private HttpRequestBase buildRedirectRequest(HttpRequestBase current, URI target2, int statusCode) throws ClientProtocolException { 2351 var method = current.getMethod(); 2352 var preserveMethod = eqic(method, "GET") || eqic(method, "HEAD"); 2353 var rewriteToGet = ! preserveMethod && (statusCode == 301 || statusCode == 302 || statusCode == 303); 2354 var entity = (! rewriteToGet && current instanceof HttpEntityEnclosingRequestBase e) ? e.getEntity() : null; 2355 2356 HttpRequestBase next; 2357 if (entity != null) { 2358 if (! entity.isRepeatable()) 2359 throw new ClientProtocolException("Redirect (status " + statusCode + ") requires re-sending a " 2360 + "non-repeatable request body, which cannot be safely replayed; refusing to follow (fail " 2361 + "closed) for a policy-covered @Remote redirect chain: " + target2); 2362 var next2 = new BasicHttpEntityRequestBase(this, method); 2363 next2.setEntity(entity); 2364 next = next2; 2365 } else { 2366 next = new BasicHttpRequestBase(this, rewriteToGet ? "GET" : method); 2367 } 2368 next.setURI(target2); 2369 var strip = crossesOrigin(current.getURI(), target2); 2370 for (var h : current.getAllHeaders()) 2371 if (! (rewriteToGet && (eqic(h.getName(), "Content-Length") || eqic(h.getName(), HEADER_CONTENT_TYPE))) && ! (strip && isCredentialHeader(h.getName()))) 2372 next.addHeader(h); 2373 return next; 2374 } 2375 2376 // Whether a hop from one URI to the other leaves the origin, or cannot be told not to (either is not absolute). 2377 private static boolean crossesOrigin(URI from, URI to) { 2378 return from.getScheme() == null || from.getHost() == null || to.getScheme() == null || to.getHost() == null 2379 || RedirectSecurity.shouldStripCredentials(from, to); 2380 } 2381 2382 // The shared RedirectSecurity set, plus Proxy-Authorization whatever that set holds: a guarded call never goes 2383 // through a proxy (one is refused), so on any hop it could only be a credential sent to the target itself. 2384 // Defense in depth: the shared set holds Proxy-Authorization today, so no test can tell this pin from it. 2385 private static boolean isCredentialHeader(String name) { 2386 return eqic(name, "Proxy-Authorization") || RedirectSecurity.stripOnCrossOrigin().stream().anyMatch(x -> eqic(x, name)); 2387 } 2388 2389 private static void resetTargetAuthState(HttpContext context) { 2390 var state = HttpClientContext.adapt(context).getTargetAuthState(); 2391 if (nn(state)) 2392 state.reset(); 2393 } 2394 2395 /** 2396 * Returns a message suffix describing the first entry of the given {@link Thrown} header, if present and 2397 * non-blank, or an empty string otherwise. 2398 * 2399 * @param thrown The parsed {@code Thrown} response header. 2400 * @return A <js>"\nThrown: <className>: <message>"</js> suffix, or an empty string. 2401 */ 2402 private static String getThrownDetailSuffix(Thrown thrown) { 2403 if (thrown.isPresent()) { 2404 var partsOpt = thrown.asParts(); 2405 if (partsOpt.isPresent() && ! partsOpt.get().isEmpty()) { 2406 var part = partsOpt.get().get(0); 2407 if (inb(part.getClassName()) && inb(part.getMessage())) 2408 return "\nThrown: " + part.getClassName() + ": " + part.getMessage(); 2409 } 2410 } 2411 return ""; 2412 } 2413 2414 /** 2415 * Same as {@link #run()} but allows you to run the call asynchronously. 2416 * 2417 * <h5 class='section'>Example:</h5> 2418 * <p class='bjava'> 2419 * Future<RestResponse> <jv>future</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).runFuture(); 2420 * 2421 * <jc>// Do some other stuff</jc> 2422 * 2423 * <jk>try</jk> { 2424 * String <jv>body</jv> = <jv>future</jv>.get().getContent().asString(); 2425 * <jc>// Succeeded!</jc> 2426 * } <jk>catch</jk> (RestCallException <jv>e</jv>) { 2427 * <jc>// Failed!</jc> 2428 * } 2429 * </p> 2430 * 2431 * <h5 class='section'>Notes:</h5><ul> 2432 * <li class='note'>Use the {@link RestClient.Builder#executorService(ExecutorService, boolean)} method to customize the 2433 * executor service used for creating {@link Future Futures}. 2434 * </ul> 2435 * 2436 * @return The HTTP status code. 2437 * @throws RestCallException If the executor service was not defined. 2438 */ 2439 public Future<RestResponse> runFuture() throws RestCallException { 2440 return client.getExecutorService().submit(this::run); 2441 } 2442 2443 /** 2444 * Specifies the serializer to use on the request body. 2445 * 2446 * <p> 2447 * Overrides the serializers specified on the {@link RestClient}. 2448 * 2449 * <p> 2450 * The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 2451 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 2452 * 2453 * <p> 2454 * If the <c>Content-Type</c> header is not set on the request, it will be set to the media type of this serializer. 2455 * 2456 * @param serializer The serializer used to serialize POJOs to the body of the HTTP request. 2457 * <br>Can be <jk>null</jk> (will use default serializer matching Content-Type). 2458 * @return This object. 2459 */ 2460 public RestRequest serializer(Class<? extends Serializer> serializer) { 2461 this.serializer = serializer == null ? null : client.getInstance(serializer); 2462 return this; 2463 } 2464 2465 /** 2466 * Specifies the serializer to use on the request body. 2467 * 2468 * <p> 2469 * Overrides the serializers specified on the {@link RestClient}. 2470 * 2471 * <p> 2472 * The serializer is not modified by an of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 2473 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 2474 * 2475 * <p> 2476 * If the <c>Content-Type</c> header is not set on the request, it will be set to the media type of this serializer. 2477 * 2478 * @param serializer The serializer used to serialize POJOs to the body of the HTTP request. 2479 * <br>Can be <jk>null</jk> (will use default serializer matching Content-Type). 2480 * @return This object. 2481 */ 2482 public RestRequest serializer(Serializer serializer) { 2483 this.serializer = serializer; 2484 return this; 2485 } 2486 2487 /** 2488 * Overwrites the first header with the same name. 2489 * 2490 * The new header will be appended to the end of the list, if no header with the given name can be found. 2491 * 2492 * @param header The header to set. 2493 * <br>Can be <jk>null</jk> (ignored). 2494 */ 2495 @Override /* Overridden from HttpMessage */ 2496 public void setHeader(Header header) { 2497 headerData.set(header); 2498 } 2499 2500 /** 2501 * Overwrites the first header with the same name. 2502 * 2503 * The new header will be appended to the end of the list, if no header with the given name can be found. 2504 * 2505 * @param name The name of the header. 2506 * <br>Cannot be <jk>null</jk> or empty (header will not be created). 2507 * @param value The value of the header. 2508 * <br>Can be <jk>null</jk>. 2509 */ 2510 @Override /* Overridden from HttpMessage */ 2511 public void setHeader(String name, String value) { 2512 headerData.set(stringHeader(name, value)); 2513 } 2514 2515 /** 2516 * Overwrites all the headers in the message. 2517 * 2518 * @param headers The array of headers to set. 2519 * <br>Can be <jk>null</jk> (all headers will be cleared). 2520 * <br>Can contain <jk>null</jk> values (ignored). 2521 */ 2522 @Override /* Overridden from HttpMessage */ 2523 public void setHeaders(Header[] headers) { 2524 headerData.set(headers); 2525 } 2526 2527 /** 2528 * Provides parameters to be used for the processing of this message. 2529 * 2530 * @param params The parameters. 2531 * <br>Can be <jk>null</jk> (default parameters will be used). 2532 * @deprecated Use constructor parameters of configuration API provided by HttpClient. 2533 */ 2534 @Override /* Overridden from HttpMessage */ 2535 @Deprecated(since = "10.0", forRemoval = true) 2536 public void setParams(HttpParams params) { 2537 request.setParams(params); 2538 } 2539 2540 /** 2541 * Causes logging to be suppressed for the duration of this request. 2542 * 2543 * <p> 2544 * Overrides the {@link #debug()} setting on this request or client. 2545 * 2546 * @return This object. 2547 */ 2548 public RestRequest suppressLogging() { 2549 this.suppressLogging = true; 2550 return this; 2551 } 2552 2553 /** 2554 * Specifies the target host for the request. 2555 * 2556 * @param target The target host for the request. 2557 * Implementations may accept <jk>null</jk> if they can still determine a route, for example to a default 2558 * target or by inspecting the request. 2559 * @return This object. 2560 */ 2561 public RestRequest target(HttpHost target) { 2562 this.target = target; 2563 return this; 2564 } 2565 2566 /** 2567 * Convenience method for specifying UON as the marshalling transmission media type for this request only. 2568 * 2569 * <p> 2570 * UON is Url-Encoding Object notation that is equivalent to JSON but suitable for transmission as URL-encoded 2571 * query and form post values. 2572 * 2573 * <p> 2574 * {@link UonSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 2575 * <ul> 2576 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 2577 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 2578 * </ul> 2579 * <p> 2580 * {@link UonParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 2581 * <ul> 2582 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or 2583 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 2584 * </ul> 2585 * <p> 2586 * <c>Accept</c> request header will be set to <js>"text/uon"</js> unless overridden 2587 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 2588 * <p> 2589 * <c>Content-Type</c> request header will be set to <js>"text/uon"</js> unless overridden 2590 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 2591 * <p> 2592 * Identical to calling <c>serializer(UonSerializer.<jk>class</jk>).parser(UonParser.<jk>class</jk>)</c>. 2593 * 2594 * @return This object. 2595 */ 2596 public RestRequest uon() { 2597 return serializer(UonSerializer.class).parser(UonParser.class); 2598 } 2599 2600 /** 2601 * Sets the URI for this request. 2602 * 2603 * <p> 2604 * Can be any of the following types: 2605 * <ul> 2606 * <li>{@link URI} 2607 * <li>{@link URL} 2608 * <li>{@link URIBuilder} 2609 * <li>Anything else converted to a string using {@link Object#toString()}. 2610 * </ul> 2611 * 2612 * <p> 2613 * Relative URI strings will be interpreted as relative to the root URI defined on the client. 2614 * 2615 * @param uri 2616 * The URI of the remote REST resource. 2617 * <br>This overrides the URI passed in from the client. 2618 * <br>Can be any of the following types: 2619 * <ul> 2620 * <li>{@link URIBuilder} 2621 * <li>{@link URI} 2622 * <li>{@link URL} 2623 * <li>{@link String} 2624 * <li>{@link Object} - Converted to <c>String</c> using <c>toString()</c> 2625 * </ul> 2626 * <br>Cannot be <jk>null</jk>. 2627 * @return This object. 2628 * @throws RestCallException Invalid URI syntax detected. 2629 */ 2630 public RestRequest uri(Object uri) throws RestCallException { 2631 var x = client.toUri(uri, null); 2632 if (nn(x.getScheme())) 2633 uriBuilder.setScheme(x.getScheme()); 2634 if (nn(x.getHost())) 2635 uriBuilder.setHost(x.getHost()); 2636 if (x.getPort() != -1) 2637 uriBuilder.setPort(x.getPort()); 2638 if (nn(x.getUserInfo())) 2639 uriBuilder.setUserInfo(x.getUserInfo()); 2640 if (nn(x.getFragment())) 2641 uriBuilder.setFragment(x.getFragment()); 2642 if (nn(x.getQuery())) 2643 uriBuilder.setCustomQuery(x.getQuery()); 2644 uriBuilder.setPath(x.getPath()); 2645 return this; 2646 } 2647 2648 /** 2649 * Sets the URI fragment. 2650 * 2651 * @param fragment The URI fragment. The value is expected to be unescaped and may contain non ASCII characters. 2652 * <br>Can be <jk>null</jk> (fragment will be cleared). 2653 * @return This object. 2654 */ 2655 public RestRequest uriFragment(String fragment) { 2656 uriBuilder.setFragment(fragment); 2657 return this; 2658 } 2659 2660 /** 2661 * Sets the URI host. 2662 * 2663 * @param host The new URI host. 2664 * <br>Can be <jk>null</jk> (host will be cleared). 2665 * @return This object. 2666 */ 2667 public RestRequest uriHost(String host) { 2668 uriBuilder.setHost(host); 2669 return this; 2670 } 2671 2672 /** 2673 * Sets the URI port. 2674 * 2675 * @param port The new URI port. 2676 * @return This object. 2677 */ 2678 public RestRequest uriPort(int port) { 2679 uriBuilder.setPort(port); 2680 return this; 2681 } 2682 2683 /** 2684 * Sets the URI scheme. 2685 * 2686 * @param scheme The new URI scheme. 2687 * <br>Can be <jk>null</jk> (scheme will be cleared). 2688 * @return This object. 2689 */ 2690 public RestRequest uriScheme(String scheme) { 2691 uriBuilder.setScheme(scheme); 2692 return this; 2693 } 2694 2695 /** 2696 * Sets the URI user info. 2697 * 2698 * @param userInfo The new URI user info. 2699 * <br>Can be <jk>null</jk> (user info will be cleared). 2700 * @return This object. 2701 */ 2702 public RestRequest uriUserInfo(String userInfo) { 2703 uriBuilder.setUserInfo(userInfo); 2704 return this; 2705 } 2706 2707 /** 2708 * Sets the URI user info. 2709 * 2710 * @param username The new URI username. 2711 * <br>Can be <jk>null</jk>. 2712 * @param password The new URI password. 2713 * <br>Can be <jk>null</jk>. 2714 * @return This object. 2715 */ 2716 public RestRequest uriUserInfo(String username, String password) { 2717 uriBuilder.setUserInfo(username, password); 2718 return this; 2719 } 2720 2721 /** 2722 * Convenience method for specifying URL-Encoding as the marshalling transmission media type for this request only. 2723 * 2724 * <p> 2725 * {@link UrlEncodingSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 2726 * <ul> 2727 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 2728 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 2729 * <li>This serializer is NOT used when using the {@link RestRequest#formData(String, Object)} (and related) methods for constructing 2730 * the request body. Instead, the part serializer specified via {@link RestClient.Builder#partSerializer(Class)} is used. 2731 * </ul> 2732 * <p> 2733 * {@link UrlEncodingParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 2734 * <ul> 2735 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or 2736 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 2737 * </ul> 2738 * <p> 2739 * <c>Accept</c> request header will be set to <js>"application/x-www-form-urlencoded"</js> unless overridden 2740 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 2741 * <p> 2742 * <c>Content-Type</c> request header will be set to <js>"application/x-www-form-urlencoded"</js> unless overridden 2743 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 2744 * <p> 2745 * Identical to calling <c>serializer(UrlEncodingSerializer.<jk>class</jk>).parser(UrlEncodingParser.<jk>class</jk>)</c>. 2746 * 2747 * @return This object. 2748 */ 2749 public RestRequest urlEnc() { 2750 return serializer(UrlEncodingSerializer.class).parser(UrlEncodingParser.class); 2751 } 2752 2753 /** 2754 * Convenience method for specifying XML as the marshalling transmission media type for this request only. 2755 * 2756 * <p> 2757 * {@link XmlSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}. 2758 * <ul> 2759 * <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClient.Builder#sortCollections()}) or 2760 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 2761 * </ul> 2762 * <p> 2763 * {@link XmlParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}. 2764 * <ul> 2765 * <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClient.Builder#ignoreUnknownBeanProperties()}) or 2766 * bean context property setters (e.g. {@link RestClient.Builder#swaps(Class...)}) defined on this builder class. 2767 * </ul> 2768 * <p> 2769 * <c>Accept</c> request header will be set to <js>"text/xml"</js> unless overridden 2770 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(String)}. 2771 * <p> 2772 * <c>Content-Type</c> request header will be set to <js>"text/xml"</js> unless overridden 2773 * by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(String)}. 2774 * <p> 2775 * Identical to calling <c>serializer(XmlSerializer.<jk>class</jk>).parser(XmlParser.<jk>class</jk>)</c>. 2776 * 2777 * @return This object. 2778 */ 2779 public RestRequest xml() { 2780 return serializer(XmlSerializer.class).parser(XmlParser.class); 2781 } 2782 2783 private Header createHeader(String name, Object value) { 2784 return createHeader(name, value, null, null, null); 2785 } 2786 2787 private NameValuePair createPart(HttpPartType type, String name, Object value) { 2788 return createPart(name, value, type, null, null, null); 2789 } 2790 2791 private HttpPartSerializerSession getPartSerializerSession(HttpPartSerializer serializer) { 2792 if (serializer == null) 2793 serializer = client.getPartSerializer(); 2794 return partSerializerSessions.computeIfAbsent(serializer, HttpPartSerializer::getPartSession); 2795 } 2796 2797 private ContentType getRequestContentType(ContentType def) { 2798 var h = request.getFirstHeader(HEADER_CONTENT_TYPE); 2799 if (nn(h)) { 2800 var s = h.getValue(); 2801 if (ine(s)) 2802 return ContentType.of(s); 2803 } 2804 return def; 2805 } 2806 2807 /** 2808 * Creates a new header. 2809 * 2810 * @param name The header name. 2811 * @param value The header value. 2812 * @param serializer The part serializer to use, or <jk>null</jk> to use the part serializer defined on the client. 2813 * @param schema Optional HTTP part schema to provide to the part serializer. 2814 * @param skipIfEmpty If <jk>true</jk>, empty string values will be ignored on the request. 2815 * @return A new header. 2816 */ 2817 protected Header createHeader(String name, Object value, HttpPartSerializer serializer, HttpPartSchema schema, Boolean skipIfEmpty) { 2818 if (ie(name)) 2819 return null; 2820 if (skipIfEmpty == null) 2821 skipIfEmpty = client.isSkipEmptyHeaderData(); 2822 if (serializer == null) 2823 serializer = client.getPartSerializer(); 2824 return new SerializedHeader(name, value, getPartSerializerSession(serializer), schema, skipIfEmpty); 2825 } 2826 2827 /** 2828 * Constructs the {@link HttpRequestBase} object that ends up being passed to the client execute method. 2829 * 2830 * <p> 2831 * Subclasses can override this method to create their own request base objects. 2832 * 2833 * @param method The HTTP method. 2834 * @param uri The HTTP URI. 2835 * @param hasBody Whether the HTTP request has a body. 2836 * @return A new {@link HttpRequestBase} object. 2837 */ 2838 protected HttpRequestBase createInnerRequest(String method, URI uri, boolean hasBody) { 2839 var req = hasBody ? new BasicHttpEntityRequestBase(this, method) : new BasicHttpRequestBase(this, method); 2840 req.setURI(uri); 2841 return req; 2842 } 2843 2844 /** 2845 * Creates a new query/form-data/path part. 2846 * 2847 * @param name The part name. 2848 * @param value The part value. 2849 * @param type The HTTP part type. 2850 * @param serializer The part serializer to use, or <jk>null</jk> to use the part serializer defined on the client. 2851 * @param schema Optional HTTP part schema to provide to the part serializer. 2852 * @param skipIfEmpty If <jk>true</jk>, empty string values will be ignored on the request. 2853 * @return A new part. 2854 */ 2855 protected NameValuePair createPart(String name, Object value, HttpPartType type, HttpPartSerializer serializer, HttpPartSchema schema, Boolean skipIfEmpty) { 2856 if (ie(name)) 2857 return null; 2858 if (skipIfEmpty == null) { 2859 if (type == QUERY) 2860 skipIfEmpty = client.isSkipEmptyQueryData(); 2861 else if (type == FORMDATA) 2862 skipIfEmpty = client.isSkipEmptyFormData(); 2863 else 2864 skipIfEmpty = false; 2865 } 2866 if (serializer == null) 2867 serializer = client.getPartSerializer(); 2868 return new SerializedPart(name, value, type, getPartSerializerSession(serializer), schema, skipIfEmpty); 2869 } 2870 2871 @Override /* Overridden from MarshallingSession */ 2872 protected FluentMap<String,Object> properties() { 2873 return super.properties() 2874 .a("client", client.properties()) 2875 .a("ignoreErrors", ignoreErrors) 2876 .a("interceptors", interceptors) 2877 .a("requestBodySchema", contentSchema) 2878 .a("response", response) 2879 .a("serializer", serializer); 2880 } 2881 2882 RestRequest formDataArg(String name, Object value, HttpPartSchema schema, HttpPartSerializer serializer, boolean skipIfEmpty) { 2883 var isMulti = ie(name) || eq(name, "*") || value instanceof PartList || isNameValuePairArray(value); 2884 2885 if (! isMulti) { 2886 if (! (skipIfEmpty && ie(s(value)))) 2887 return formData(createPart(name, value, FORMDATA, serializer, schema, skipIfEmpty)); 2888 return this; 2889 } 2890 2891 List<NameValuePair> l = list(); 2892 2893 if (HttpParts.canCast(value)) { 2894 l.add(HttpParts.cast(value)); 2895 } else if (value instanceof PartList value2) { 2896 value2.forEach(l::add); 2897 } else if (value instanceof Collection<?> value3) { 2898 value3.forEach(x -> l.add(HttpParts.cast(x))); 2899 } else if (isArray(value)) { 2900 for (var i = 0; i < Array.getLength(value); i++) 2901 l.add(HttpParts.cast(Array.get(value, i))); 2902 } else if (value instanceof Map value2) { 2903 toMap(value2).forEach((k, v) -> l.add(createPart(s(k), v, FORMDATA, serializer, schema, skipIfEmpty))); 2904 } else if (isBean(value)) { 2905 toBeanMap(value).forEach((k, v) -> l.add(createPart(k, v, FORMDATA, serializer, schema, skipIfEmpty))); 2906 } else if (nn(value)) { 2907 formDataCustom(value); 2908 return this; 2909 } 2910 2911 if (skipIfEmpty) 2912 l.removeIf(x -> ie(x.getValue())); 2913 2914 formData.append(l); 2915 2916 return this; 2917 } 2918 2919 HttpPartSerializerSession getPartSerializerSession() { 2920 if (partSerializerSession == null) 2921 partSerializerSession = getPartSerializerSession(null); 2922 return partSerializerSession; 2923 } 2924 2925 RestRequest headerArg(String name, Object value, HttpPartSchema schema, HttpPartSerializer serializer, boolean skipIfEmpty) { 2926 var isMulti = ie(name) || eq(name, "*") || value instanceof HeaderList || isHeaderArray(value); 2927 2928 if (! isMulti) { 2929 if (! (skipIfEmpty && ie(s(value)))) 2930 return header(createHeader(name, value, serializer, schema, skipIfEmpty)); 2931 return this; 2932 } 2933 2934 List<Header> l = list(); 2935 2936 if (HttpHeaders.canCast(value)) { 2937 l.add(HttpHeaders.cast(value)); 2938 } else if (value instanceof HeaderList value2) { 2939 value2.forEach(l::add); 2940 } else if (value instanceof Collection<?> value3) { 2941 value3.forEach(x -> l.add(HttpHeaders.cast(x))); 2942 } else if (isArray(value)) { 2943 for (var i = 0; i < Array.getLength(value); i++) 2944 l.add(HttpHeaders.cast(Array.get(value, i))); 2945 } else if (value instanceof Map value2) { 2946 toMap(value2).forEach((k, v) -> l.add(createHeader(s(k), v, serializer, schema, skipIfEmpty))); 2947 } else if (isBean(value)) { 2948 toBeanMap(value).forEach((k, v) -> l.add(createHeader(k, v, serializer, schema, skipIfEmpty))); 2949 } else if (nn(value)) { 2950 throw rex("Invalid value type for header arg '%s': %s", name, cn(value)); 2951 } 2952 2953 if (skipIfEmpty) 2954 l.removeIf(x -> ie(x.getValue())); 2955 2956 headerData.append(l); 2957 2958 return this; 2959 } 2960 2961 boolean isLoggingSuppressed() { return suppressLogging; } 2962 2963 /** 2964 * Returns <jk>true</jk> if the request body can be safely re-sent (required for a gated automatic retry). 2965 * 2966 * <p> 2967 * A missing body is trivially repeatable; a streaming body ({@link InputStream}/{@link Reader}/record-stream) is 2968 * not; an {@link HttpEntity} body defers to {@link HttpEntity#isRepeatable()}; any other (buffered POJO/String) 2969 * body is repeatable. 2970 * 2971 * @return <jk>true</jk> if the body can be safely re-sent. 2972 */ 2973 boolean isBodyRepeatable() { 2974 var c = content; 2975 if (c == null || c == NO_BODY) 2976 return true; 2977 if (c instanceof InputStream || c instanceof Reader || c instanceof RecordStreamBody) 2978 return false; 2979 if (c instanceof HttpEntity e) 2980 return e.isRepeatable(); 2981 return true; 2982 } 2983 2984 RestRequest pathArg(String name, Object value, HttpPartSchema schema, HttpPartSerializer serializer) { 2985 var isMulti = ie(name) || eq(name, "*") || value instanceof PartList || isNameValuePairArray(value); 2986 2987 if (! isMulti) 2988 return pathData(createPart(name, value, PATH, serializer, schema, false)); 2989 2990 List<NameValuePair> l = list(); 2991 2992 if (HttpParts.canCast(value)) { 2993 l.add(HttpParts.cast(value)); 2994 } else if (value instanceof PartList value2) { 2995 value2.forEach(l::add); 2996 } else if (value instanceof Collection<?> value3) { 2997 value3.forEach(x -> l.add(HttpParts.cast(x))); 2998 } else if (isArray(value)) { 2999 for (var i = 0; i < Array.getLength(value); i++) 3000 l.add(HttpParts.cast(Array.get(value, i))); 3001 } else if (value instanceof Map value2) { 3002 toMap(value2).forEach((k, v) -> l.add(createPart(s(k), v, PATH, serializer, schema, false))); 3003 } else if (isBean(value)) { 3004 toBeanMap(value).forEach((k, v) -> l.add(createPart(k, v, PATH, serializer, schema, false))); 3005 } else if (nn(value)) { 3006 throw rex("Invalid value type for path arg '%s': %s", name, cn(value)); 3007 } 3008 3009 pathData.append(l); 3010 3011 return this; 3012 } 3013 3014 RestRequest queryArg(String name, Object value, HttpPartSchema schema, HttpPartSerializer serializer, boolean skipIfEmpty) { 3015 if (schema != null && schema.getCollectionFormat() == HttpPartCollectionFormat.MULTI) { 3016 if (value instanceof Collection<?> value2) { 3017 List<NameValuePair> l = list(); 3018 value2.forEach(x -> l.add(createPart(name, x, QUERY, serializer, schema, skipIfEmpty))); 3019 if (skipIfEmpty) 3020 l.removeIf(x -> ie(x.getValue())); 3021 queryData.append(l); 3022 return this; 3023 } 3024 if (isArray(value) && ! isNameValuePairArray(value)) { 3025 List<NameValuePair> l = list(); 3026 for (var i = 0; i < Array.getLength(value); i++) 3027 l.add(createPart(name, Array.get(value, i), QUERY, serializer, schema, skipIfEmpty)); 3028 if (skipIfEmpty) 3029 l.removeIf(x -> ie(x.getValue())); 3030 queryData.append(l); 3031 return this; 3032 } 3033 if (value instanceof Map<?,?> value2) { 3034 List<NameValuePair> l = list(); 3035 var sess = getPartSerializerSession(serializer); 3036 for (var e : value2.entrySet()) { 3037 try { 3038 var uon = sess.write(QUERY, schema, e.getValue()); 3039 l.add(BasicPart.of(name, s(e.getKey()) + "=" + (uon == null ? "" : uon))); 3040 } catch (SerializeException | SchemaValidationException ex) { 3041 throw rex(ex, "Serialization error on request query part '%s'", name); 3042 } 3043 } 3044 if (skipIfEmpty) 3045 l.removeIf(x -> ie(x.getValue())); 3046 queryData.append(l); 3047 return this; 3048 } 3049 } 3050 3051 var isMulti = ie(name) || eq(name, "*") || value instanceof PartList || isNameValuePairArray(value); 3052 3053 if (! isMulti) { 3054 if (! (skipIfEmpty && ie(s(value)))) 3055 return queryData(createPart(name, value, QUERY, serializer, schema, skipIfEmpty)); 3056 return this; 3057 } 3058 3059 List<NameValuePair> l = list(); 3060 3061 if (HttpParts.canCast(value)) { 3062 l.add(HttpParts.cast(value)); 3063 } else if (value instanceof PartList value2) { 3064 value2.forEach(l::add); 3065 } else if (value instanceof Collection<?> value3) { 3066 value3.forEach(x -> l.add(HttpParts.cast(x))); 3067 } else if (isArray(value)) { 3068 for (var i = 0; i < Array.getLength(value); i++) 3069 l.add(HttpParts.cast(Array.get(value, i))); 3070 } else if (value instanceof Map value2) { 3071 toMap(value2).forEach((k, v) -> l.add(createPart(s(k), v, QUERY, serializer, schema, skipIfEmpty))); 3072 } else if (isBean(value)) { 3073 toBeanMap(value).forEach((k, v) -> l.add(createPart(k, v, QUERY, serializer, schema, skipIfEmpty))); 3074 } else if (nn(value)) { 3075 queryCustom(value); 3076 return this; 3077 } 3078 3079 if (skipIfEmpty) 3080 l.removeIf(x -> ie(x.getValue())); 3081 3082 queryData.append(l); 3083 3084 return this; 3085 } 3086 3087 /** 3088 * Resolves a {@link RecordStreamBody} body by invoking the caller's consumer against a 3089 * cursor opened over a temporary buffer, then sending the buffered bytes as the request 3090 * body. 3091 */ 3092 private HttpEntity streamBodyEntity(RecordStreamBody body, Serializer serializer) { 3093 if (serializer == null) 3094 throw iaex("No serializer registered for cursor-streamed request body."); 3095 try { 3096 var consumer = body.getConsumer(); 3097 var writerKind = body.getWriterKind(); 3098 3099 if (writerKind == TokenWriter.class && !(serializer instanceof TokenWritable)) 3100 throw iaex("Serializer '%s' does not support the token-writer surface.", serializer.getClass().getName()); 3101 if (writerKind == RecordWriter.class && !(serializer instanceof RecordWritable)) 3102 throw iaex("Serializer '%s' does not support the record-writer surface.", serializer.getClass().getName()); 3103 3104 var buf = new ByteArrayOutputStream(); 3105 Object output = serializer.isWriterSerializer() 3106 ? new OutputStreamWriter(buf, java.nio.charset.StandardCharsets.UTF_8) 3107 : buf; 3108 3109 writeCursor(serializer, writerKind, consumer, output); 3110 3111 // Use the serializer's primary produces() media type if no explicit Content-Type set. 3112 var ct = serializer.getPrimaryMediaType(); 3113 var contentType2 = ct != null ? ContentType.of(ct.toString()) : ContentType.APPLICATION_OCTET_STREAM; 3114 return byteArrayEntity(buf.toByteArray(), getRequestContentType(contentType2)); 3115 } catch (IOException e) { 3116 throw rex(e, "I/O error streaming request body."); 3117 } 3118 } 3119 3120 @SuppressWarnings({ 3121 "rawtypes" // The body consumer's element type depends on the writer kind, so it is invoked through a raw Consumer. 3122 }) 3123 private static void writeCursor(Serializer serializer, Class<?> writerKind, Object consumer, Object output) throws IOException { 3124 try (var w = (writerKind == TokenWriter.class) 3125 ? ((TokenWritable) serializer).writeTokens(output) 3126 : ((RecordWritable) serializer).writeRecords(output)) { 3127 ((Consumer) consumer).accept(w); 3128 w.flush(); 3129 } 3130 } 3131}