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