001/* 002 * Copyright 2002-2018 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; 018 019import java.io.ByteArrayInputStream; 020import java.io.ByteArrayOutputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.OutputStreamWriter; 024import java.io.Writer; 025import java.net.InetSocketAddress; 026import java.net.URI; 027import java.net.URISyntaxException; 028import java.net.URLEncoder; 029import java.nio.charset.Charset; 030import java.nio.charset.StandardCharsets; 031import java.security.Principal; 032import java.util.Arrays; 033import java.util.Enumeration; 034import java.util.Iterator; 035import java.util.List; 036import java.util.Map; 037 038import javax.servlet.http.HttpServletRequest; 039 040import org.springframework.http.HttpHeaders; 041import org.springframework.http.HttpMethod; 042import org.springframework.http.InvalidMediaTypeException; 043import org.springframework.http.MediaType; 044import org.springframework.lang.Nullable; 045import org.springframework.util.Assert; 046import org.springframework.util.LinkedCaseInsensitiveMap; 047import org.springframework.util.StringUtils; 048 049/** 050 * {@link ServerHttpRequest} implementation that is based on a {@link HttpServletRequest}. 051 * 052 * @author Arjen Poutsma 053 * @author Rossen Stoyanchev 054 * @author Juergen Hoeller 055 * @since 3.0 056 */ 057public class ServletServerHttpRequest implements ServerHttpRequest { 058 059 protected static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded"; 060 061 protected static final Charset FORM_CHARSET = StandardCharsets.UTF_8; 062 063 064 private final HttpServletRequest servletRequest; 065 066 @Nullable 067 private URI uri; 068 069 @Nullable 070 private HttpHeaders headers; 071 072 @Nullable 073 private ServerHttpAsyncRequestControl asyncRequestControl; 074 075 076 /** 077 * Construct a new instance of the ServletServerHttpRequest based on the 078 * given {@link HttpServletRequest}. 079 * @param servletRequest the servlet request 080 */ 081 public ServletServerHttpRequest(HttpServletRequest servletRequest) { 082 Assert.notNull(servletRequest, "HttpServletRequest must not be null"); 083 this.servletRequest = servletRequest; 084 } 085 086 087 /** 088 * Returns the {@code HttpServletRequest} this object is based on. 089 */ 090 public HttpServletRequest getServletRequest() { 091 return this.servletRequest; 092 } 093 094 @Override 095 @Nullable 096 public HttpMethod getMethod() { 097 return HttpMethod.resolve(this.servletRequest.getMethod()); 098 } 099 100 @Override 101 public String getMethodValue() { 102 return this.servletRequest.getMethod(); 103 } 104 105 @Override 106 public URI getURI() { 107 if (this.uri == null) { 108 String urlString = null; 109 boolean hasQuery = false; 110 try { 111 StringBuffer url = this.servletRequest.getRequestURL(); 112 String query = this.servletRequest.getQueryString(); 113 hasQuery = StringUtils.hasText(query); 114 if (hasQuery) { 115 url.append('?').append(query); 116 } 117 urlString = url.toString(); 118 this.uri = new URI(urlString); 119 } 120 catch (URISyntaxException ex) { 121 if (!hasQuery) { 122 throw new IllegalStateException( 123 "Could not resolve HttpServletRequest as URI: " + urlString, ex); 124 } 125 // Maybe a malformed query string... try plain request URL 126 try { 127 urlString = this.servletRequest.getRequestURL().toString(); 128 this.uri = new URI(urlString); 129 } 130 catch (URISyntaxException ex2) { 131 throw new IllegalStateException( 132 "Could not resolve HttpServletRequest as URI: " + urlString, ex2); 133 } 134 } 135 } 136 return this.uri; 137 } 138 139 @Override 140 public HttpHeaders getHeaders() { 141 if (this.headers == null) { 142 this.headers = new HttpHeaders(); 143 144 for (Enumeration<?> names = this.servletRequest.getHeaderNames(); names.hasMoreElements();) { 145 String headerName = (String) names.nextElement(); 146 for (Enumeration<?> headerValues = this.servletRequest.getHeaders(headerName); 147 headerValues.hasMoreElements();) { 148 String headerValue = (String) headerValues.nextElement(); 149 this.headers.add(headerName, headerValue); 150 } 151 } 152 153 // HttpServletRequest exposes some headers as properties: 154 // we should include those if not already present 155 try { 156 MediaType contentType = this.headers.getContentType(); 157 if (contentType == null) { 158 String requestContentType = this.servletRequest.getContentType(); 159 if (StringUtils.hasLength(requestContentType)) { 160 contentType = MediaType.parseMediaType(requestContentType); 161 this.headers.setContentType(contentType); 162 } 163 } 164 if (contentType != null && contentType.getCharset() == null) { 165 String requestEncoding = this.servletRequest.getCharacterEncoding(); 166 if (StringUtils.hasLength(requestEncoding)) { 167 Charset charSet = Charset.forName(requestEncoding); 168 Map<String, String> params = new LinkedCaseInsensitiveMap<>(); 169 params.putAll(contentType.getParameters()); 170 params.put("charset", charSet.toString()); 171 MediaType mediaType = new MediaType(contentType.getType(), contentType.getSubtype(), params); 172 this.headers.setContentType(mediaType); 173 } 174 } 175 } 176 catch (InvalidMediaTypeException ex) { 177 // Ignore: simply not exposing an invalid content type in HttpHeaders... 178 } 179 180 if (this.headers.getContentLength() < 0) { 181 int requestContentLength = this.servletRequest.getContentLength(); 182 if (requestContentLength != -1) { 183 this.headers.setContentLength(requestContentLength); 184 } 185 } 186 } 187 188 return this.headers; 189 } 190 191 @Override 192 public Principal getPrincipal() { 193 return this.servletRequest.getUserPrincipal(); 194 } 195 196 @Override 197 public InetSocketAddress getLocalAddress() { 198 return new InetSocketAddress(this.servletRequest.getLocalName(), this.servletRequest.getLocalPort()); 199 } 200 201 @Override 202 public InetSocketAddress getRemoteAddress() { 203 return new InetSocketAddress(this.servletRequest.getRemoteHost(), this.servletRequest.getRemotePort()); 204 } 205 206 @Override 207 public InputStream getBody() throws IOException { 208 if (isFormPost(this.servletRequest)) { 209 return getBodyFromServletRequestParameters(this.servletRequest); 210 } 211 else { 212 return this.servletRequest.getInputStream(); 213 } 214 } 215 216 @Override 217 public ServerHttpAsyncRequestControl getAsyncRequestControl(ServerHttpResponse response) { 218 if (this.asyncRequestControl == null) { 219 if (!ServletServerHttpResponse.class.isInstance(response)) { 220 throw new IllegalArgumentException( 221 "Response must be a ServletServerHttpResponse: " + response.getClass()); 222 } 223 ServletServerHttpResponse servletServerResponse = (ServletServerHttpResponse) response; 224 this.asyncRequestControl = new ServletServerHttpAsyncRequestControl(this, servletServerResponse); 225 } 226 return this.asyncRequestControl; 227 } 228 229 230 private static boolean isFormPost(HttpServletRequest request) { 231 String contentType = request.getContentType(); 232 return (contentType != null && contentType.contains(FORM_CONTENT_TYPE) && 233 HttpMethod.POST.matches(request.getMethod())); 234 } 235 236 /** 237 * Use {@link javax.servlet.ServletRequest#getParameterMap()} to reconstruct the 238 * body of a form 'POST' providing a predictable outcome as opposed to reading 239 * from the body, which can fail if any other code has used the ServletRequest 240 * to access a parameter, thus causing the input stream to be "consumed". 241 */ 242 private static InputStream getBodyFromServletRequestParameters(HttpServletRequest request) throws IOException { 243 ByteArrayOutputStream bos = new ByteArrayOutputStream(1024); 244 Writer writer = new OutputStreamWriter(bos, FORM_CHARSET); 245 246 Map<String, String[]> form = request.getParameterMap(); 247 for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext();) { 248 String name = nameIterator.next(); 249 List<String> values = Arrays.asList(form.get(name)); 250 for (Iterator<String> valueIterator = values.iterator(); valueIterator.hasNext();) { 251 String value = valueIterator.next(); 252 writer.write(URLEncoder.encode(name, FORM_CHARSET.name())); 253 if (value != null) { 254 writer.write('='); 255 writer.write(URLEncoder.encode(value, FORM_CHARSET.name())); 256 if (valueIterator.hasNext()) { 257 writer.write('&'); 258 } 259 } 260 } 261 if (nameIterator.hasNext()) { 262 writer.append('&'); 263 } 264 } 265 writer.flush(); 266 267 return new ByteArrayInputStream(bos.toByteArray()); 268 } 269 270}