Stopping point with initial POC

This commit is contained in:
Oleg Zhurakousky
2023-02-21 11:52:06 +01:00
parent 3ded697223
commit d395ab8a8f
28 changed files with 4092 additions and 3 deletions

View File

@@ -0,0 +1,153 @@
/*
* Copyright 2019-2021 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.adapter.aws.web;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
//import org.springframework.boot.SpringBootConfiguration;
//import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.KotlinDetector;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* General utility class which aggregates various class-level utility functions
* used by the framework.
*
* @author Oleg Zhurakousky
* @since 3.0.1
*/
public final class FunctionClassUtils {
private static Log logger = LogFactory.getLog(FunctionClassUtils.class);
private FunctionClassUtils() {
}
/**
* Discovers the start class in the currently running application.
* The discover search order is 'MAIN_CLASS' environment property,
* 'MAIN_CLASS' system property, META-INF/MANIFEST.MF:'Start-Class' attribute,
* meta-inf/manifest.mf:'Start-Class' attribute.
*
* @return instance of Class which represent the start class of the application.
*/
public static Class<?> getStartClass() {
ClassLoader classLoader = FunctionClassUtils.class.getClassLoader();
return getStartClass(classLoader);
}
static Class<?> getStartClass(ClassLoader classLoader) {
Class<?> mainClass = null;
if (System.getenv("MAIN_CLASS") != null) {
mainClass = ClassUtils.resolveClassName(System.getenv("MAIN_CLASS"), classLoader);
}
else if (System.getProperty("MAIN_CLASS") != null) {
mainClass = ClassUtils.resolveClassName(System.getProperty("MAIN_CLASS"), classLoader);
}
else {
try {
Class<?> result = getStartClass(
Collections.list(classLoader.getResources(JarFile.MANIFEST_NAME)), classLoader);
if (result == null) {
result = getStartClass(Collections
.list(classLoader.getResources("meta-inf/manifest.mf")), classLoader);
}
Assert.notNull(result, "Failed to locate main class");
mainClass = result;
}
catch (Exception ex) {
throw new IllegalStateException("Failed to discover main class. An attempt was made to discover "
+ "main class as 'MAIN_CLASS' environment variable, system property as well as "
+ "entry in META-INF/MANIFEST.MF (in that order).", ex);
}
}
logger.info("Main class: " + mainClass);
return mainClass;
}
private static Class<?> getStartClass(List<URL> list, ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Searching manifests: " + list);
}
for (URL url : list) {
try {
InputStream inputStream = null;
Manifest manifest = new Manifest(url.openStream());
logger.info("Searching for start class in manifest: " + url);
if (logger.isDebugEnabled()) {
manifest.write(System.out);
}
try {
String startClassName = manifest.getMainAttributes().getValue("Start-Class");
if (!StringUtils.hasText(startClassName)) {
startClassName = manifest.getMainAttributes().getValue("Main-Class");
}
if (StringUtils.hasText(startClassName)) {
Class<?> startClass = ClassUtils.forName(startClassName, classLoader);
if (KotlinDetector.isKotlinType(startClass)) {
PathMatchingResourcePatternResolver r = new PathMatchingResourcePatternResolver(classLoader);
String packageName = startClass.getPackage().getName();
Resource[] resources = r.getResources("classpath:" + packageName.replace(".", "/") + "/*.class");
for (int i = 0; i < resources.length; i++) {
Resource resource = resources[i];
String className = packageName + "." + (resource.getFilename().replace("/", ".")).replace(".class", "");
startClass = ClassUtils.forName(className, classLoader);
// if (isSpringBootApplication(startClass)) {
// logger.info("Loaded Main Kotlin Class: " + startClass);
// return startClass;
// }
}
}
// else if (isSpringBootApplication(startClass)) {
// logger.info("Loaded Start Class: " + startClass);
// return startClass;
// }
}
}
finally {
if (inputStream != null) {
inputStream.close();
}
}
}
catch (Exception ex) {
logger.debug("Failed to determine Start-Class in manifest file of " + url, ex);
}
}
return null;
}
// private static boolean isSpringBootApplication(Class<?> startClass) {
// return startClass.getDeclaredAnnotation(SpringBootApplication.class) != null
// || startClass.getDeclaredAnnotation(SpringBootConfiguration.class) != null;
// }
}

View File

@@ -0,0 +1,5 @@
Classes in this package would remain specific to AWS (in this case). There would be something similar in Azure and others.
And these classes would depend on what is currently in `org.springframework.web.client` package of this module.
However, ideally the contents of the `org.springframework.web.client` package should reside in spring-web somewhere as a light weight
HTTP proxy as we technically already have it in a form of MockMVC.

View File

@@ -0,0 +1,178 @@
/*
* 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.adapter.aws.web;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.Filter;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.ProxyHttpServletRequest;
import org.springframework.web.client.ProxyHttpServletResponse;
import org.springframework.web.client.ProxyMvc;
import org.springframework.web.client.ProxyServletContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
/**
*
* AWS Lambda specific handler that will proxy API Gateway request to Spring Web-app
* This class represents AWS Lambda fronted by API Gateway and is identified as 'handler' during the deployment.
*
* @author Oleg Zhurakousky
*
*/
public class WebProxyInvoker {
private static Log logger = LogFactory.getLog(WebProxyInvoker.class);
private final ProxyMvc mvc;
private final ServletContext servletContext;
ObjectMapper mapper = new ObjectMapper();
public WebProxyInvoker() throws ServletException {
Class<?> startClass = FunctionClassUtils.getStartClass();
AnnotationConfigWebApplicationContext applpicationContext = new AnnotationConfigWebApplicationContext();
applpicationContext.register(startClass);
this.servletContext = new ProxyServletContext();
ServletConfig servletConfig = new ProxyServletConfig(this.servletContext);
DispatcherServlet servlet = new DispatcherServlet(applpicationContext);
servlet.init(servletConfig);
this.mvc = new ProxyMvc(servlet, applpicationContext.getBeansOfType(Filter.class).values().toArray(new Filter[0]));
}
/*
* TODO
* - Security context propagation from AWS API Gateway (easy)
* - Error handling
*/
@SuppressWarnings("unchecked")
private HttpServletRequest prepareRequest(InputStream input) throws IOException {
Map<String, Object> request = mapper.readValue(input, Map.class);
if (logger.isDebugEnabled()) {
logger.debug("Request: " + request);
}
String httpMethod = (String) request.get("httpMethod");
String path = (String) request.get("path");
if (logger.isDebugEnabled()) {
logger.debug("httpMethod: " + httpMethod);
logger.debug("path: " + path);
}
ProxyHttpServletRequest httpRequest = new ProxyHttpServletRequest(servletContext, httpMethod, path);
if (StringUtils.hasText((String) request.get("body"))) {
httpRequest.setContent(((String) request.get("body")).getBytes());
}
if (request.get("queryStringParameters") != null) {
httpRequest.setParameters((Map<String, ?>) request.get("queryStringParameters"));
}
Map<String, Object> headers = (Map<String, Object>) request.get("headers");
headers.putAll((Map<String, Object>) request.get("multiValueHeaders"));
for (Entry<String, Object> entry : headers.entrySet()) {
httpRequest.addHeader(entry.getKey(), entry.getValue());
}
return httpRequest;
}
public void handleRequest(InputStream input, OutputStream output) throws IOException {
HttpServletRequest httpRequest = this.prepareRequest(input);
ProxyHttpServletResponse httpResponse = new ProxyHttpServletResponse();
try {
this.mvc.perform(httpRequest, httpResponse);
}
catch (Exception e) {
e.printStackTrace();
throw new IllegalStateException(e);
}
String responseString = httpResponse.getContentAsString();
if (StringUtils.hasText(responseString)) {
if (logger.isDebugEnabled()) {
logger.debug("Response: " + responseString);
}
Map<String, Object> apiGatewayResponseStructure = new HashMap<String, Object>();
apiGatewayResponseStructure.put("isBase64Encoded", false);
apiGatewayResponseStructure.put("statusCode", 200);
apiGatewayResponseStructure.put("body", responseString);
Map<String, List<String>> multiValueHeaders = new HashMap<>();
for (String headerName : httpResponse.getHeaderNames()) {
multiValueHeaders.put(headerName, httpResponse.getHeaders(headerName));
}
// TODO investigate why AWS doesn't like List as value
// apiGatewayResponseStructure.put("headers", multiValueHeaders);
byte[] apiGatewayResponseBytes = mapper.writeValueAsBytes(apiGatewayResponseStructure);
StreamUtils.copy(apiGatewayResponseBytes, output);
}
}
private static class ProxyServletConfig implements ServletConfig {
private final ServletContext servletContext;
ProxyServletConfig(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public String getServletName() {
return "serverless-proxy";
}
@Override
public ServletContext getServletContext() {
return this.servletContext;
}
@Override
public Enumeration<String> getInitParameterNames() {
return Collections.enumeration(new ArrayList<String>());
}
@Override
public String getInitParameter(String name) {
return null;
}
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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.web.client;
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();
}
}

View File

@@ -0,0 +1,601 @@
/*
* 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.web.client;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
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;
import javax.servlet.http.Cookie;
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;
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 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
public String getCharacterEncoding() {
return this.characterEncoding;
}
@Override
public ServletOutputStream getOutputStream() {
Assert.state(this.outputStreamAccessAllowed, "OutputStream access not allowed");
return this.outputStream;
}
@Override
public PrintWriter getWriter() throws UnsupportedEncodingException {
throw new UnsupportedOperationException();
}
public byte[] getContentAsByteArray() {
return this.content.toByteArray();
}
/**
* Get the content of the response body as a {@code String}, 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}. If no charset has been explicitly defined, the
* {@linkplain #setDefaultCharacterEncoding(String) default character encoding}
* will be used.
*
* @return the content as a {@code String}
* @throws UnsupportedEncodingException if the character encoding is not
* supported
* @see #getContentAsString(Charset)
* @see #setCharacterEncoding(String)
* @see #setContentType(String)
*/
public String getContentAsString() throws UnsupportedEncodingException {
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);
}
@Override
public void setContentLength(int contentLength) {
throw new UnsupportedOperationException();
}
@Override
public void setContentLengthLong(long len) {
throw new UnsupportedOperationException();
}
@Override
public void setContentType(@Nullable String contentType) {
this.contentType = contentType;
}
@Override
@Nullable
public String getContentType() {
return this.contentType;
}
@Override
public void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
@Override
public int getBufferSize() {
return this.bufferSize;
}
@Override
public void flushBuffer() {
}
@Override
public void resetBuffer() {
Assert.state(!isCommitted(), "Cannot reset buffer - response is already committed");
this.content.reset();
}
public void setCommitted(boolean committed) {
this.committed = committed;
}
@Override
public boolean isCommitted() {
return this.committed;
}
@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();
this.headers.clear();
this.status = HttpServletResponse.SC_OK;
this.errorMessage = null;
}
@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);
}
@Override
public Locale getLocale() {
return this.locale;
}
// ---------------------------------------------------------------------
// HttpServletResponse interface
// ---------------------------------------------------------------------
@Override
public void addCookie(Cookie cookie) {
throw new UnsupportedOperationException();
}
@Nullable
public Cookie getCookie(String name) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsHeader(String name) {
return this.headers.containsKey(name);
}
/**
* Return the names of all specified headers as a Set of Strings.
* <p>
* As of Servlet 3.0, this method is also defined in
* {@link HttpServletResponse}.
*
* @return the {@code Set} of header name {@code Strings}, or an empty
* {@code Set} if none
*/
@Override
public Collection<String> getHeaderNames() {
return this.headers.keySet();
}
/**
* Return the primary value for the given header as a String, if any. Will
* return the first value in case of multiple values.
* <p>
* As of Servlet 3.0, this method is also defined in
* {@link HttpServletResponse}. As of Spring 3.1, it returns a stringified value
* for Servlet 3.0 compatibility. Consider using {@link #getHeaderValue(String)}
* for raw Object access.
*
* @param name the name of the header
* @return the associated header value, or {@code null} if none
*/
@Override
@Nullable
public String getHeader(String name) {
HeaderValueHolder header = this.headers.get(name);
return (header != null ? header.getStringValue() : null);
}
/**
* Return all values for the given header as a List of Strings.
* <p>
* As of Servlet 3.0, this method is also defined in
* {@link HttpServletResponse}. As of Spring 3.1, it returns a List of
* stringified values for Servlet 3.0 compatibility. Consider using
* {@link #getHeaderValues(String)} for raw Object access.
*
* @param name the name of the header
* @return the associated header values, or an empty List if none
*/
@Override
public List<String> getHeaders(String name) {
HeaderValueHolder header = this.headers.get(name);
if (header != null) {
return header.getStringValues();
}
else {
return Collections.emptyList();
}
}
/**
* Return the primary value for the given header, if any.
* <p>
* Will return the first value in case of multiple values.
*
* @param name the name of the header
* @return the associated header value, or {@code null} if none
*/
@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();
}
}
/**
* The default implementation returns the given URL String as-is.
* <p>
* Can be overridden in subclasses, appending a session id or the like.
*/
@Override
public String encodeURL(String url) {
return url;
}
/**
* The default implementation delegates to {@link #encodeURL}, returning the
* given URL String as-is.
* <p>
* Can be overridden in subclasses, appending a session id or the like in a
* redirect-specific fashion. For general URL encoding rules, override the
* common {@link #encodeURL} method instead, applying to redirect URLs as well
* as to general URLs.
*/
@Override
public String encodeRedirectURL(String url) {
return encodeURL(url);
}
@Override
@Deprecated
public String encodeUrl(String url) {
return encodeURL(url);
}
@Override
@Deprecated
public String encodeRedirectUrl(String url) {
return encodeRedirectURL(url);
}
@Override
public void sendError(int status, String errorMessage) throws IOException {
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
public void sendRedirect(String url) throws IOException {
Assert.state(!isCommitted(), "Cannot send redirect - response is already committed");
Assert.notNull(url, "Redirect URL must not be null");
setHeader(HttpHeaders.LOCATION, url);
setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
setCommitted(true);
}
@Nullable
public String getRedirectedUrl() {
return getHeader(HttpHeaders.LOCATION);
}
@Override
public void setDateHeader(String name, long value) {
setHeaderValue(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);
}
}
private String formatDate(long date) {
return newDateFormat().format(new Date(date));
}
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);
}
@Override
public void addHeader(String name, @Nullable String value) {
addHeaderValue(name, value);
}
@Override
public void setIntHeader(String name, int value) {
setHeaderValue(name, 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);
}
}
@Override
public void setStatus(int status) {
if (!this.isCommitted()) {
this.status = status;
}
}
@Override
@Deprecated
public void setStatus(int status, String errorMessage) {
throw new UnsupportedOperationException();
}
@Override
public int getStatus() {
return this.status;
}
@Nullable
public String getErrorMessage() {
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.
*/
private class ResponseServletOutputStream extends ServletOutputStream {
private WriteListener listener;
@Override
public boolean isReady() {
return true;
}
@Override
public void setWriteListener(WriteListener writeListener) {
if (writeListener != null) {
try {
writeListener.onWritePossible();
}
catch (IOException e) {
// log.error("Output stream is not writable", e);
}
listener = writeListener;
}
}
@Override
public void write(int b) throws IOException {
try {
content.write(b);
}
catch (Exception e) {
if (listener != null) {
listener.onError(e);
}
}
}
@Override
public void close() throws IOException {
super.close();
flushBuffer();
}
}
}

View File

@@ -0,0 +1,214 @@
/*
* 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.web.client;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.DispatcherServlet;
public class ProxyMvc {
static final String MVC_RESULT_ATTRIBUTE = ProxyMvc.class.getName().concat(".MVC_RESULT_ATTRIBUTE");
private final DispatcherServlet servlet;
private final Filter[] filters;
@Nullable
private Charset defaultResponseCharacterEncoding;
/**
* Private constructor, not for direct instantiation.
*
* @see org.springframework.test.web.servlet.setup.MockMvcBuilders
*/
public ProxyMvc(DispatcherServlet servlet, Filter... filters) {
Assert.notNull(servlet, "DispatcherServlet is required");
Assert.notNull(filters, "Filters cannot be null");
Assert.noNullElements(filters, "Filters cannot contain null values");
this.servlet = servlet;
this.filters = filters;
}
/**
* The default character encoding to be applied to every response.
*
* @see org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder#defaultResponseCharacterEncoding(Charset)
*/
void setDefaultResponseCharacterEncoding(@Nullable Charset defaultResponseCharacterEncoding) {
this.defaultResponseCharacterEncoding = defaultResponseCharacterEncoding;
}
/**
* Return the underlying {@link DispatcherServlet} instance that this
* {@code MockMvc} was initialized with.
* <p>
* This is intended for use in custom request processing scenario where a
* request handling component happens to delegate to the
* {@code DispatcherServlet} at runtime and therefore needs to be injected with
* it.
* <p>
* For most processing scenarios, simply use {@link MockMvc#perform}, or if you
* need to configure the {@code DispatcherServlet}, provide a
* {@link DispatcherServletCustomizer} to the {@code MockMvcBuilder}.
*
* @since 5.1
*/
public DispatcherServlet getDispatcherServlet() {
return this.servlet;
}
/**
* Perform a request and return a type that allows chaining further actions,
* such as asserting expectations, on the result.
*
* @param requestBuilder used to prepare the request to execute; see static
* factory methods in
* {@link org.springframework.test.web.servlet.request.MockMvcRequestBuilders}
* @return an instance of {@link ResultActions} (never {@code null})
* @see org.springframework.test.web.servlet.request.MockMvcRequestBuilders
* @see org.springframework.test.web.servlet.result.MockMvcResultMatchers
*/
public void perform(HttpServletRequest request, HttpServletResponse response) throws Exception {
ProxyFilterChain filterChain = new ProxyFilterChain(this.servlet, this.filters);
filterChain.doFilter(request, response);
}
private static class ProxyFilterChain implements FilterChain {
@Nullable
private ServletRequest request;
@Nullable
private ServletResponse response;
private final List<Filter> filters;
@Nullable
private Iterator<Filter> iterator;
/**
* Create a {@code FilterChain} with Filter's and a Servlet.
*
* @param servlet the {@link Servlet} to invoke in this {@link FilterChain}
* @param filters the {@link Filter}'s to invoke in this {@link FilterChain}
* @since 3.2
*/
ProxyFilterChain(Servlet servlet, Filter... filters) {
Assert.notNull(filters, "filters cannot be null");
Assert.noNullElements(filters, "filters cannot contain null values");
this.filters = initFilterList(servlet, filters);
}
private static List<Filter> initFilterList(Servlet servlet, Filter... filters) {
Filter[] allFilters = ObjectUtils.addObjectToArray(filters, new ServletFilterProxy(servlet));
return Arrays.asList(allFilters);
}
/**
* Return the request that {@link #doFilter} has been called with.
*/
@Nullable
public ServletRequest getRequest() {
return this.request;
}
/**
* Return the response that {@link #doFilter} has been called with.
*/
@Nullable
public ServletResponse getResponse() {
return this.response;
}
/**
* Invoke registered {@link Filter Filters} and/or {@link Servlet} also saving
* the request and response.
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
Assert.notNull(request, "Request must not be null");
Assert.notNull(response, "Response must not be null");
Assert.state(this.request == null, "This FilterChain has already been called!");
if (this.iterator == null) {
this.iterator = this.filters.iterator();
}
if (this.iterator.hasNext()) {
Filter nextFilter = this.iterator.next();
nextFilter.doFilter(request, response, this);
}
this.request = request;
this.response = response;
}
/**
* A filter that simply delegates to a Servlet.
*/
private static final class ServletFilterProxy implements Filter {
private final Servlet delegateServlet;
private ServletFilterProxy(Servlet servlet) {
Assert.notNull(servlet, "servlet cannot be null");
this.delegateServlet = servlet;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
this.delegateServlet.service(request, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
@Override
public String toString() {
return this.delegateServlet.toString();
}
}
}
}

View File

@@ -0,0 +1,397 @@
/*
* 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.web.client;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.servlet.ServletRegistration.Dynamic;
import javax.servlet.SessionCookieConfig;
import javax.servlet.SessionTrackingMode;
import javax.servlet.descriptor.JspConfigDescriptor;
public class ProxyServletContext implements ServletContext {
@Override
public Enumeration<String> getInitParameterNames() {
List<String> arrlist = new ArrayList<String>();
return Collections.enumeration(arrlist);
}
@Override
public Enumeration<String> getAttributeNames() {
List<String> arrlist = new ArrayList<String>();
return Collections.enumeration(arrlist);
}
@Override
public String getContextPath() {
// TODO Auto-generated method stub
return null;
}
@Override
public ServletContext getContext(String uripath) {
// TODO Auto-generated method stub
return null;
}
@Override
public int getMajorVersion() {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getMinorVersion() {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getEffectiveMajorVersion() {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getEffectiveMinorVersion() {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getMimeType(String file) {
// TODO Auto-generated method stub
return null;
}
@Override
public Set<String> getResourcePaths(String path) {
// TODO Auto-generated method stub
return null;
}
@Override
public URL getResource(String path) throws MalformedURLException {
// TODO Auto-generated method stub
return null;
}
@Override
public InputStream getResourceAsStream(String path) {
// TODO Auto-generated method stub
return null;
}
@Override
public RequestDispatcher getRequestDispatcher(String path) {
// TODO Auto-generated method stub
return null;
}
@Override
public RequestDispatcher getNamedDispatcher(String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public Servlet getServlet(String name) throws ServletException {
// TODO Auto-generated method stub
return null;
}
@Override
public Enumeration<Servlet> getServlets() {
// TODO Auto-generated method stub
return null;
}
@Override
public Enumeration<String> getServletNames() {
// TODO Auto-generated method stub
return null;
}
@Override
public void log(String msg) {
// TODO Auto-generated method stub
}
@Override
public void log(Exception exception, String msg) {
// TODO Auto-generated method stub
}
@Override
public void log(String message, Throwable throwable) {
// TODO Auto-generated method stub
}
@Override
public String getRealPath(String path) {
// TODO Auto-generated method stub
return null;
}
@Override
public String getServerInfo() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getInitParameter(String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean setInitParameter(String name, String value) {
// TODO Auto-generated method stub
return false;
}
@Override
public Object getAttribute(String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public void setAttribute(String name, Object object) {
// TODO Auto-generated method stub
}
@Override
public void removeAttribute(String name) {
// TODO Auto-generated method stub
}
@Override
public String getServletContextName() {
// TODO Auto-generated method stub
return null;
}
@Override
public Dynamic addServlet(String servletName, String className) {
// TODO Auto-generated method stub
return null;
}
@Override
public Dynamic addServlet(String servletName, Servlet servlet) {
// TODO Auto-generated method stub
return null;
}
@Override
public Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass) {
// TODO Auto-generated method stub
return null;
}
@Override
public Dynamic addJspFile(String jspName, String jspFile) {
// TODO Auto-generated method stub
return null;
}
@Override
public <T extends Servlet> T createServlet(Class<T> c) throws ServletException {
// TODO Auto-generated method stub
return null;
}
@Override
public ServletRegistration getServletRegistration(String servletName) {
// TODO Auto-generated method stub
return null;
}
@Override
public Map<String, ? extends ServletRegistration> getServletRegistrations() {
// TODO Auto-generated method stub
return null;
}
@Override
public javax.servlet.FilterRegistration.Dynamic addFilter(String filterName, String className) {
// TODO Auto-generated method stub
return null;
}
@Override
public javax.servlet.FilterRegistration.Dynamic addFilter(String filterName, Filter filter) {
// TODO Auto-generated method stub
return null;
}
@Override
public javax.servlet.FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass) {
// TODO Auto-generated method stub
return null;
}
@Override
public <T extends Filter> T createFilter(Class<T> c) throws ServletException {
// TODO Auto-generated method stub
return null;
}
@Override
public FilterRegistration getFilterRegistration(String filterName) {
// TODO Auto-generated method stub
return null;
}
@Override
public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
// TODO Auto-generated method stub
return null;
}
@Override
public SessionCookieConfig getSessionCookieConfig() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes) {
// TODO Auto-generated method stub
}
@Override
public Set<javax.servlet.SessionTrackingMode> getDefaultSessionTrackingModes() {
// TODO Auto-generated method stub
return null;
}
@Override
public Set<javax.servlet.SessionTrackingMode> getEffectiveSessionTrackingModes() {
// TODO Auto-generated method stub
return null;
}
@Override
public void addListener(String className) {
// TODO Auto-generated method stub
}
@Override
public <T extends EventListener> void addListener(T t) {
// TODO Auto-generated method stub
}
@Override
public void addListener(Class<? extends EventListener> listenerClass) {
// TODO Auto-generated method stub
}
@Override
public <T extends EventListener> T createListener(Class<T> c) throws ServletException {
// TODO Auto-generated method stub
return null;
}
@Override
public JspConfigDescriptor getJspConfigDescriptor() {
// TODO Auto-generated method stub
return null;
}
@Override
public ClassLoader getClassLoader() {
// TODO Auto-generated method stub
return null;
}
@Override
public void declareRoles(String... roleNames) {
// TODO Auto-generated method stub
}
@Override
public String getVirtualServerName() {
// TODO Auto-generated method stub
return null;
}
@Override
public int getSessionTimeout() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void setSessionTimeout(int sessionTimeout) {
// TODO Auto-generated method stub
}
@Override
public String getRequestCharacterEncoding() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setRequestCharacterEncoding(String encoding) {
// TODO Auto-generated method stub
}
@Override
public String getResponseCharacterEncoding() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setResponseCharacterEncoding(String encoding) {
// TODO Auto-generated method stub
}
}

View File

@@ -0,0 +1,3 @@
Classes in this package should ideally reside in spring-web somewhere as a light weight HTTP proxy, since they are independent of the
context of the execution (i.e., AWS or Azure or whatever).
In fact classes in these package is a slimed-down copy of similar classes in MockMVC.

View File

@@ -0,0 +1,58 @@
/*
* 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.adapter.aws.web;
import java.util.Date;
public class Pet {
private String id;
private String breed;
private String name;
private Date dateOfBirth;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getBreed() {
return breed;
}
public void setBreed(String breed) {
this.breed = breed;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(Date dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
}

View File

@@ -0,0 +1,118 @@
/*
* 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.adapter.aws.web;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class PetData {
private static List<String> breeds = new ArrayList<>();
static {
breeds.add("Afghan Hound");
breeds.add("Beagle");
breeds.add("Bernese Mountain Dog");
breeds.add("Bloodhound");
breeds.add("Dalmatian");
breeds.add("Jack Russell Terrier");
breeds.add("Norwegian Elkhound");
}
private static List<String> names = new ArrayList<>();
static {
names.add("Bailey");
names.add("Bella");
names.add("Max");
names.add("Lucy");
names.add("Charlie");
names.add("Molly");
names.add("Buddy");
names.add("Daisy");
names.add("Rocky");
names.add("Maggie");
names.add("Jake");
names.add("Sophie");
names.add("Jack");
names.add("Sadie");
names.add("Toby");
names.add("Chloe");
names.add("Cody");
names.add("Bailey");
names.add("Buster");
names.add("Lola");
names.add("Duke");
names.add("Zoe");
names.add("Cooper");
names.add("Abby");
names.add("Riley");
names.add("Ginger");
names.add("Harley");
names.add("Roxy");
names.add("Bear");
names.add("Gracie");
names.add("Tucker");
names.add("Coco");
names.add("Murphy");
names.add("Sasha");
names.add("Lucky");
names.add("Lily");
names.add("Oliver");
names.add("Angel");
names.add("Sam");
names.add("Princess");
names.add("Oscar");
names.add("Emma");
names.add("Teddy");
names.add("Annie");
names.add("Winston");
names.add("Rosie");
}
public static List<String> getBreeds() {
return breeds;
}
public static List<String> getNames() {
return names;
}
public static String getRandomBreed() {
return breeds.get(ThreadLocalRandom.current().nextInt(0, breeds.size() - 1));
}
public static String getRandomName() {
return names.get(ThreadLocalRandom.current().nextInt(0, names.size() - 1));
}
public static Date getRandomDoB() {
GregorianCalendar gc = new GregorianCalendar();
int year = ThreadLocalRandom.current().nextInt(Calendar.getInstance().get(Calendar.YEAR) - 15,
Calendar.getInstance().get(Calendar.YEAR));
gc.set(Calendar.YEAR, year);
int dayOfYear = ThreadLocalRandom.current().nextInt(1, gc.getActualMaximum(Calendar.DAY_OF_YEAR));
gc.set(Calendar.DAY_OF_YEAR, dayOfYear);
return gc.getTime();
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.adapter.aws.web;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
//@SpringBootApplication
@Configuration
@Import({ PetsController.class })
public class PetStoreSpringAppConfig {
/*
* Create required HandlerMapping, to avoid several default HandlerMapping
* instances being created
*/
@Bean
public HandlerMapping handlerMapping() {
return new RequestMappingHandlerMapping();
}
/*
* Create required HandlerAdapter, to avoid several default HandlerAdapter
* instances being created
*/
@Bean
public HandlerAdapter handlerAdapter() {
return new RequestMappingHandlerAdapter();
}
@Bean
public Filter filter() {
return new Filter() {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("FILTER ===> Hello from: " + request.getLocalAddr());
chain.doFilter(request, response);
}
};
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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.adapter.aws.web;
import java.security.Principal;
import java.util.Optional;
import java.util.UUID;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@RestController
@EnableWebMvc
public class PetsController {
@RequestMapping(path = "/pets", method = RequestMethod.POST)
public Pet createPet(@RequestBody Pet newPet) {
if (newPet.getName() == null || newPet.getBreed() == null) {
return null;
}
Pet dbPet = newPet;
dbPet.setId(UUID.randomUUID().toString());
return dbPet;
}
@RequestMapping(path = "/pets", method = RequestMethod.GET)
public Pet[] listPets(@RequestParam("limit") Optional<Integer> limit, Principal principal) {
System.out.println("=====> EXECUTING");
int queryLimit = 10;
if (limit.isPresent()) {
queryLimit = limit.get();
}
Pet[] outputPets = new Pet[queryLimit];
for (int i = 0; i < queryLimit; i++) {
Pet newPet = new Pet();
newPet.setId(UUID.randomUUID().toString());
newPet.setName(PetData.getRandomName());
newPet.setBreed(PetData.getRandomBreed());
newPet.setDateOfBirth(PetData.getRandomDoB());
outputPets[i] = newPet;
}
return outputPets;
}
@RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET)
public Pet listPets() {
System.out.println("=====> Getting pet by id");
Pet newPet = new Pet();
newPet.setId(UUID.randomUUID().toString());
newPet.setBreed(PetData.getRandomBreed());
newPet.setDateOfBirth(PetData.getRandomDoB());
newPet.setName(PetData.getRandomName());
return newPet;
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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.adapter.aws.web;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
public class WebProxyInvokerTests {
static String apiGatewayEvent = "{\n" +
" \"resource\": \"/pets\",\n" +
" \"path\": \"/pets/64f56d94-a059-4111-9eeb-ee0c994b1ba8?foo=bar\",\n" +
" \"httpMethod\": \"GET\",\n" +
" \"headers\": {\n" +
" \"accept\": \"*/*\",\n" +
" \"content-type\": \"application/json\",\n" +
" \"Host\": \"fhul32ccy2.execute-api.eu-west-3.amazonaws.com\",\n" +
" \"User-Agent\": \"curl/7.54.0\",\n" +
" \"X-Amzn-Trace-Id\": \"Root=1-5ece339e-e0595766066d703ec70f1522\",\n" +
" \"X-Forwarded-For\": \"90.37.8.133\",\n" +
" \"X-Forwarded-Port\": \"443\",\n" +
" \"X-Forwarded-Proto\": \"https\"\n" +
" },\n" +
" \"multiValueHeaders\": {\n" +
" \"accept\": [\n" +
" \"*/*\"\n" +
" ],\n" +
" \"content-type\": [\n" +
" \"application/json\"\n" +
" ],\n" +
" \"Host\": [\n" +
" \"fhul32ccy2.execute-api.eu-west-3.amazonaws.com\"\n" +
" ],\n" +
" \"User-Agent\": [\n" +
" \"curl/7.54.0\"\n" +
" ],\n" +
" \"X-Amzn-Trace-Id\": [\n" +
" \"Root=1-5ece339e-e0595766066d703ec70f1522\"\n" +
" ],\n" +
" \"X-Forwarded-For\": [\n" +
" \"90.37.8.133\"\n" +
" ],\n" +
" \"X-Forwarded-Port\": [\n" +
" \"443\"\n" +
" ],\n" +
" \"X-Forwarded-Proto\": [\n" +
" \"https\"\n" +
" ]\n" +
" },\n" +
" \"queryStringParameters\": null,\n" +
" \"multiValueQueryStringParameters\": null,\n" +
" \"pathParameters\": null,\n" +
" \"stageVariables\": null,\n" +
" \"requestContext\": {\n" +
" \"resourceId\": \"qf0io6\",\n" +
" \"resourcePath\": \"/pets\",\n" +
" \"httpMethod\": \"GET\",\n" +
" \"extendedRequestId\": \"NL0A1EokCGYFZOA=\",\n" +
" \"requestTime\": \"27/May/2020:09:32:14 +0000\",\n" +
" \"path\": \"/test/uppercase2\",\n" +
" \"accountId\": \"123456789098\",\n" +
" \"protocol\": \"HTTP/1.1\",\n" +
" \"stage\": \"test\",\n" +
" \"domainPrefix\": \"fhul32ccy2\",\n" +
" \"requestTimeEpoch\": 1590571934872,\n" +
" \"requestId\": \"b96500aa-f92a-43c3-9360-868ba4053a00\",\n" +
" \"identity\": {\n" +
" \"cognitoIdentityPoolId\": null,\n" +
" \"accountId\": null,\n" +
" \"cognitoIdentityId\": null,\n" +
" \"caller\": null,\n" +
" \"sourceIp\": \"90.37.8.133\",\n" +
" \"principalOrgId\": null,\n" +
" \"accessKey\": null,\n" +
" \"cognitoAuthenticationType\": null,\n" +
" \"cognitoAuthenticationProvider\": null,\n" +
" \"userArn\": null,\n" +
" \"userAgent\": \"curl/7.54.0\",\n" +
" \"user\": null\n" +
" },\n" +
" \"domainName\": \"fhul32ccy2.execute-api.eu-west-3.amazonaws.com\",\n" +
" \"apiId\": \"fhul32ccy2\"\n" +
" },\n" +
" \"body\":\"\",\n" +
" \"isBase64Encoded\": false\n" +
"}";
@Test
public void testApiGatewayProxy() throws Exception {
System.setProperty("MAIN_CLASS", PetStoreSpringAppConfig.class.getName());
WebProxyInvoker invoker = new WebProxyInvoker();
InputStream targetStream = new ByteArrayInputStream(this.apiGatewayEvent.getBytes());
ByteArrayOutputStream output = new ByteArrayOutputStream();
invoker.handleRequest(targetStream, output);
ObjectMapper mapper = new ObjectMapper();
System.out.println("RESULT: =======> " + new String(output.toByteArray()));
Map result = mapper.readValue(output.toByteArray(), Map.class);
System.out.println(result);
}
}