Fix Request/Response attributes, add more assertions in tests
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-adapter-parent</artifactId>
|
||||
<version>3.2.9-SNAPSHOT</version>
|
||||
<version>3.2.10-SNAPSHOT</version>
|
||||
</parent>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023-2023 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.serverless.web;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
class HeaderValueHolder {
|
||||
|
||||
private final List<Object> values = new LinkedList<>();
|
||||
|
||||
void setValue(@Nullable Object value) {
|
||||
this.values.clear();
|
||||
if (value != null) {
|
||||
this.values.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
void addValue(Object value) {
|
||||
this.values.add(value);
|
||||
}
|
||||
|
||||
void addValues(Collection<?> values) {
|
||||
this.values.addAll(values);
|
||||
}
|
||||
|
||||
void addValueArray(Object values) {
|
||||
CollectionUtils.mergeArrayIntoCollection(values, this.values);
|
||||
}
|
||||
|
||||
List<Object> getValues() {
|
||||
return Collections.unmodifiableList(this.values);
|
||||
}
|
||||
|
||||
List<String> getStringValues() {
|
||||
List<String> stringList = new ArrayList<>(this.values.size());
|
||||
for (Object value : this.values) {
|
||||
stringList.add(value.toString());
|
||||
}
|
||||
return Collections.unmodifiableList(stringList);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Object getValue() {
|
||||
return (!this.values.isEmpty() ? this.values.get(0) : null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getStringValue() {
|
||||
return (!this.values.isEmpty() ? String.valueOf(this.values.get(0)) : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.values.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Principal;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
@@ -58,18 +59,19 @@ import javax.servlet.http.HttpUpgradeHandler;
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
*/
|
||||
public class ProxyHttpServletRequest implements HttpServletRequest {
|
||||
|
||||
private static final String CHARSET_PREFIX = "charset=";
|
||||
|
||||
private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
|
||||
|
||||
private static final BufferedReader EMPTY_BUFFERED_READER = new BufferedReader(new StringReader(""));
|
||||
@@ -97,9 +99,6 @@ public class ProxyHttpServletRequest implements HttpServletRequest {
|
||||
@Nullable
|
||||
private byte[] content;
|
||||
|
||||
@Nullable
|
||||
private String contentType;
|
||||
|
||||
@Nullable
|
||||
private ServletInputStream inputStream;
|
||||
|
||||
@@ -117,17 +116,13 @@ public class ProxyHttpServletRequest implements HttpServletRequest {
|
||||
|
||||
private DispatcherType dispatcherType = DispatcherType.REQUEST;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// HttpServletRequest properties
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
@Nullable
|
||||
private String authType;
|
||||
|
||||
@Nullable
|
||||
private Cookie[] cookies;
|
||||
|
||||
private final Map<String, HeaderValueHolder> headers = new LinkedCaseInsensitiveMap<>();
|
||||
private final HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
@Nullable
|
||||
private String method;
|
||||
@@ -202,18 +197,6 @@ public class ProxyHttpServletRequest implements HttpServletRequest {
|
||||
@Override
|
||||
public void setCharacterEncoding(@Nullable String characterEncoding) {
|
||||
this.characterEncoding = characterEncoding;
|
||||
updateContentTypeHeader();
|
||||
}
|
||||
|
||||
private void updateContentTypeHeader() {
|
||||
if (StringUtils.hasLength(this.contentType)) {
|
||||
String value = this.contentType;
|
||||
if (StringUtils.hasLength(this.characterEncoding)
|
||||
&& !this.contentType.toLowerCase().contains(CHARSET_PREFIX)) {
|
||||
value += ';' + CHARSET_PREFIX + this.characterEncoding;
|
||||
}
|
||||
doAddHeaderValue(HttpHeaders.CONTENT_TYPE, value, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,13 +245,13 @@ public class ProxyHttpServletRequest implements HttpServletRequest {
|
||||
*/
|
||||
@Nullable
|
||||
public String getContentAsString() throws IllegalStateException, UnsupportedEncodingException {
|
||||
Assert.state(this.characterEncoding != null, "Cannot get content as a String for a null character encoding. "
|
||||
+ "Consider setting the characterEncoding in the request.");
|
||||
// Assert.state(this.characterEncoding != null, "Cannot get content as a String for a null character encoding. "
|
||||
// + "Consider setting the characterEncoding in the request.");
|
||||
|
||||
if (this.content == null) {
|
||||
return null;
|
||||
}
|
||||
return new String(this.content, this.characterEncoding);
|
||||
return new String(this.content, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -282,29 +265,13 @@ public class ProxyHttpServletRequest implements HttpServletRequest {
|
||||
}
|
||||
|
||||
public void setContentType(@Nullable String contentType) {
|
||||
this.contentType = contentType;
|
||||
if (contentType != null) {
|
||||
try {
|
||||
MediaType mediaType = MediaType.parseMediaType(contentType);
|
||||
if (mediaType.getCharset() != null) {
|
||||
this.characterEncoding = mediaType.getCharset().name();
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
// Try to get charset value anyway
|
||||
int charsetIndex = contentType.toLowerCase().indexOf(CHARSET_PREFIX);
|
||||
if (charsetIndex != -1) {
|
||||
this.characterEncoding = contentType.substring(charsetIndex + CHARSET_PREFIX.length());
|
||||
}
|
||||
}
|
||||
updateContentTypeHeader();
|
||||
}
|
||||
this.headers.set(HttpHeaders.CONTENT_TYPE, contentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getContentType() {
|
||||
return this.contentType;
|
||||
return this.headers.containsKey(HttpHeaders.CONTENT_TYPE) ? this.headers.get(HttpHeaders.CONTENT_TYPE).get(0) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -564,37 +531,6 @@ public class ProxyHttpServletRequest implements HttpServletRequest {
|
||||
this.attributes.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new preferred locale, before any existing locales.
|
||||
*
|
||||
* @see #setPreferredLocales
|
||||
*/
|
||||
public void addPreferredLocale(Locale locale) {
|
||||
Assert.notNull(locale, "Locale must not be null");
|
||||
this.locales.addFirst(locale);
|
||||
updateAcceptLanguageHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of preferred locales, in descending order, effectively replacing
|
||||
* any existing locales.
|
||||
*
|
||||
* @since 3.2
|
||||
* @see #addPreferredLocale
|
||||
*/
|
||||
public void setPreferredLocales(List<Locale> locales) {
|
||||
Assert.notEmpty(locales, "Locale list must not be empty");
|
||||
this.locales.clear();
|
||||
this.locales.addAll(locales);
|
||||
updateAcceptLanguageHeader();
|
||||
}
|
||||
|
||||
private void updateAcceptLanguageHeader() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setAcceptLanguageAsLocales(this.locales);
|
||||
doAddHeaderValue(HttpHeaders.ACCEPT_LANGUAGE, headers.getFirst(HttpHeaders.ACCEPT_LANGUAGE), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first preferred {@linkplain Locale locale} configured in this mock
|
||||
* request.
|
||||
@@ -758,104 +694,86 @@ public class ProxyHttpServletRequest implements HttpServletRequest {
|
||||
return this.cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an HTTP header entry for the given name.
|
||||
* <p>
|
||||
* While this method can take any {@code Object} as a parameter, it is
|
||||
* recommended to use the following types:
|
||||
* <ul>
|
||||
* <li>String or any Object to be converted using {@code toString()}; see
|
||||
* {@link #getHeader}.</li>
|
||||
* <li>String, Number, or Date for date headers; see
|
||||
* {@link #getDateHeader}.</li>
|
||||
* <li>String or Number for integer headers; see {@link #getIntHeader}.</li>
|
||||
* <li>{@code String[]} or {@code Collection<String>} for multiple values; see
|
||||
* {@link #getHeaders}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see #getHeaderNames
|
||||
* @see #getHeaders
|
||||
* @see #getHeader
|
||||
* @see #getDateHeader
|
||||
*/
|
||||
public void addHeader(String name, Object value) {
|
||||
if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name) && !this.headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
|
||||
setContentType(value.toString());
|
||||
}
|
||||
else if (HttpHeaders.ACCEPT_LANGUAGE.equalsIgnoreCase(name)
|
||||
&& !this.headers.containsKey(HttpHeaders.ACCEPT_LANGUAGE)) {
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add(HttpHeaders.ACCEPT_LANGUAGE, value.toString());
|
||||
List<Locale> locales = headers.getAcceptLanguageAsLocales();
|
||||
this.locales.clear();
|
||||
this.locales.addAll(locales);
|
||||
if (this.locales.isEmpty()) {
|
||||
this.locales.add(Locale.ENGLISH);
|
||||
}
|
||||
@Override
|
||||
@Nullable
|
||||
public String getHeader(String name) {
|
||||
return this.headers.containsKey(name) ? this.headers.get(name).toString() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getHeaders(String name) {
|
||||
return Collections.enumeration(this.headers.containsKey(name) ? this.headers.get(name) : new LinkedList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getHeaderNames() {
|
||||
return Collections.enumeration(this.headers.keySet());
|
||||
}
|
||||
|
||||
public void setHeader(String name, @Nullable String value) {
|
||||
this.headers.set(name, value);
|
||||
}
|
||||
|
||||
public void addHeader(String name, @Nullable String value) {
|
||||
this.headers.add(name, value);
|
||||
}
|
||||
|
||||
public void addHeaders(MultiValueMap<String, String> headers) {
|
||||
this.headers.addAll(headers);
|
||||
}
|
||||
|
||||
public void setHeaders(MultiValueMap<String, String> headers) {
|
||||
this.headers.clear();
|
||||
this.addHeaders(headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntHeader(String name) {
|
||||
List<String> header = this.headers.get(name);
|
||||
if (!CollectionUtils.isEmpty(header) && header.size() == 1) {
|
||||
Object value = header.get(0);
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).intValue();
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
// Invalid Accept-Language format -> just store plain header
|
||||
else if (value instanceof String) {
|
||||
return Integer.parseInt((String) value);
|
||||
}
|
||||
else if (value != null) {
|
||||
throw new NumberFormatException("Value for header '" + name + "' is not a Number: " + value);
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
doAddHeaderValue(name, value, true);
|
||||
}
|
||||
else {
|
||||
doAddHeaderValue(name, value, false);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void doAddHeaderValue(String name, @Nullable Object value, boolean replace) {
|
||||
HeaderValueHolder header = this.headers.get(name);
|
||||
Assert.notNull(value, "Header value must not be null");
|
||||
if (header == null || replace) {
|
||||
header = new HeaderValueHolder();
|
||||
this.headers.put(name, header);
|
||||
}
|
||||
if (value instanceof Collection) {
|
||||
header.addValues((Collection<?>) value);
|
||||
}
|
||||
else if (value.getClass().isArray()) {
|
||||
header.addValueArray(value);
|
||||
}
|
||||
else {
|
||||
header.addValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the long timestamp for the date header with the given {@code name}.
|
||||
* <p>
|
||||
* If the internal value representation is a String, this method will try to
|
||||
* parse it as a date using the supported date formats:
|
||||
* <ul>
|
||||
* <li>"EEE, dd MMM yyyy HH:mm:ss zzz"</li>
|
||||
* <li>"EEE, dd-MMM-yy HH:mm:ss zzz"</li>
|
||||
* <li>"EEE MMM dd HH:mm:ss yyyy"</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param name the header name
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section
|
||||
* 7.1.1.1 of RFC 7231</a>
|
||||
*/
|
||||
@Override
|
||||
public long getDateHeader(String name) {
|
||||
HeaderValueHolder header = this.headers.get(name);
|
||||
Object value = (header != null ? header.getValue() : null);
|
||||
if (value instanceof Date) {
|
||||
return ((Date) value).getTime();
|
||||
}
|
||||
else if (value instanceof Number) {
|
||||
return ((Number) value).longValue();
|
||||
}
|
||||
else if (value instanceof String) {
|
||||
return parseDateHeader(name, (String) value);
|
||||
}
|
||||
else if (value != null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Value for header '" + name + "' is not a Date, Number, or String: " + value);
|
||||
List<String> header = this.headers.get(name);
|
||||
if (!CollectionUtils.isEmpty(header) && header.size() == 1) {
|
||||
Object value = header.get(0);
|
||||
if (value instanceof Date) {
|
||||
return ((Date) value).getTime();
|
||||
}
|
||||
else if (value instanceof Number) {
|
||||
return ((Number) value).longValue();
|
||||
}
|
||||
else if (value instanceof String) {
|
||||
return parseDateHeader(name, (String) value);
|
||||
}
|
||||
else if (value != null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Value for header '" + name + "' is not a Date, Number, or String: " + value);
|
||||
}
|
||||
else {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return -1L;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -873,42 +791,6 @@ public class ProxyHttpServletRequest implements HttpServletRequest {
|
||||
throw new IllegalArgumentException("Cannot parse date value '" + value + "' for '" + name + "' header");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getHeader(String name) {
|
||||
HeaderValueHolder header = this.headers.get(name);
|
||||
return (header != null ? header.getStringValue() : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getHeaders(String name) {
|
||||
HeaderValueHolder header = this.headers.get(name);
|
||||
return Collections.enumeration(header != null ? header.getStringValues() : new LinkedList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getHeaderNames() {
|
||||
return Collections.enumeration(this.headers.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntHeader(String name) {
|
||||
HeaderValueHolder header = this.headers.get(name);
|
||||
Object value = (header != null ? header.getValue() : null);
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).intValue();
|
||||
}
|
||||
else if (value instanceof String) {
|
||||
return Integer.parseInt((String) value);
|
||||
}
|
||||
else if (value != null) {
|
||||
throw new NumberFormatException("Value for header '" + name + "' is not a Number: " + value);
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public void setMethod(@Nullable String method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@@ -22,16 +22,12 @@ import java.io.PrintWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.WriteListener;
|
||||
@@ -41,86 +37,43 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
*/
|
||||
public class ProxyHttpServletResponse implements HttpServletResponse {
|
||||
|
||||
private static final String CHARSET_PREFIX = "charset=";
|
||||
|
||||
private static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
|
||||
|
||||
private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// ServletResponse properties
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
private boolean outputStreamAccessAllowed = true;
|
||||
|
||||
private String defaultCharacterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
|
||||
|
||||
private String characterEncoding = this.defaultCharacterEncoding;
|
||||
|
||||
/**
|
||||
* {@code true} if the character encoding has been explicitly set through
|
||||
* {@link HttpServletResponse} methods or through a {@code charset} parameter on
|
||||
* the {@code Content-Type}.
|
||||
*/
|
||||
private boolean characterEncodingSet = false;
|
||||
|
||||
private final ByteArrayOutputStream content = new ByteArrayOutputStream(1024);
|
||||
|
||||
private final ServletOutputStream outputStream = new ResponseServletOutputStream();
|
||||
|
||||
private long contentLength = 0;
|
||||
|
||||
private String contentType;
|
||||
|
||||
private int bufferSize = 4096;
|
||||
|
||||
private boolean committed;
|
||||
|
||||
private Locale locale = Locale.getDefault();
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// HttpServletResponse properties
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
private final List<Cookie> cookies = new ArrayList<>();
|
||||
|
||||
private final Map<String, HeaderValueHolder> headers = new LinkedCaseInsensitiveMap<>();
|
||||
private final HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
private int status = HttpServletResponse.SC_OK;
|
||||
|
||||
@Nullable
|
||||
private String errorMessage;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// ServletResponse interface
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void setCharacterEncoding(String characterEncoding) {
|
||||
setExplicitCharacterEncoding(characterEncoding);
|
||||
updateContentTypePropertyAndHeader();
|
||||
}
|
||||
|
||||
private void setExplicitCharacterEncoding(String characterEncoding) {
|
||||
Assert.notNull(characterEncoding, "'characterEncoding' must not be null");
|
||||
this.characterEncoding = characterEncoding;
|
||||
this.characterEncodingSet = true;
|
||||
}
|
||||
|
||||
private void updateContentTypePropertyAndHeader() {
|
||||
if (this.contentType != null) {
|
||||
String value = this.contentType;
|
||||
if (this.characterEncodingSet && !value.toLowerCase().contains(CHARSET_PREFIX)) {
|
||||
value += ';' + CHARSET_PREFIX + getCharacterEncoding();
|
||||
this.contentType = value;
|
||||
}
|
||||
doAddHeaderValue(HttpHeaders.CONTENT_TYPE, value, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,7 +83,6 @@ public class ProxyHttpServletResponse implements HttpServletResponse {
|
||||
|
||||
@Override
|
||||
public ServletOutputStream getOutputStream() {
|
||||
Assert.state(this.outputStreamAccessAllowed, "OutputStream access not allowed");
|
||||
return this.outputStream;
|
||||
}
|
||||
|
||||
@@ -162,24 +114,8 @@ public class ProxyHttpServletResponse implements HttpServletResponse {
|
||||
return this.content.toString(getCharacterEncoding());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content of the response body as a {@code String}, using the provided
|
||||
* {@code fallbackCharset} if no charset has been explicitly defined and
|
||||
* otherwise using the charset specified for the response by the application,
|
||||
* either through {@link HttpServletResponse} methods or through a charset
|
||||
* parameter on the {@code Content-Type}.
|
||||
*
|
||||
* @return the content as a {@code String}
|
||||
* @throws UnsupportedEncodingException if the character encoding is not
|
||||
* supported
|
||||
* @since 5.2
|
||||
* @see #getContentAsString()
|
||||
* @see #setCharacterEncoding(String)
|
||||
* @see #setContentType(String)
|
||||
*/
|
||||
public String getContentAsString(Charset fallbackCharset) throws UnsupportedEncodingException {
|
||||
String charsetName = (this.characterEncodingSet ? getCharacterEncoding() : fallbackCharset.name());
|
||||
return this.content.toString(charsetName);
|
||||
return this.content.toString(getCharacterEncoding());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -224,21 +160,15 @@ public class ProxyHttpServletResponse implements HttpServletResponse {
|
||||
this.content.reset();
|
||||
}
|
||||
|
||||
public void setCommitted(boolean committed) {
|
||||
this.committed = committed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommitted() {
|
||||
return this.committed;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
resetBuffer();
|
||||
this.characterEncoding = this.defaultCharacterEncoding;
|
||||
this.characterEncodingSet = false;
|
||||
this.contentLength = 0;
|
||||
this.contentType = null;
|
||||
this.locale = Locale.getDefault();
|
||||
this.cookies.clear();
|
||||
@@ -249,16 +179,11 @@ public class ProxyHttpServletResponse implements HttpServletResponse {
|
||||
|
||||
@Override
|
||||
public void setLocale(@Nullable Locale locale) {
|
||||
// Although the Javadoc for javax.servlet.ServletResponse.setLocale(Locale) does
|
||||
// not
|
||||
// state how a null value for the supplied Locale should be handled, both Tomcat
|
||||
// and
|
||||
// Jetty simply ignore a null value. So we do the same here.
|
||||
if (locale == null) {
|
||||
return;
|
||||
}
|
||||
this.locale = locale;
|
||||
doAddHeaderValue(HttpHeaders.CONTENT_LANGUAGE, locale.toLanguageTag(), true);
|
||||
this.headers.add(HttpHeaders.CONTENT_LANGUAGE, locale.toLanguageTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -314,8 +239,7 @@ public class ProxyHttpServletResponse implements HttpServletResponse {
|
||||
@Override
|
||||
@Nullable
|
||||
public String getHeader(String name) {
|
||||
HeaderValueHolder header = this.headers.get(name);
|
||||
return (header != null ? header.getStringValue() : null);
|
||||
return this.headers.containsKey(name) ? this.headers.get(name).toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -331,13 +255,7 @@ public class ProxyHttpServletResponse implements HttpServletResponse {
|
||||
*/
|
||||
@Override
|
||||
public List<String> getHeaders(String name) {
|
||||
HeaderValueHolder header = this.headers.get(name);
|
||||
if (header != null) {
|
||||
return header.getStringValues();
|
||||
}
|
||||
else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return this.headers.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,24 +268,7 @@ public class ProxyHttpServletResponse implements HttpServletResponse {
|
||||
*/
|
||||
@Nullable
|
||||
public Object getHeaderValue(String name) {
|
||||
HeaderValueHolder header = this.headers.get(name);
|
||||
return (header != null ? header.getValue() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all values for the given header as a List of value objects.
|
||||
*
|
||||
* @param name the name of the header
|
||||
* @return the associated header values, or an empty List if none
|
||||
*/
|
||||
public List<Object> getHeaderValues(String name) {
|
||||
HeaderValueHolder header = this.headers.get(name);
|
||||
if (header != null) {
|
||||
return header.getValues();
|
||||
}
|
||||
else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return this.headers.containsKey(name) ? this.headers.get(name).get(0) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -411,14 +312,12 @@ public class ProxyHttpServletResponse implements HttpServletResponse {
|
||||
Assert.state(!isCommitted(), "Cannot set error status - response is already committed");
|
||||
this.status = status;
|
||||
this.errorMessage = errorMessage;
|
||||
setCommitted(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int status) throws IOException {
|
||||
Assert.state(!isCommitted(), "Cannot set error status - response is already committed");
|
||||
this.status = status;
|
||||
setCommitted(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -427,7 +326,6 @@ public class ProxyHttpServletResponse implements HttpServletResponse {
|
||||
Assert.notNull(url, "Redirect URL must not be null");
|
||||
setHeader(HttpHeaders.LOCATION, url);
|
||||
setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
|
||||
setCommitted(true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -437,25 +335,12 @@ public class ProxyHttpServletResponse implements HttpServletResponse {
|
||||
|
||||
@Override
|
||||
public void setDateHeader(String name, long value) {
|
||||
setHeaderValue(name, formatDate(value));
|
||||
this.headers.set(name, formatDate(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDateHeader(String name, long value) {
|
||||
addHeaderValue(name, formatDate(value));
|
||||
}
|
||||
|
||||
public long getDateHeader(String name) {
|
||||
String headerValue = getHeader(name);
|
||||
if (headerValue == null) {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
return newDateFormat().parse(getHeader(name)).getTime();
|
||||
}
|
||||
catch (ParseException ex) {
|
||||
throw new IllegalArgumentException("Value for header '" + name + "' is not a valid Date: " + headerValue);
|
||||
}
|
||||
this.headers.add(name, formatDate(value));
|
||||
}
|
||||
|
||||
private String formatDate(long date) {
|
||||
@@ -464,55 +349,27 @@ public class ProxyHttpServletResponse implements HttpServletResponse {
|
||||
|
||||
private DateFormat newDateFormat() {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.US);
|
||||
dateFormat.setTimeZone(GMT);
|
||||
return dateFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, @Nullable String value) {
|
||||
setHeaderValue(name, value);
|
||||
this.headers.set(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeader(String name, @Nullable String value) {
|
||||
addHeaderValue(name, value);
|
||||
this.headers.add(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIntHeader(String name, int value) {
|
||||
setHeaderValue(name, value);
|
||||
this.headers.set(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addIntHeader(String name, int value) {
|
||||
addHeaderValue(name, value);
|
||||
}
|
||||
|
||||
private void setHeaderValue(String name, @Nullable Object value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
boolean replaceHeader = true;
|
||||
doAddHeaderValue(name, value, replaceHeader);
|
||||
}
|
||||
|
||||
private void addHeaderValue(String name, @Nullable Object value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
boolean replaceHeader = false;
|
||||
doAddHeaderValue(name, value, replaceHeader);
|
||||
}
|
||||
|
||||
private void doAddHeaderValue(String name, Object value, boolean replace) {
|
||||
Assert.notNull(value, "Header value must not be null");
|
||||
HeaderValueHolder header = this.headers.computeIfAbsent(name, key -> new HeaderValueHolder());
|
||||
if (replace) {
|
||||
header.setValue(value);
|
||||
}
|
||||
else {
|
||||
header.addValue(value);
|
||||
}
|
||||
this.headers.add(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -538,20 +395,6 @@ public class ProxyHttpServletResponse implements HttpServletResponse {
|
||||
return this.errorMessage;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Methods for MockRequestDispatcher
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
@Nullable
|
||||
public String getForwardedUrl() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getIncludedUrl() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner class that adapts the ServletOutputStream to mark the response as
|
||||
* committed once the buffer size is exceeded.
|
||||
|
||||
@@ -44,6 +44,15 @@ import org.springframework.web.context.ConfigurableWebApplicationContext;
|
||||
import org.springframework.web.context.support.GenericWebApplicationContext;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* Represents the main entry point into interaction with web application over light-weight proxy.
|
||||
* After creating an instance via {@link #INSTANCE(Class...)} operation which will initialize the provided component
|
||||
* classes of your web application (effectively starting your web application less web server),
|
||||
* you use {@link #service(HttpServletRequest, HttpServletResponse)} operation to send request and receive a response.
|
||||
*
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
*/
|
||||
public class ProxyMvc {
|
||||
|
||||
static final String MVC_RESULT_ATTRIBUTE = ProxyMvc.class.getName().concat(".MVC_RESULT_ATTRIBUTE");
|
||||
|
||||
@@ -14,9 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.adapter.aws.web;
|
||||
|
||||
|
||||
package org.springframework.cloud.function.serverless.web;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -28,9 +26,8 @@ import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cloud.function.serverless.web.ProxyHttpServletRequest;
|
||||
import org.springframework.cloud.function.serverless.web.ProxyHttpServletResponse;
|
||||
import org.springframework.cloud.function.serverless.web.ProxyMvc;
|
||||
import org.springframework.cloud.function.test.app.Pet;
|
||||
import org.springframework.cloud.function.test.app.PetStoreSpringAppConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.adapter.aws.web;
|
||||
package org.springframework.cloud.function.test.app;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.adapter.aws.web;
|
||||
package org.springframework.cloud.function.test.app;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.adapter.aws.web;
|
||||
package org.springframework.cloud.function.test.app;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.adapter.aws.web;
|
||||
package org.springframework.cloud.function.test.app;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Optional;
|
||||
Reference in New Issue
Block a user