HttpHeaders.java 35.7 KB
Newer Older
1
/*
2
 * Copyright 2002-2015 the original author or authors.
3 4 5 6 7
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9 10 11 12 13 14 15 16
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

17
package org.springframework.http;
18

19
import java.io.Serializable;
20
import java.net.URI;
21
import java.nio.charset.Charset;
A
Arjen Poutsma 已提交
22 23
import java.text.ParseException;
import java.text.SimpleDateFormat;
24 25 26
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
A
Arjen Poutsma 已提交
27
import java.util.Date;
28
import java.util.EnumSet;
29
import java.util.Iterator;
30
import java.util.LinkedHashMap;
31 32
import java.util.LinkedList;
import java.util.List;
33
import java.util.Locale;
34 35
import java.util.Map;
import java.util.Set;
A
Arjen Poutsma 已提交
36
import java.util.TimeZone;
37 38

import org.springframework.util.Assert;
39
import org.springframework.util.LinkedCaseInsensitiveMap;
A
Arjen Poutsma 已提交
40
import org.springframework.util.MultiValueMap;
41 42 43 44 45
import org.springframework.util.StringUtils;

/**
 * Represents HTTP request and response headers, mapping string header names to list of string values.
 *
J
Juergen Hoeller 已提交
46 47
 * <p>In addition to the normal methods defined by {@link Map}, this class offers the following
 * convenience methods:
48 49 50 51 52
 * <ul>
 * <li>{@link #getFirst(String)} returns the first value associated with a given header name</li>
 * <li>{@link #add(String, String)} adds a header value to the list of values for a header name</li>
 * <li>{@link #set(String, String)} sets the header value to a single string value</li>
 * </ul>
53 54 55 56
 *
 * <p>Inspired by {@link com.sun.net.httpserver.Headers}.
 *
 * @author Arjen Poutsma
57
 * @author Sebastien Deleuze
58 59
 * @since 3.0
 */
60 61 62
public class HttpHeaders implements MultiValueMap<String, String>, Serializable {

	private static final long serialVersionUID = -8578554704772377436L;
63

64 65 66 67
	/**
	 * The HTTP {@code Accept} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-5.3.2">Section 5.3.2 of RFC 7231</a>
	 */
68
	public static final String ACCEPT = "Accept";
69 70 71 72
	/**
	 * The HTTP {@code Accept-Charset} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-5.3.3">Section 5.3.3 of RFC 7231</a>
	 */
73
	public static final String ACCEPT_CHARSET = "Accept-Charset";
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
	/**
	 * The HTTP {@code Accept-Encoding} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-5.3.4">Section 5.3.4 of RFC 7231</a>
	 */
	public static final String ACCEPT_ENCODING = "Accept-Encoding";
	/**
	 * The HTTP {@code Accept-Language} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-5.3.5">Section 5.3.5 of RFC 7231</a>
	 */
	public static final String ACCEPT_LANGUAGE = "Accept-Language";
	/**
	 * The HTTP {@code Accept-Ranges} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7233#section-2.3">Section 5.3.5 of RFC 7233</a>
	 */
	public static final String ACCEPT_RANGES = "Accept-Ranges";
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
	/**
	 * The CORS {@code Access-Control-Allow-Credentials} response header field name.
	 * @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommandation</a>
	 */
	public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
	/**
	 * The CORS {@code Access-Control-Allow-Headers} response header field name.
	 * @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommandation</a>
	 */
	public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
	/**
	 * The CORS {@code Access-Control-Allow-Methods} response header field name.
	 * @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommandation</a>
	 */
	public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
	/**
	 * The CORS {@code Access-Control-Allow-Origin} response header field name.
	 * @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommandation</a>
	 */
	public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
	/**
	 * The CORS {@code Access-Control-Expose-Headers} response header field name.
	 * @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommandation</a>
	 */
	public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
	/**
	 * The CORS {@code Access-Control-Max-Age} response header field name.
	 * @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommandation</a>
	 */
	public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
	/**
	 * The CORS {@code Access-Control-Request-Headers} request header field name.
	 * @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommandation</a>
	 */
	public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
	/**
	 * The CORS {@code Access-Control-Request-Method} request header field name.
	 * @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommandation</a>
	 */
	public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
129 130 131 132 133 134 135 136 137
	/**
	 * The HTTP {@code Age} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7234#section-5.1">Section 5.1 of RFC 7234</a>
	 */
	public static final String AGE = "Age";
	/**
	 * The HTTP {@code Allow} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-7.4.1">Section 7.4.1 of RFC 7231</a>
	 */
138
	public static final String ALLOW = "Allow";
139 140 141 142 143 144 145 146 147
	/**
	 * The HTTP {@code Authorization} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7235#section-4.2">Section 4.2 of RFC 7235</a>
	 */
	public static final String AUTHORIZATION = "Authorization";
	/**
	 * The HTTP {@code Cache-Control} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7234#section-5.2">Section 5.2 of RFC 7234</a>
	 */
148
	public static final String CACHE_CONTROL = "Cache-Control";
149 150 151 152
	/**
	 * The HTTP {@code Connection} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7230#section-6.1">Section 6.1 of RFC 7230</a>
	 */
153
	public static final String CONNECTION = "Connection";
154 155 156 157 158 159 160 161 162
	/**
	 * The HTTP {@code Content-Encoding} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.2.2">Section 3.1.2.2 of RFC 7231</a>
	 */
	public static final String CONTENT_ENCODING = "Content-Encoding";
	/**
	 * The HTTP {@code Content-Disposition} header field name
	 * @see <a href="http://tools.ietf.org/html/rfc6266">RFC 6266</a>
	 */
163
	public static final String CONTENT_DISPOSITION = "Content-Disposition";
164 165 166 167 168 169 170 171 172
	/**
	 * The HTTP {@code Content-Language} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.3.2">Section 3.1.3.2 of RFC 7231</a>
	 */
	public static final String CONTENT_LANGUAGE = "Content-Language";
	/**
	 * The HTTP {@code Content-Length} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7230#section-3.3.2">Section 3.3.2 of RFC 7230</a>
	 */
173
	public static final String CONTENT_LENGTH = "Content-Length";
174 175 176 177 178 179 180 181 182 183 184 185 186 187
	/**
	 * The HTTP {@code Content-Location} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.4.2">Section 3.1.4.2 of RFC 7231</a>
	 */
	public static final String CONTENT_LOCATION = "Content-Location";
	/**
	 * The HTTP {@code Content-Range} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7233#section-4.2">Section 4.2 of RFC 7233</a>
	 */
	public static final String CONTENT_RANGE = "Content-Range";
	/**
	 * The HTTP {@code Content-Type} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.1.5">Section 3.1.1.5 of RFC 7231</a>
	 */
188
	public static final String CONTENT_TYPE = "Content-Type";
189 190 191 192 193 194 195 196 197
	/**
	 * The HTTP {@code Cookie} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc2109#section-4.3.4">Section 4.3.4 of RFC 2109</a>
	 */
	public static final String COOKIE = "Cookie";
	/**
	 * The HTTP {@code Date} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-7.1.1.2">Section 7.1.1.2 of RFC 7231</a>
	 */
198
	public static final String DATE = "Date";
199 200 201 202
	/**
	 * The HTTP {@code ETag} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a>
	 */
203
	public static final String ETAG = "ETag";
204 205 206 207 208 209 210 211 212
	/**
	 * The HTTP {@code Expect} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-5.1.1">Section 5.1.1 of RFC 7231</a>
	 */
	public static final String EXPECT = "Expect";
	/**
	 * The HTTP {@code Expires} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7234#section-5.3">Section 5.3 of RFC 7234</a>
	 */
213
	public static final String EXPIRES = "Expires";
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
	/**
	 * The HTTP {@code From} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-5.5.1">Section 5.5.1 of RFC 7231</a>
	 */
	public static final String FROM = "From";
	/**
	 * The HTTP {@code Host} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7230#section-5.4">Section 5.4 of RFC 7230</a>
	 */
	public static final String HOST = "Host";
	/**
	 * The HTTP {@code If-Match} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7232#section-3.1">Section 3.1 of RFC 7232</a>
	 */
	public static final String IF_MATCH = "If-Match";
	/**
	 * The HTTP {@code If-Modified-Since} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7232#section-3.3">Section 3.3 of RFC 7232</a>
	 */
233
	public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
234 235 236 237
	/**
	 * The HTTP {@code If-None-Match} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7232#section-3.2">Section 3.2 of RFC 7232</a>
	 */
238
	public static final String IF_NONE_MATCH = "If-None-Match";
239 240 241 242 243 244 245 246 247 248 249 250 251 252
	/**
	 * The HTTP {@code If-Range} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7233#section-3.2">Section 3.2 of RFC 7233</a>
	 */
	public static final String IF_RANGE = "If-Range";
	/**
	 * The HTTP {@code If-Unmodified-Since} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7232#section-3.4">Section 3.4 of RFC 7232</a>
	 */
	public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
	/**
	 * The HTTP {@code Last-Modified} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7232#section-2.2">Section 2.2 of RFC 7232</a>
	 */
253
	public static final String LAST_MODIFIED = "Last-Modified";
254 255 256 257 258 259 260 261 262
	/**
	 * The HTTP {@code Link} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc5988">RFC 5988</a>
	 */
	public static final String LINK = "Link";
	/**
	 * The HTTP {@code Location} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-7.1.2">Section 7.1.2 of RFC 7231</a>
	 */
263
	public static final String LOCATION = "Location";
264 265 266 267 268 269 270 271 272
	/**
	 * The HTTP {@code Max-Forwards} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-5.1.2">Section 5.1.2 of RFC 7231</a>
	 */
	public static final String MAX_FORWARDS = "Max-Forwards";
	/**
	 * The HTTP {@code Origin} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc6454">RFC 6454</a>
	 */
273
	public static final String ORIGIN = "Origin";
274 275 276 277
	/**
	 * The HTTP {@code Pragma} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7234#section-5.4">Section 5.4 of RFC 7234</a>
	 */
278
	public static final String PRAGMA = "Pragma";
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
	/**
	 * The HTTP {@code Proxy-Authenticate} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7235#section-4.3">Section 4.3 of RFC 7235</a>
	 */
	public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate";
	/**
	 * The HTTP {@code Proxy-Authorization} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7235#section-4.4">Section 4.4 of RFC 7235</a>
	 */
	public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
	/**
	 * The HTTP {@code Range} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7233#section-3.1">Section 3.1 of RFC 7233</a>
	 */
	public static final String RANGE = "Range";
	/**
	 * The HTTP {@code Referer} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-5.5.2">Section 5.5.2 of RFC 7231</a>
	 */
	public static final String REFERER = "Referer";
	/**
	 * The HTTP {@code Retry-After} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-7.1.3">Section 7.1.3 of RFC 7231</a>
	 */
	public static final String RETRY_AFTER = "Retry-After";
	/**
	 * The HTTP {@code Server} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-7.4.2">Section 7.4.2 of RFC 7231</a>
	 */
	public static final String SERVER = "Server";
	/**
	 * The HTTP {@code Set-Cookie} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc2109#section-4.2.2">Section 4.2.2 of RFC 2109</a>
	 */
	public static final String SET_COOKIE = "Set-Cookie";
	/**
	 * The HTTP {@code Set-Cookie2} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc2965">RFC 2965</a>
	 */
	public static final String SET_COOKIE2 = "Set-Cookie2";
	/**
	 * The HTTP {@code TE} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7230#section-4.3">Section 4.3 of RFC 7230</a>
	 */
	public static final String TE = "TE";
	/**
	 * The HTTP {@code Trailer} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7230#section-4.4">Section 4.4 of RFC 7230</a>
	 */
	public static final String TRAILER = "Trailer";
	/**
	 * The HTTP {@code Transfer-Encoding} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7230#section-3.3.1">Section 3.3.1 of RFC 7230</a>
	 */
	public static final String TRANSFER_ENCODING = "Transfer-Encoding";
	/**
	 * The HTTP {@code Upgrade} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7230#section-6.7">Section 6.7 of RFC 7230</a>
	 */
338
	public static final String UPGRADE = "Upgrade";
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
	/**
	 * The HTTP {@code User-Agent} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-5.5.3">Section 5.5.3 of RFC 7231</a>
	 */
	public static final String USER_AGENT = "User-Agent";
	/**
	 * The HTTP {@code Vary} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-7.1.4">Section 7.1.4 of RFC 7231</a>
	 */
	public static final String VARY = "Vary";
	/**
	 * The HTTP {@code Via} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7230#section-5.7.1">Section 5.7.1 of RFC 7230</a>
	 */
	public static final String VIA = "Via";
	/**
	 * The HTTP {@code Warning} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7234#section-5.5">Section 5.5 of RFC 7234</a>
	 */
	public static final String WARNING = "Warning";
	/**
	 * The HTTP {@code WWW-Authenticate} header field name.
	 * @see <a href="http://tools.ietf.org/html/rfc7235#section-4.1">Section 4.1 of RFC 7235</a>
	 */
	public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
A
Arjen Poutsma 已提交
364 365 366

	private static final String[] DATE_FORMATS = new String[] {
		"EEE, dd MMM yyyy HH:mm:ss zzz",
P
Phillip Webb 已提交
367 368
		"EEE, dd-MMM-yy HH:mm:ss zzz",
		"EEE MMM dd HH:mm:ss yyyy"
A
Arjen Poutsma 已提交
369 370 371
	};

	private static TimeZone GMT = TimeZone.getTimeZone("GMT");
372

J
Juergen Hoeller 已提交
373

374 375
	private final Map<String, List<String>> headers;

376

J
Juergen Hoeller 已提交
377 378 379 380 381 382 383
	/**
	 * Constructs a new, empty instance of the {@code HttpHeaders} object.
	 */
	public HttpHeaders() {
		this(new LinkedCaseInsensitiveMap<List<String>>(8, Locale.ENGLISH), false);
	}

384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
	/**
	 * Private constructor that can create read-only {@code HttpHeader} instances.
	 */
	private HttpHeaders(Map<String, List<String>> headers, boolean readOnly) {
		Assert.notNull(headers, "'headers' must not be null");
		if (readOnly) {
			Map<String, List<String>> map =
					new LinkedCaseInsensitiveMap<List<String>>(headers.size(), Locale.ENGLISH);
			for (Entry<String, List<String>> entry : headers.entrySet()) {
				List<String> values = Collections.unmodifiableList(entry.getValue());
				map.put(entry.getKey(), values);
			}
			this.headers = Collections.unmodifiableMap(map);
		}
		else {
			this.headers = headers;
		}
	}
402 403 404


	/**
J
Juergen Hoeller 已提交
405 406
	 * Set the list of acceptable {@linkplain MediaType media types},
	 * as specified by the {@code Accept} header.
407 408 409 410
	 */
	public void setAccept(List<MediaType> acceptableMediaTypes) {
		set(ACCEPT, MediaType.toString(acceptableMediaTypes));
	}
411 412

	/**
J
Juergen Hoeller 已提交
413 414
	 * Return the list of acceptable {@linkplain MediaType media types},
	 * as specified by the {@code Accept} header.
415
	 * <p>Returns an empty list when the acceptable media types are unspecified.
416 417 418
	 */
	public List<MediaType> getAccept() {
		String value = getFirst(ACCEPT);
J
Juergen Hoeller 已提交
419
		List<MediaType> result = (value != null ? MediaType.parseMediaTypes(value) : Collections.<MediaType>emptyList());
420 421

		// Some containers parse 'Accept' into multiple values
J
Juergen Hoeller 已提交
422 423 424 425 426 427
		if (result.size() == 1) {
			List<String> acceptHeader = get(ACCEPT);
			if (acceptHeader.size() > 1) {
				value = StringUtils.collectionToCommaDelimitedString(acceptHeader);
				result = MediaType.parseMediaTypes(value);
			}
428 429 430
		}

		return result;
431 432
	}

433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555
	/**
	 * Set the (new) value of the {@code Access-Control-Allow-Credentials} response header.
	 */
	public void setAccessControlAllowCredentials(boolean allowCredentials) {
		set(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.toString(allowCredentials));
	}

	/**
	 * Returns the value of the {@code Access-Control-Allow-Credentials} response header.
	 */
	public boolean getAccessControlAllowCredentials() {
		return new Boolean(getFirst(ACCESS_CONTROL_ALLOW_CREDENTIALS));
	}

	/**
	 * Set the (new) value of the {@code Access-Control-Allow-Headers} response header.
	 */
	public void setAccessControlAllowHeaders(List<String> allowedHeaders) {
		set(ACCESS_CONTROL_ALLOW_HEADERS, toCommaDelimitedString(allowedHeaders));
	}

	/**
	 * Returns the value of the {@code Access-Control-Allow-Headers} response header.
	 */
	public List<String> getAccessControlAllowHeaders() {
		return getFirstValueAsList(ACCESS_CONTROL_ALLOW_HEADERS);
	}

	/**
	 * Set the (new) value of the {@code Access-Control-Allow-Methods} response header.
	 */
	public void setAccessControlAllowMethods(List<HttpMethod> allowedMethods) {
		set(ACCESS_CONTROL_ALLOW_METHODS, StringUtils.collectionToCommaDelimitedString(allowedMethods));
	}

	/**
	 * Returns the value of the {@code Access-Control-Allow-Methods} response header.
	 */
	public List<HttpMethod> getAccessControlAllowMethods() {
		List<HttpMethod> result = new ArrayList<HttpMethod>();
		String value = getFirst(ACCESS_CONTROL_ALLOW_METHODS);
		if (value != null) {
			String[] tokens = value.split(",\\s*");
			for (String token : tokens) {
				result.add(HttpMethod.valueOf(token));
			}
		}
		return result;
	}

	/**
	 * Set the (new) value of the {@code Access-Control-Allow-Origin} response header.
	 */
	public void setAccessControlAllowOrigin(String allowedOrigin) {
		set(ACCESS_CONTROL_ALLOW_ORIGIN, allowedOrigin);
	}

	/**
	 * Returns the value of the {@code Access-Control-Allow-Origin} response header.
	 */
	public String getAccessControlAllowOrigin() {
		return getFirst(ACCESS_CONTROL_ALLOW_ORIGIN);
	}

	/**
	 * Set the (new) value of the {@code Access-Control-Expose-Headers} response header.
	 */
	public void setAccessControlExposeHeaders(List<String> exposedHeaders) {
		set(ACCESS_CONTROL_EXPOSE_HEADERS, toCommaDelimitedString(exposedHeaders));
	}

	/**
	 * Returns the value of the {@code Access-Control-Expose-Headers} response header.
	 */
	public List<String> getAccessControlExposeHeaders() {
		return getFirstValueAsList(ACCESS_CONTROL_EXPOSE_HEADERS);
	}

	/**
	 * Set the (new) value of the {@code Access-Control-Max-Age} response header.
	 */
	public void setAccessControlMaxAge(long maxAge) {
		set(ACCESS_CONTROL_MAX_AGE, Long.toString(maxAge));
	}

	/**
	 * Returns the value of the {@code Access-Control-Max-Age} response header.
	 * <p>Returns -1 when the max age is unknown.
	 */
	public long getAccessControlMaxAge() {
		String value = getFirst(ACCESS_CONTROL_MAX_AGE);
		return (value != null ? Long.parseLong(value) : -1);
	}

	/**
	 * Set the (new) value of the {@code Access-Control-Request-Headers} request header.
	 */
	public void setAccessControlRequestHeaders(List<String> requestHeaders) {
		set(ACCESS_CONTROL_REQUEST_HEADERS, toCommaDelimitedString(requestHeaders));
	}

	/**
	 * Returns the value of the {@code Access-Control-Request-Headers} request header.
	 */
	public List<String> getAccessControlRequestHeaders() {
		return getFirstValueAsList(ACCESS_CONTROL_REQUEST_HEADERS);
	}

	/**
	 * Set the (new) value of the {@code Access-Control-Request-Method} request header.
	 */
	public void setAccessControlRequestMethod(HttpMethod requestedMethod) {
		set(ACCESS_CONTROL_REQUEST_METHOD, requestedMethod.name());
	}

	/**
	 * Returns the value of the {@code Access-Control-Request-Method} request header.
	 */
	public HttpMethod getAccessControlRequestMethod() {
		String value = getFirst(ACCESS_CONTROL_REQUEST_METHOD);
		return (value != null ? HttpMethod.valueOf(value) : null);
	}

556
	/**
J
Juergen Hoeller 已提交
557 558
	 * Set the list of acceptable {@linkplain Charset charsets},
	 * as specified by the {@code Accept-Charset} header.
559
	 */
560 561 562 563 564 565 566 567 568 569
	public void setAcceptCharset(List<Charset> acceptableCharsets) {
		StringBuilder builder = new StringBuilder();
		for (Iterator<Charset> iterator = acceptableCharsets.iterator(); iterator.hasNext();) {
			Charset charset = iterator.next();
			builder.append(charset.name().toLowerCase(Locale.ENGLISH));
			if (iterator.hasNext()) {
				builder.append(", ");
			}
		}
		set(ACCEPT_CHARSET, builder.toString());
570 571
	}

572
	/**
J
Juergen Hoeller 已提交
573 574
	 * Return the list of acceptable {@linkplain Charset charsets},
	 * as specified by the {@code Accept-Charset} header.
575 576 577 578 579 580 581 582
	 */
	public List<Charset> getAcceptCharset() {
		List<Charset> result = new ArrayList<Charset>();
		String value = getFirst(ACCEPT_CHARSET);
		if (value != null) {
			String[] tokens = value.split(",\\s*");
			for (String token : tokens) {
				int paramIdx = token.indexOf(';');
583
				String charsetName;
584
				if (paramIdx == -1) {
585
					charsetName = token;
586 587
				}
				else {
588 589 590 591
					charsetName = token.substring(0, paramIdx);
				}
				if (!charsetName.equals("*")) {
					result.add(Charset.forName(charsetName));
592 593 594 595 596 597 598
				}
			}
		}
		return result;
	}

	/**
J
Juergen Hoeller 已提交
599 600
	 * Set the set of allowed {@link HttpMethod HTTP methods},
	 * as specified by the {@code Allow} header.
601
	 */
602 603
	public void setAllow(Set<HttpMethod> allowedMethods) {
		set(ALLOW, StringUtils.collectionToCommaDelimitedString(allowedMethods));
604 605
	}

606
	/**
J
Juergen Hoeller 已提交
607 608
	 * Return the set of allowed {@link HttpMethod HTTP methods},
	 * as specified by the {@code Allow} header.
609
	 * <p>Returns an empty set when the allowed methods are unspecified.
610
	 */
611
	public Set<HttpMethod> getAllow() {
612
		String value = getFirst(ALLOW);
613
		if (!StringUtils.isEmpty(value)) {
614 615 616 617 618 619 620 621 622 623 624 625
			List<HttpMethod> allowedMethod = new ArrayList<HttpMethod>(5);
			String[] tokens = value.split(",\\s*");
			for (String token : tokens) {
				allowedMethod.add(HttpMethod.valueOf(token));
			}
			return EnumSet.copyOf(allowedMethod);
		}
		else {
			return EnumSet.noneOf(HttpMethod.class);
		}
	}

A
Arjen Poutsma 已提交
626
	/**
J
Juergen Hoeller 已提交
627
	 * Set the (new) value of the {@code Cache-Control} header.
A
Arjen Poutsma 已提交
628 629 630 631 632 633 634 635 636 637 638 639
	 */
	public void setCacheControl(String cacheControl) {
		set(CACHE_CONTROL, cacheControl);
	}

	/**
	 * Returns the value of the {@code Cache-Control} header.
	 */
	public String getCacheControl() {
		return getFirst(CACHE_CONTROL);
	}

640
	/**
J
Juergen Hoeller 已提交
641
	 * Set the (new) value of the {@code Connection} header.
642 643 644 645 646 647
	 */
	public void setConnection(String connection) {
		set(CONNECTION, connection);
	}

	/**
J
Juergen Hoeller 已提交
648
	 * Set the (new) value of the {@code Connection} header.
649 650 651 652 653 654 655 656 657 658 659 660
	 */
	public void setConnection(List<String> connection) {
		set(CONNECTION, toCommaDelimitedString(connection));
	}

	/**
	 * Returns the value of the {@code Connection} header.
	 */
	public List<String> getConnection() {
		return getFirstValueAsList(CONNECTION);
	}

661
	/**
J
Juergen Hoeller 已提交
662 663
	 * Set the (new) value of the {@code Content-Disposition} header
	 * for {@code form-data}.
664
	 * @param name the control name
J
Juergen Hoeller 已提交
665
	 * @param filename the filename (may be {@code null})
666 667 668 669 670 671 672 673 674 675 676 677
	 */
	public void setContentDispositionFormData(String name, String filename) {
		Assert.notNull(name, "'name' must not be null");
		StringBuilder builder = new StringBuilder("form-data; name=\"");
		builder.append(name).append('\"');
		if (filename != null) {
			builder.append("; filename=\"");
			builder.append(filename).append('\"');
		}
		set(CONTENT_DISPOSITION, builder.toString());
	}

678
	/**
J
Juergen Hoeller 已提交
679 680
	 * Set the length of the body in bytes, as specified by the
	 * {@code Content-Length} header.
681
	 */
682 683
	public void setContentLength(long contentLength) {
		set(CONTENT_LENGTH, Long.toString(contentLength));
684 685 686
	}

	/**
J
Juergen Hoeller 已提交
687 688
	 * Return the length of the body in bytes, as specified by the
	 * {@code Content-Length} header.
689
	 * <p>Returns -1 when the content-length is unknown.
690 691 692
	 */
	public long getContentLength() {
		String value = getFirst(CONTENT_LENGTH);
693
		return (value != null ? Long.parseLong(value) : -1);
694 695 696
	}

	/**
J
Juergen Hoeller 已提交
697 698
	 * Set the {@linkplain MediaType media type} of the body,
	 * as specified by the {@code Content-Type} header.
699
	 */
700 701 702 703
	public void setContentType(MediaType mediaType) {
		Assert.isTrue(!mediaType.isWildcardType(), "'Content-Type' cannot contain wildcard type '*'");
		Assert.isTrue(!mediaType.isWildcardSubtype(), "'Content-Type' cannot contain wildcard subtype '*'");
		set(CONTENT_TYPE, mediaType.toString());
704 705 706
	}

	/**
J
Juergen Hoeller 已提交
707 708
	 * Return the {@linkplain MediaType media type} of the body, as specified
	 * by the {@code Content-Type} header.
A
Arjen Poutsma 已提交
709
	 * <p>Returns {@code null} when the content-type is unknown.
710 711 712
	 */
	public MediaType getContentType() {
		String value = getFirst(CONTENT_TYPE);
713
		return (StringUtils.hasLength(value) ? MediaType.parseMediaType(value) : null);
714 715 716
	}

	/**
J
Juergen Hoeller 已提交
717 718 719 720
	 * Set the date and time at which the message was created, as specified
	 * by the {@code Date} header.
	 * <p>The date should be specified as the number of milliseconds since
	 * January 1, 1970 GMT.
A
Arjen Poutsma 已提交
721 722 723 724 725 726
	 */
	public void setDate(long date) {
		setDate(DATE, date);
	}

	/**
J
Juergen Hoeller 已提交
727 728 729 730
	 * Return the date and time at which the message was created, as specified
	 * by the {@code Date} header.
	 * <p>The date is returned as the number of milliseconds since
	 * January 1, 1970 GMT. Returns -1 when the date is unknown.
A
Arjen Poutsma 已提交
731 732 733 734 735 736 737
	 * @throws IllegalArgumentException if the value can't be converted to a date
	 */
	public long getDate() {
		return getFirstDate(DATE);
	}

	/**
J
Juergen Hoeller 已提交
738
	 * Set the (new) entity tag of the body, as specified by the {@code ETag} header.
A
Arjen Poutsma 已提交
739 740
	 */
	public void setETag(String eTag) {
741
		if (eTag != null) {
J
Juergen Hoeller 已提交
742 743
			Assert.isTrue(eTag.startsWith("\"") || eTag.startsWith("W/"),
					"Invalid eTag, does not start with W/ or \"");
744 745
			Assert.isTrue(eTag.endsWith("\""), "Invalid eTag, does not end with \"");
		}
J
Juergen Hoeller 已提交
746
		set(ETAG, eTag);
A
Arjen Poutsma 已提交
747 748 749
	}

	/**
J
Juergen Hoeller 已提交
750
	 * Return the entity tag of the body, as specified by the {@code ETag} header.
A
Arjen Poutsma 已提交
751 752
	 */
	public String getETag() {
753
		return getFirst(ETAG);
A
Arjen Poutsma 已提交
754 755 756
	}

	/**
J
Juergen Hoeller 已提交
757 758
	 * Set the date and time at which the message is no longer valid,
	 * as specified by the {@code Expires} header.
J
Juergen Hoeller 已提交
759 760
	 * <p>The date should be specified as the number of milliseconds since
	 * January 1, 1970 GMT.
A
Arjen Poutsma 已提交
761 762 763 764 765 766
	 */
	public void setExpires(long expires) {
		setDate(EXPIRES, expires);
	}

	/**
J
Juergen Hoeller 已提交
767
	 * Return the date and time at which the message is no longer valid,
J
Juergen Hoeller 已提交
768
	 * as specified by the {@code Expires} header.
J
Juergen Hoeller 已提交
769 770
	 * <p>The date is returned as the number of milliseconds since
	 * January 1, 1970 GMT. Returns -1 when the date is unknown.
A
Arjen Poutsma 已提交
771 772
	 */
	public long getExpires() {
773 774 775 776 777 778
		try {
			return getFirstDate(EXPIRES);
		}
		catch (IllegalArgumentException ex) {
			return -1;
		}
A
Arjen Poutsma 已提交
779 780
	}

A
Arjen Poutsma 已提交
781
	/**
J
Juergen Hoeller 已提交
782
	 * Set the (new) value of the {@code If-Modified-Since} header.
J
Juergen Hoeller 已提交
783 784
	 * <p>The date should be specified as the number of milliseconds since
	 * January 1, 1970 GMT.
A
Arjen Poutsma 已提交
785 786 787 788 789
	 */
	public void setIfModifiedSince(long ifModifiedSince) {
		setDate(IF_MODIFIED_SINCE, ifModifiedSince);
	}

790
	/**
J
Juergen Hoeller 已提交
791 792 793
	 * Return the value of the {@code If-Modified-Since} header.
	 * <p>The date is returned as the number of milliseconds since
	 * January 1, 1970 GMT. Returns -1 when the date is unknown.
794 795
	 */
	public long getIfModifiedSince() {
A
Arjen Poutsma 已提交
796 797 798
		return getFirstDate(IF_MODIFIED_SINCE);
	}

A
Arjen Poutsma 已提交
799
	/**
J
Juergen Hoeller 已提交
800
	 * Set the (new) value of the {@code If-None-Match} header.
A
Arjen Poutsma 已提交
801 802
	 */
	public void setIfNoneMatch(String ifNoneMatch) {
803
		set(IF_NONE_MATCH, ifNoneMatch);
A
Arjen Poutsma 已提交
804 805 806
	}

	/**
J
Juergen Hoeller 已提交
807
	 * Set the (new) values of the {@code If-None-Match} header.
A
Arjen Poutsma 已提交
808 809
	 */
	public void setIfNoneMatch(List<String> ifNoneMatchList) {
810 811 812
		set(IF_NONE_MATCH, toCommaDelimitedString(ifNoneMatchList));
	}

813
	protected String toCommaDelimitedString(List<String> list) {
A
Arjen Poutsma 已提交
814
		StringBuilder builder = new StringBuilder();
815
		for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
A
Arjen Poutsma 已提交
816
			String ifNoneMatch = iterator.next();
817
			builder.append(ifNoneMatch);
A
Arjen Poutsma 已提交
818 819 820 821
			if (iterator.hasNext()) {
				builder.append(", ");
			}
		}
822
		return builder.toString();
A
Arjen Poutsma 已提交
823 824 825
	}

	/**
J
Juergen Hoeller 已提交
826
	 * Return the value of the {@code If-None-Match} header.
A
Arjen Poutsma 已提交
827 828
	 */
	public List<String> getIfNoneMatch() {
829 830 831
		return getFirstValueAsList(IF_NONE_MATCH);
	}

832
	protected List<String> getFirstValueAsList(String header) {
A
Arjen Poutsma 已提交
833
		List<String> result = new ArrayList<String>();
834
		String value = getFirst(header);
A
Arjen Poutsma 已提交
835 836 837
		if (value != null) {
			String[] tokens = value.split(",\\s*");
			for (String token : tokens) {
838
				result.add(token);
A
Arjen Poutsma 已提交
839 840 841 842 843 844
			}
		}
		return result;
	}

	/**
J
Juergen Hoeller 已提交
845 846 847 848
	 * Set the time the resource was last changed, as specified by the
	 * {@code Last-Modified} header.
	 * <p>The date should be specified as the number of milliseconds since
	 * January 1, 1970 GMT.
A
Arjen Poutsma 已提交
849 850 851 852 853 854
	 */
	public void setLastModified(long lastModified) {
		setDate(LAST_MODIFIED, lastModified);
	}

	/**
J
Juergen Hoeller 已提交
855 856 857 858
	 * Return the time the resource was last changed, as specified by the
	 * {@code Last-Modified} header.
	 * <p>The date is returned as the number of milliseconds since
	 * January 1, 1970 GMT. Returns -1 when the date is unknown.
A
Arjen Poutsma 已提交
859 860 861 862 863 864
	 */
	public long getLastModified() {
		return getFirstDate(LAST_MODIFIED);
	}

	/**
J
Juergen Hoeller 已提交
865 866
	 * Set the (new) location of a resource,
	 * as specified by the {@code Location} header.
867
	 */
868 869
	public void setLocation(URI location) {
		set(LOCATION, location.toASCIIString());
870 871 872
	}

	/**
J
Juergen Hoeller 已提交
873 874
	 * Return the (new) location of a resource
	 * as specified by the {@code Location} header.
A
Arjen Poutsma 已提交
875
	 * <p>Returns {@code null} when the location is unknown.
876 877 878
	 */
	public URI getLocation() {
		String value = getFirst(LOCATION);
879
		return (value != null ? URI.create(value) : null);
880 881
	}

882
	/**
J
Juergen Hoeller 已提交
883
	 * Set the (new) value of the {@code Origin} header.
884 885 886 887 888 889
	 */
	public void setOrigin(String origin) {
		set(ORIGIN, origin);
	}

	/**
J
Juergen Hoeller 已提交
890
	 * Return the value of the {@code Origin} header.
891 892 893 894 895
	 */
	public String getOrigin() {
		return getFirst(ORIGIN);
	}

A
Arjen Poutsma 已提交
896
	/**
J
Juergen Hoeller 已提交
897
	 * Set the (new) value of the {@code Pragma} header.
A
Arjen Poutsma 已提交
898 899 900 901 902 903
	 */
	public void setPragma(String pragma) {
		set(PRAGMA, pragma);
	}

	/**
J
Juergen Hoeller 已提交
904
	 * Return the value of the {@code Pragma} header.
A
Arjen Poutsma 已提交
905 906 907 908 909
	 */
	public String getPragma() {
		return getFirst(PRAGMA);
	}

910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926
	/**
	 * Sets the (new) value of the {@code Range} header.
	 */
	public void setRange(List<HttpRange> ranges) {
		String value = HttpRange.toString(ranges);
		set(RANGE, value);
	}

	/**
	 * Returns the value of the {@code Range} header.
	 * <p>Returns an empty list when the range is unknown.
	 */
	public List<HttpRange> getRange() {
		String value = getFirst(RANGE);
		return HttpRange.parseRanges(value);
	}

927
	/**
J
Juergen Hoeller 已提交
928
	 * Set the (new) value of the {@code Upgrade} header.
929 930
	 */
	public void setUpgrade(String upgrade) {
R
Rossen Stoyanchev 已提交
931
		set(UPGRADE, upgrade);
932 933 934 935 936 937
	}

	/**
	 * Returns the value of the {@code Upgrade} header.
	 */
	public String getUpgrade() {
R
Rossen Stoyanchev 已提交
938
		return getFirst(UPGRADE);
939 940
	}

941
	/**
J
Juergen Hoeller 已提交
942 943 944
	 * Parse the first header value for the given header name as a date,
	 * return -1 if there is no value, or raise {@link IllegalArgumentException}
	 * if the value cannot be parsed as a date.
945 946
	 */
	public long getFirstDate(String headerName) {
A
Arjen Poutsma 已提交
947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964
		String headerValue = getFirst(headerName);
		if (headerValue == null) {
			return -1;
		}
		for (String dateFormat : DATE_FORMATS) {
			SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat, Locale.US);
			simpleDateFormat.setTimeZone(GMT);
			try {
				return simpleDateFormat.parse(headerValue).getTime();
			}
			catch (ParseException e) {
				// ignore
			}
		}
		throw new IllegalArgumentException("Cannot parse date value \"" + headerValue +
				"\" for \"" + headerName + "\" header");
	}

965 966 967 968 969 970
	/**
	 * Set the given date under the given header name after formatting it as a string
	 * using the pattern {@code "EEE, dd MMM yyyy HH:mm:ss zzz"}. The equivalent of
	 * {@link #set(String, String)} but for date headers.
	 */
	public void setDate(String headerName, long date) {
971
		SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMATS[0], Locale.US);
A
Arjen Poutsma 已提交
972 973 974
		dateFormat.setTimeZone(GMT);
		set(headerName, dateFormat.format(new Date(date)));
	}
975 976

	/**
977
	 * Return the first header value for the given header name, if any.
978
	 * @param headerName the header name
J
Juergen Hoeller 已提交
979
	 * @return the first header value, or {@code null} if none
980
	 */
981
	@Override
982 983 984 985 986 987
	public String getFirst(String headerName) {
		List<String> headerValues = headers.get(headerName);
		return headerValues != null ? headerValues.get(0) : null;
	}

	/**
988
	 * Add the given, single header value under the given name.
J
Juergen Hoeller 已提交
989
	 * @param headerName the header name
990 991 992 993 994
	 * @param headerValue the header value
	 * @throws UnsupportedOperationException if adding headers is not supported
	 * @see #put(String, List)
	 * @see #set(String, String)
	 */
995
	@Override
996 997 998 999
	public void add(String headerName, String headerValue) {
		List<String> headerValues = headers.get(headerName);
		if (headerValues == null) {
			headerValues = new LinkedList<String>();
1000
			this.headers.put(headerName, headerValues);
1001 1002 1003 1004 1005
		}
		headerValues.add(headerValue);
	}

	/**
1006
	 * Set the given, single header value under the given name.
J
Juergen Hoeller 已提交
1007
	 * @param headerName the header name
1008 1009 1010 1011 1012
	 * @param headerValue the header value
	 * @throws UnsupportedOperationException if adding headers is not supported
	 * @see #put(String, List)
	 * @see #add(String, String)
	 */
1013
	@Override
1014 1015 1016 1017 1018 1019
	public void set(String headerName, String headerValue) {
		List<String> headerValues = new LinkedList<String>();
		headerValues.add(headerValue);
		headers.put(headerName, headerValues);
	}

1020
	@Override
1021 1022 1023 1024 1025 1026
	public void setAll(Map<String, String> values) {
		for (Entry<String, String> entry : values.entrySet()) {
			set(entry.getKey(), entry.getValue());
		}
	}

1027
	@Override
1028 1029 1030 1031 1032 1033 1034
	public Map<String, String> toSingleValueMap() {
		LinkedHashMap<String, String> singleValueMap = new LinkedHashMap<String,String>(this.headers.size());
		for (Entry<String, List<String>> entry : headers.entrySet()) {
			singleValueMap.put(entry.getKey(), entry.getValue().get(0));
		}
		return singleValueMap;
	}
1035

J
Juergen Hoeller 已提交
1036

1037
	// Map implementation
1038

1039
	@Override
1040
	public int size() {
1041
		return this.headers.size();
1042 1043
	}

1044
	@Override
1045
	public boolean isEmpty() {
1046
		return this.headers.isEmpty();
1047 1048
	}

1049
	@Override
1050
	public boolean containsKey(Object key) {
1051
		return this.headers.containsKey(key);
1052 1053
	}

1054
	@Override
1055
	public boolean containsValue(Object value) {
1056
		return this.headers.containsValue(value);
1057 1058
	}

1059
	@Override
1060
	public List<String> get(Object key) {
1061
		return this.headers.get(key);
1062 1063
	}

1064
	@Override
1065
	public List<String> put(String key, List<String> value) {
1066
		return this.headers.put(key, value);
1067 1068
	}

1069
	@Override
1070
	public List<String> remove(Object key) {
1071
		return this.headers.remove(key);
1072 1073
	}

1074
	@Override
J
Juergen Hoeller 已提交
1075 1076
	public void putAll(Map<? extends String, ? extends List<String>> map) {
		this.headers.putAll(map);
1077 1078
	}

1079
	@Override
1080
	public void clear() {
1081
		this.headers.clear();
1082 1083
	}

1084
	@Override
1085
	public Set<String> keySet() {
1086
		return this.headers.keySet();
1087 1088
	}

1089
	@Override
1090
	public Collection<List<String>> values() {
1091
		return this.headers.values();
1092 1093
	}

1094
	@Override
1095
	public Set<Entry<String, List<String>>> entrySet() {
1096
		return this.headers.entrySet();
1097 1098
	}

1099

1100
	@Override
1101 1102
	public boolean equals(Object other) {
		if (this == other) {
1103 1104
			return true;
		}
1105 1106
		if (!(other instanceof HttpHeaders)) {
			return false;
1107
		}
1108 1109 1110 1111 1112 1113 1114
		HttpHeaders otherHeaders = (HttpHeaders) other;
		return this.headers.equals(otherHeaders.headers);
	}

	@Override
	public int hashCode() {
		return this.headers.hashCode();
1115 1116 1117 1118
	}

	@Override
	public String toString() {
1119
		return this.headers.toString();
1120
	}
1121

J
Juergen Hoeller 已提交
1122 1123 1124 1125 1126 1127 1128 1129

	/**
	 * Return a {@code HttpHeaders} object that can only be read, not written to.
	 */
	public static HttpHeaders readOnlyHttpHeaders(HttpHeaders headers) {
		return new HttpHeaders(headers, true);
	}

1130
}