001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.http.server.reactive; 018 019import java.io.UnsupportedEncodingException; 020import java.net.URI; 021import java.net.URLDecoder; 022import java.util.regex.Matcher; 023import java.util.regex.Pattern; 024 025import org.apache.commons.logging.Log; 026 027import org.springframework.http.HttpCookie; 028import org.springframework.http.HttpHeaders; 029import org.springframework.http.HttpLogging; 030import org.springframework.http.server.RequestPath; 031import org.springframework.lang.Nullable; 032import org.springframework.util.CollectionUtils; 033import org.springframework.util.LinkedMultiValueMap; 034import org.springframework.util.MultiValueMap; 035import org.springframework.util.ObjectUtils; 036import org.springframework.util.StringUtils; 037 038/** 039 * Common base class for {@link ServerHttpRequest} implementations. 040 * 041 * @author Rossen Stoyanchev 042 * @since 5.0 043 */ 044public abstract class AbstractServerHttpRequest implements ServerHttpRequest { 045 046 private static final Pattern QUERY_PATTERN = Pattern.compile("([^&=]+)(=?)([^&]+)?"); 047 048 049 protected final Log logger = HttpLogging.forLogName(getClass()); 050 051 private final URI uri; 052 053 private final RequestPath path; 054 055 private final HttpHeaders headers; 056 057 @Nullable 058 private MultiValueMap<String, String> queryParams; 059 060 @Nullable 061 private MultiValueMap<String, HttpCookie> cookies; 062 063 @Nullable 064 private SslInfo sslInfo; 065 066 @Nullable 067 private String id; 068 069 @Nullable 070 private String logPrefix; 071 072 073 /** 074 * Constructor with the URI and headers for the request. 075 * @param uri the URI for the request 076 * @param contextPath the context path for the request 077 * @param headers the headers for the request 078 */ 079 public AbstractServerHttpRequest(URI uri, @Nullable String contextPath, HttpHeaders headers) { 080 this.uri = uri; 081 this.path = RequestPath.parse(uri, contextPath); 082 this.headers = HttpHeaders.readOnlyHttpHeaders(headers); 083 } 084 085 086 @Override 087 public String getId() { 088 if (this.id == null) { 089 this.id = initId(); 090 if (this.id == null) { 091 this.id = ObjectUtils.getIdentityHexString(this); 092 } 093 } 094 return this.id; 095 } 096 097 /** 098 * Obtain the request id to use, or {@code null} in which case the Object 099 * identity of this request instance is used. 100 * @since 5.1 101 */ 102 @Nullable 103 protected String initId() { 104 return null; 105 } 106 107 @Override 108 public URI getURI() { 109 return this.uri; 110 } 111 112 @Override 113 public RequestPath getPath() { 114 return this.path; 115 } 116 117 @Override 118 public HttpHeaders getHeaders() { 119 return this.headers; 120 } 121 122 @Override 123 public MultiValueMap<String, String> getQueryParams() { 124 if (this.queryParams == null) { 125 this.queryParams = CollectionUtils.unmodifiableMultiValueMap(initQueryParams()); 126 } 127 return this.queryParams; 128 } 129 130 /** 131 * A method for parsing of the query into name-value pairs. The return 132 * value is turned into an immutable map and cached. 133 * <p>Note that this method is invoked lazily on first access to 134 * {@link #getQueryParams()}. The invocation is not synchronized but the 135 * parsing is thread-safe nevertheless. 136 */ 137 protected MultiValueMap<String, String> initQueryParams() { 138 MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>(); 139 String query = getURI().getRawQuery(); 140 if (query != null) { 141 Matcher matcher = QUERY_PATTERN.matcher(query); 142 while (matcher.find()) { 143 String name = decodeQueryParam(matcher.group(1)); 144 String eq = matcher.group(2); 145 String value = matcher.group(3); 146 value = (value != null ? decodeQueryParam(value) : (StringUtils.hasLength(eq) ? "" : null)); 147 queryParams.add(name, value); 148 } 149 } 150 return queryParams; 151 } 152 153 @SuppressWarnings("deprecation") 154 private String decodeQueryParam(String value) { 155 try { 156 return URLDecoder.decode(value, "UTF-8"); 157 } 158 catch (UnsupportedEncodingException ex) { 159 if (logger.isWarnEnabled()) { 160 logger.warn(getLogPrefix() + "Could not decode query value [" + value + "] as 'UTF-8'. " + 161 "Falling back on default encoding: " + ex.getMessage()); 162 } 163 return URLDecoder.decode(value); 164 } 165 } 166 167 @Override 168 public MultiValueMap<String, HttpCookie> getCookies() { 169 if (this.cookies == null) { 170 this.cookies = CollectionUtils.unmodifiableMultiValueMap(initCookies()); 171 } 172 return this.cookies; 173 } 174 175 /** 176 * Obtain the cookies from the underlying "native" request and adapt those to 177 * an {@link HttpCookie} map. The return value is turned into an immutable 178 * map and cached. 179 * <p>Note that this method is invoked lazily on access to 180 * {@link #getCookies()}. Sub-classes should synchronize cookie 181 * initialization if the underlying "native" request does not provide 182 * thread-safe access to cookie data. 183 */ 184 protected abstract MultiValueMap<String, HttpCookie> initCookies(); 185 186 @Nullable 187 @Override 188 public SslInfo getSslInfo() { 189 if (this.sslInfo == null) { 190 this.sslInfo = initSslInfo(); 191 } 192 return this.sslInfo; 193 } 194 195 /** 196 * Obtain SSL session information from the underlying "native" request. 197 * @return the session information, or {@code null} if none available 198 * @since 5.0.2 199 */ 200 @Nullable 201 protected abstract SslInfo initSslInfo(); 202 203 /** 204 * Return the underlying server response. 205 * <p><strong>Note:</strong> This is exposed mainly for internal framework 206 * use such as WebSocket upgrades in the spring-webflux module. 207 */ 208 public abstract <T> T getNativeRequest(); 209 210 /** 211 * For internal use in logging at the HTTP adapter layer. 212 * @since 5.1 213 */ 214 String getLogPrefix() { 215 if (this.logPrefix == null) { 216 this.logPrefix = "[" + getId() + "] "; 217 } 218 return this.logPrefix; 219 } 220 221}