本节我们学习一下ConsumesRequestCondition 和 ProducesRequestCondition。
客户端在请求服务端的资源时,会告诉服务端我携带数据是什么类型,你返回给我数据时按照我的可接受的格式返回,于是在请求头中就可以到 Content-Type和Accept。请求头
的Content-Type很多时候会缺失,Content-Type默认值是text/html。
服务器在返回数据时,也会告诉客户端我实际返回的数据类型,在响应头中我们可以看到Content-Type。
1、ConsumesRequestCondition
ConsumesRequestCondition 用于存储@RequestMapping注解中的consumes和headers中的Content-Type。
/**
* A logical disjunction (' || ') request condition to match a request's
* 'Content-Type' header to a list of media type expressions. Two kinds of
* media type expressions are supported, which are described in
* {@link RequestMapping#consumes()} and {@link RequestMapping#headers()}
* where the header name is 'Content-Type'. Regardless of which syntax is
* used, the semantics are the same.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
*/
public final class ConsumesRequestCondition extends AbstractRequestCondition<ConsumesRequestCondition> {
private final List<ConsumeMediaTypeExpression> expressions;
/**
* Creates a new instance from 0 or more "consumes" expressions.
* @param consumes expressions with the syntax described in
* {@link RequestMapping#consumes()}; if 0 expressions are provided,
* the condition will match to every request
*/
public ConsumesRequestCondition(String... consumes) {
this(consumes, null);
}
/**
* Creates a new instance with "consumes" and "header" expressions.
* "Header" expressions where the header name is not 'Content-Type' or have
* no header value defined are ignored. If 0 expressions are provided in
* total, the condition will match to every request
* @param consumes as described in {@link RequestMapping#consumes()}
* @param headers as described in {@link RequestMapping#headers()}
*/
public ConsumesRequestCondition(String[] consumes, String[] headers) {
this(parseExpressions(consumes, headers));
}
/**
* Private constructor accepting parsed media type expressions.
*/
private ConsumesRequestCondition(Collection<ConsumeMediaTypeExpression> expressions) {
this.expressions = new ArrayList<ConsumeMediaTypeExpression>(expressions);
Collections.sort(this.expressions);
}
private static Set<ConsumeMediaTypeExpression> parseExpressions(String[] consumes, String[] headers) {
Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<ConsumeMediaTypeExpression>();
if (headers != null) {
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if ("Content-Type".equalsIgnoreCase(expr.name)) {
for (MediaType mediaType : MediaType.parseMediaTypes(expr.value)) {
result.add(new ConsumeMediaTypeExpression(mediaType, expr.isNegated));
}
}
}
}
if (consumes != null) {
for (String consume : consumes) {
result.add(new ConsumeMediaTypeExpression(consume));
}
}
return result;
}
/**
* Return the contained MediaType expressions.
*/
public Set<MediaTypeExpression> getExpressions() {
return new LinkedHashSet<MediaTypeExpression>(this.expressions);
}
/**
* Returns the media types for this condition excluding negated expressions.
*/
public Set<MediaType> getConsumableMediaTypes() {
Set<MediaType> result = new LinkedHashSet<MediaType>();
for (ConsumeMediaTypeExpression expression : this.expressions) {
if (!expression.isNegated()) {
result.add(expression.getMediaType());
}
}
return result;
}
/**
* Whether the condition has any media type expressions.
*/
public boolean isEmpty() {
return this.expressions.isEmpty();
}
@Override
protected Collection<ConsumeMediaTypeExpression> getContent() {
return this.expressions;
}
@Override
protected String getToStringInfix() {
return " || ";
}
/**
* Returns the "other" instance if it has any expressions; returns "this"
* instance otherwise. Practically that means a method-level "consumes"
* overrides a type-level "consumes" condition.
*/
public ConsumesRequestCondition combine(ConsumesRequestCondition other) {
return !other.expressions.isEmpty() ? other : this;
}
/**
* Checks if any of the contained media type expressions match the given
* request 'Content-Type' header and returns an instance that is guaranteed
* to contain matching expressions only. The match is performed via
* {@link MediaType#includes(MediaType)}.
* @param request the current request
* @return the same instance if the condition contains no expressions;
* or a new condition with matching expressions only;
* or {@code null} if no expressions match.
*/
public ConsumesRequestCondition getMatchingCondition(HttpServletRequest request) {
if (isEmpty()) {
return this;
}
Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<ConsumeMediaTypeExpression>(expressions);
for (Iterator<ConsumeMediaTypeExpression> iterator = result.iterator(); iterator.hasNext();) {
ConsumeMediaTypeExpression expression = iterator.next();
if (!expression.match(request)) {
iterator.remove();
}
}
return (result.isEmpty()) ? null : new ConsumesRequestCondition(result);
}
/**
* Returns:
* <ul>
* <li>0 if the two conditions have the same number of expressions
* <li>Less than 0 if "this" has more or more specific media type expressions
* <li>Greater than 0 if "other" has more or more specific media type expressions
* </ul>
* <p>It is assumed that both instances have been obtained via
* {@link #getMatchingCondition(HttpServletRequest)} and each instance contains
* the matching consumable media type expression only or is otherwise empty.
*/
public int compareTo(ConsumesRequestCondition other, HttpServletRequest request) {
if (this.expressions.isEmpty() && other.expressions.isEmpty()) {
return 0;
}
else if (this.expressions.isEmpty()) {
return 1;
}
else if (other.expressions.isEmpty()) {
return -1;
}
else {
return this.expressions.get(0).compareTo(other.expressions.get(0));
}
}
/**
* Parses and matches a single media type expression to a request's 'Content-Type' header.
*/
static class ConsumeMediaTypeExpression extends AbstractMediaTypeExpression {
ConsumeMediaTypeExpression(String expression) {
super(expression);
}
ConsumeMediaTypeExpression(MediaType mediaType, boolean negated) {
super(mediaType, negated);
}
@Override
protected boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeNotSupportedException {
try {
MediaType contentType = StringUtils.hasLength(request.getContentType()) ?
MediaType.parseMediaType(request.getContentType()) :
MediaType.APPLICATION_OCTET_STREAM;
return getMediaType().includes(contentType);
}
catch (IllegalArgumentException ex) {
throw new HttpMediaTypeNotSupportedException(
"Can't parse Content-Type [" + request.getContentType() + "]: " + ex.getMessage());
}
}
}
}
2、ProceduresRequestCondition
ProceduresRequestCondition 用于存储@RequestMapping注解中的produces和headers中的accept。
/**
* A logical disjunction (' || ') request condition to match a request's 'Accept' header
* to a list of media type expressions. Two kinds of media type expressions are
* supported, which are described in {@link RequestMapping#produces()} and
* {@link RequestMapping#headers()} where the header name is 'Accept'.
* Regardless of which syntax is used, the semantics are the same.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
*/
public final class ProducesRequestCondition extends AbstractRequestCondition<ProducesRequestCondition> {
private final List<ProduceMediaTypeExpression> MEDIA_TYPE_ALL_LIST =
Collections.singletonList(new ProduceMediaTypeExpression("*/*"));
private final List<ProduceMediaTypeExpression> expressions;
private final ContentNegotiationManager contentNegotiationManager;
/**
* Creates a new instance from "produces" expressions. If 0 expressions
* are provided in total, this condition will match to any request.
* @param produces expressions with syntax defined by {@link RequestMapping#produces()}
*/
public ProducesRequestCondition(String... produces) {
this(produces, (String[]) null);
}
/**
* Creates a new instance with "produces" and "header" expressions. "Header"
* expressions where the header name is not 'Accept' or have no header value
* defined are ignored. If 0 expressions are provided in total, this condition
* will match to any request.
* @param produces expressions with syntax defined by {@link RequestMapping#produces()}
* @param headers expressions with syntax defined by {@link RequestMapping#headers()}
*/
public ProducesRequestCondition(String[] produces, String[] headers) {
this(produces, headers, null);
}
/**
* Same as {@link #ProducesRequestCondition(String[], String[])} but also
* accepting a {@link ContentNegotiationManager}.
* @param produces expressions with syntax defined by {@link RequestMapping#produces()}
* @param headers expressions with syntax defined by {@link RequestMapping#headers()}
* @param manager used to determine requested media types
*/
public ProducesRequestCondition(String[] produces, String[] headers, ContentNegotiationManager manager) {
this.expressions = new ArrayList<ProduceMediaTypeExpression>(parseExpressions(produces, headers));
Collections.sort(this.expressions);
this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
}
/**
* Private constructor with already parsed media type expressions.
*/
private ProducesRequestCondition(Collection<ProduceMediaTypeExpression> expressions, ContentNegotiationManager manager) {
this.expressions = new ArrayList<ProduceMediaTypeExpression>(expressions);
Collections.sort(this.expressions);
this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
}
private Set<ProduceMediaTypeExpression> parseExpressions(String[] produces, String[] headers) {
Set<ProduceMediaTypeExpression> result = new LinkedHashSet<ProduceMediaTypeExpression>();
if (headers != null) {
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if ("Accept".equalsIgnoreCase(expr.name)) {
for( MediaType mediaType : MediaType.parseMediaTypes(expr.value)) {
result.add(new ProduceMediaTypeExpression(mediaType, expr.isNegated));
}
}
}
}
if (produces != null) {
for (String produce : produces) {
result.add(new ProduceMediaTypeExpression(produce));
}
}
return result;
}
/**
* Return the contained "produces" expressions.
*/
public Set<MediaTypeExpression> getExpressions() {
return new LinkedHashSet<MediaTypeExpression>(this.expressions);
}
/**
* Return the contained producible media types excluding negated expressions.
*/
public Set<MediaType> getProducibleMediaTypes() {
Set<MediaType> result = new LinkedHashSet<MediaType>();
for (ProduceMediaTypeExpression expression : this.expressions) {
if (!expression.isNegated()) {
result.add(expression.getMediaType());
}
}
return result;
}
/**
* Whether the condition has any media type expressions.
*/
public boolean isEmpty() {
return this.expressions.isEmpty();
}
@Override
protected List<ProduceMediaTypeExpression> getContent() {
return this.expressions;
}
@Override
protected String getToStringInfix() {
return " || ";
}
/**
* Returns the "other" instance if it has any expressions; returns "this"
* instance otherwise. Practically that means a method-level "produces"
* overrides a type-level "produces" condition.
*/
public ProducesRequestCondition combine(ProducesRequestCondition other) {
return !other.expressions.isEmpty() ? other : this;
}
/**
* Checks if any of the contained media type expressions match the given
* request 'Content-Type' header and returns an instance that is guaranteed
* to contain matching expressions only. The match is performed via
* {@link MediaType#isCompatibleWith(MediaType)}.
* @param request the current request
* @return the same instance if there are no expressions;
* or a new condition with matching expressions;
* or {@code null} if no expressions match.
*/
public ProducesRequestCondition getMatchingCondition(HttpServletRequest request) {
if (isEmpty()) {
return this;
}
Set<ProduceMediaTypeExpression> result = new LinkedHashSet<ProduceMediaTypeExpression>(expressions);
for (Iterator<ProduceMediaTypeExpression> iterator = result.iterator(); iterator.hasNext();) {
ProduceMediaTypeExpression expression = iterator.next();
if (!expression.match(request)) {
iterator.remove();
}
}
return (result.isEmpty()) ? null : new ProducesRequestCondition(result, this.contentNegotiationManager);
}
/**
* Compares this and another "produces" condition as follows:
* <ol>
* <li>Sort 'Accept' header media types by quality value via
* {@link MediaType#sortByQualityValue(List)} and iterate the list.
* <li>Get the first index of matching media types in each "produces"
* condition first matching with {@link MediaType#equals(Object)} and
* then with {@link MediaType#includes(MediaType)}.
* <li>If a lower index is found, the condition at that index wins.
* <li>If both indexes are equal, the media types at the index are
* compared further with {@link MediaType#SPECIFICITY_COMPARATOR}.
* </ol>
* <p>It is assumed that both instances have been obtained via
* {@link #getMatchingCondition(HttpServletRequest)} and each instance
* contains the matching producible media type expression only or
* is otherwise empty.
*/
public int compareTo(ProducesRequestCondition other, HttpServletRequest request) {
try {
List<MediaType> acceptedMediaTypes = getAcceptedMediaTypes(request);
for (MediaType acceptedMediaType : acceptedMediaTypes) {
int thisIndex = this.indexOfEqualMediaType(acceptedMediaType);
int otherIndex = other.indexOfEqualMediaType(acceptedMediaType);
int result = compareMatchingMediaTypes(this, thisIndex, other, otherIndex);
if (result != 0) {
return result;
}
thisIndex = this.indexOfIncludedMediaType(acceptedMediaType);
otherIndex = other.indexOfIncludedMediaType(acceptedMediaType);
result = compareMatchingMediaTypes(this, thisIndex, other, otherIndex);
if (result != 0) {
return result;
}
}
return 0;
}
catch (HttpMediaTypeNotAcceptableException ex) {
// should never happen
throw new IllegalStateException("Cannot compare without having any requested media types", ex);
}
}
private List<MediaType> getAcceptedMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
List<MediaType> mediaTypes = this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
return mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes;
}
private int indexOfEqualMediaType(MediaType mediaType) {
for (int i = 0; i < getExpressionsToCompare().size(); i++) {
MediaType currentMediaType = getExpressionsToCompare().get(i).getMediaType();
if (mediaType.getType().equalsIgnoreCase(currentMediaType.getType()) &&
mediaType.getSubtype().equalsIgnoreCase(currentMediaType.getSubtype())) {
return i;
}
}
return -1;
}
private int indexOfIncludedMediaType(MediaType mediaType) {
for (int i = 0; i < getExpressionsToCompare().size(); i++) {
if (mediaType.includes(getExpressionsToCompare().get(i).getMediaType())) {
return i;
}
}
return -1;
}
private int compareMatchingMediaTypes(ProducesRequestCondition condition1, int index1,
ProducesRequestCondition condition2, int index2) {
int result = 0;
if (index1 != index2) {
result = index2 - index1;
}
else if (index1 != -1) {
ProduceMediaTypeExpression expr1 = condition1.getExpressionsToCompare().get(index1);
ProduceMediaTypeExpression expr2 = condition2.getExpressionsToCompare().get(index2);
result = expr1.compareTo(expr2);
result = (result != 0) ? result : expr1.getMediaType().compareTo(expr2.getMediaType());
}
return result;
}
/**
* Return the contained "produces" expressions or if that's empty, a list
* with a {@code MediaType_ALL} expression.
*/
private List<ProduceMediaTypeExpression> getExpressionsToCompare() {
return (this.expressions.isEmpty() ? MEDIA_TYPE_ALL_LIST : this.expressions);
}
/**
* Parses and matches a single media type expression to a request's 'Accept' header.
*/
class ProduceMediaTypeExpression extends AbstractMediaTypeExpression {
ProduceMediaTypeExpression(MediaType mediaType, boolean negated) {
super(mediaType, negated);
}
ProduceMediaTypeExpression(String expression) {
super(expression);
}
@Override
protected boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
List<MediaType> acceptedMediaTypes = getAcceptedMediaTypes(request);
for (MediaType acceptedMediaType : acceptedMediaTypes) {
if (getMediaType().isCompatibleWith(acceptedMediaType)) {
return true;
}
}
return false;
}
}
}