From 03dfd8c1b8327b1d3cc3c1828eb4144697c1f464 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 1 Jun 2020 16:38:39 -0400 Subject: [PATCH] Support FileRegions lambda formatting virtual handler poll loop error messages git rid of poll loop --- .../amazon/lambda/http/LambdaHttpHandler.java | 165 +++++++++++------- .../runtime/AbstractLambdaPollLoop.java | 28 ++- .../resteasy/runtime/BaseFunction.java | 120 ++++++++----- .../netty/runtime/virtual/VirtualChannel.java | 3 +- .../virtual/VirtualClientConnection.java | 36 ++-- .../virtual/VirtualResponseHandler.java | 7 + integration-tests/amazon-lambda-http/pom.xml | 4 + .../src/main/resources/application.properties | 1 + .../lambda/AmazonLambdaSimpleTestCase.java | 25 ++- .../virtual-http-resteasy/pom.xml | 4 + .../src/main/resources/application.properties | 2 + .../io/quarkus/it/virtual/FunctionTest.java | 75 ++++---- 12 files changed, 289 insertions(+), 181 deletions(-) create mode 100644 extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/virtual/VirtualResponseHandler.java diff --git a/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpHandler.java b/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpHandler.java index a6b43e9e571e2..539a94f9f2b71 100644 --- a/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpHandler.java +++ b/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpHandler.java @@ -3,11 +3,15 @@ import java.io.ByteArrayOutputStream; import java.net.InetSocketAddress; import java.net.URLEncoder; +import java.nio.channels.Channels; +import java.nio.channels.WritableByteChannel; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.CompletableFuture; + +import org.jboss.logging.Logger; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; import com.amazonaws.services.lambda.runtime.Context; @@ -15,6 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.channel.FileRegion; import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.DefaultLastHttpContent; import io.netty.handler.codec.http.HttpContent; @@ -28,10 +33,12 @@ import io.quarkus.amazon.lambda.http.model.AwsProxyResponse; import io.quarkus.amazon.lambda.http.model.Headers; import io.quarkus.netty.runtime.virtual.VirtualClientConnection; +import io.quarkus.netty.runtime.virtual.VirtualResponseHandler; import io.quarkus.vertx.http.runtime.VertxHttpRecorder; @SuppressWarnings("unused") public class LambdaHttpHandler implements RequestHandler { + private static final Logger log = Logger.getLogger("quarkus.amazon.lambda.http"); private static Headers errorHeaders = new Headers(); static { @@ -46,19 +53,99 @@ public AwsProxyResponse handleRequest(AwsProxyRequest request, Context context) } } - VirtualClientConnection connection = VirtualClientConnection.connect(VertxHttpRecorder.VIRTUAL_HTTP, clientAddress); try { - return nettyDispatch(connection, request); + return nettyDispatch(clientAddress, request); } catch (Exception e) { + log.error("Request Failure", e); return new AwsProxyResponse(500, errorHeaders, "{ \"message\": \"Internal Server Error\" }"); - } finally { - connection.close(); } } - private AwsProxyResponse nettyDispatch(VirtualClientConnection connection, AwsProxyRequest request) throws Exception { + private class NettyResponseHandler implements VirtualResponseHandler { + AwsProxyResponse responseBuilder = new AwsProxyResponse(); + ByteArrayOutputStream baos; + WritableByteChannel byteChannel; + final AwsProxyRequest request; + CompletableFuture future = new CompletableFuture<>(); + + public NettyResponseHandler(AwsProxyRequest request) { + this.request = request; + } + + public CompletableFuture getFuture() { + return future; + } + + @Override + public void handleMessage(Object msg) { + try { + //log.info("Got message: " + msg.getClass().getName()); + + if (msg instanceof HttpResponse) { + HttpResponse res = (HttpResponse) msg; + responseBuilder.setStatusCode(res.status().code()); + + if (request.getRequestSource() == AwsProxyRequest.RequestSource.ALB) { + responseBuilder.setStatusDescription(res.status().reasonPhrase()); + } + responseBuilder.setMultiValueHeaders(new Headers()); + for (String name : res.headers().names()) { + for (String v : res.headers().getAll(name)) { + responseBuilder.getMultiValueHeaders().add(name, v); + } + } + } + if (msg instanceof HttpContent) { + HttpContent content = (HttpContent) msg; + int readable = content.content().readableBytes(); + if (baos == null && readable > 0) { + baos = createByteStream(); + } + for (int i = 0; i < readable; i++) { + baos.write(content.content().readByte()); + } + } + if (msg instanceof FileRegion) { + FileRegion file = (FileRegion) msg; + if (file.count() > 0 && file.transferred() < file.count()) { + if (baos == null) + baos = createByteStream(); + if (byteChannel == null) + byteChannel = Channels.newChannel(baos); + file.transferTo(byteChannel, file.transferred()); + } + } + if (msg instanceof LastHttpContent) { + if (baos != null) { + if (isBinary(responseBuilder.getMultiValueHeaders().getFirst("Content-Type"))) { + responseBuilder.setBase64Encoded(true); + responseBuilder.setBody(Base64.getMimeEncoder().encodeToString(baos.toByteArray())); + } else { + responseBuilder.setBody(new String(baos.toByteArray(), "UTF-8")); + } + } + future.complete(responseBuilder); + } + } catch (Throwable ex) { + future.completeExceptionally(ex); + } finally { + if (msg != null) { + ReferenceCountUtil.release(msg); + } + } + } + + @Override + public void close() { + if (!future.isDone()) + future.completeExceptionally(new RuntimeException("Connection closed")); + } + } + + private AwsProxyResponse nettyDispatch(InetSocketAddress clientAddress, AwsProxyRequest request) throws Exception { String path = request.getPath(); + //log.info("---- Got lambda request: " + path); if (request.getMultiValueQueryStringParameters() != null && !request.getMultiValueQueryStringParameters().isEmpty()) { StringBuilder sb = new StringBuilder(path); sb.append("?"); @@ -104,69 +191,25 @@ private AwsProxyResponse nettyDispatch(VirtualClientConnection connection, AwsPr requestContent = new DefaultLastHttpContent(body); } } + NettyResponseHandler handler = new NettyResponseHandler(request); + VirtualClientConnection connection = VirtualClientConnection.connect(handler, VertxHttpRecorder.VIRTUAL_HTTP, + clientAddress); connection.sendMessage(nettyRequest); connection.sendMessage(requestContent); - AwsProxyResponse responseBuilder = new AwsProxyResponse(); - ByteArrayOutputStream baos = null; try { - for (;;) { - // todo should we timeout? have a timeout config? - //log.info("waiting for message"); - Object msg = connection.queue().poll(100, TimeUnit.MILLISECONDS); - try { - if (msg == null) - continue; - //log.info("Got message: " + msg.getClass().getName()); - - if (msg instanceof HttpResponse) { - HttpResponse res = (HttpResponse) msg; - responseBuilder.setStatusCode(res.status().code()); - - if (request.getRequestSource() == AwsProxyRequest.RequestSource.ALB) { - responseBuilder.setStatusDescription(res.status().reasonPhrase()); - } - responseBuilder.setMultiValueHeaders(new Headers()); - for (String name : res.headers().names()) { - for (String v : res.headers().getAll(name)) { - responseBuilder.getMultiValueHeaders().add(name, v); - } - } - } - if (msg instanceof HttpContent) { - HttpContent content = (HttpContent) msg; - int readable = content.content().readableBytes(); - if (baos == null && readable > 0) { - // todo what is right size? - baos = new ByteArrayOutputStream(500); - } - for (int i = 0; i < readable; i++) { - baos.write(content.content().readByte()); - } - } - if (msg instanceof LastHttpContent) { - if (baos != null) { - if (isBinary(responseBuilder.getMultiValueHeaders().getFirst("Content-Type"))) { - responseBuilder.setBase64Encoded(true); - responseBuilder.setBody(Base64.getMimeEncoder().encodeToString(baos.toByteArray())); - } else { - responseBuilder.setBody(new String(baos.toByteArray(), "UTF-8")); - } - } - return responseBuilder; - } - } finally { - if (msg != null) - ReferenceCountUtil.release(msg); - } - } + return handler.getFuture().get(); } finally { - if (baos != null) { - baos.close(); - } + connection.close(); } } + private ByteArrayOutputStream createByteStream() { + ByteArrayOutputStream baos;// todo what is right size? + baos = new ByteArrayOutputStream(1000); + return baos; + } + private boolean isBinary(String contentType) { if (contentType != null) { int index = contentType.indexOf(';'); diff --git a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AbstractLambdaPollLoop.java b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AbstractLambdaPollLoop.java index fedf61310e00b..ef729f1cba816 100644 --- a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AbstractLambdaPollLoop.java +++ b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AbstractLambdaPollLoop.java @@ -3,7 +3,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.ConnectException; import java.net.HttpURLConnection; +import java.net.SocketException; import java.net.URL; import java.util.concurrent.atomic.AtomicBoolean; @@ -45,7 +47,15 @@ public void run() { URL requestUrl = AmazonLambdaApi.invocationNext(); while (running.get()) { - HttpURLConnection requestConnection = (HttpURLConnection) requestUrl.openConnection(); + HttpURLConnection requestConnection = null; + try { + requestConnection = (HttpURLConnection) requestUrl.openConnection(); + } catch (IOException e) { + if (abortGracefully(e)) { + return; + } + throw e; + } try { String requestId = requestConnection.getHeaderField(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID); try { @@ -70,6 +80,9 @@ public void run() { } } } catch (Exception e) { + if (abortGracefully(e)) { + return; + } log.error("Failed to run lambda", e); postError(AmazonLambdaApi.invocationError(requestId), @@ -78,7 +91,8 @@ public void run() { } } catch (Exception e) { - log.error("Error running lambda", e); + if (!abortGracefully(e)) + log.error("Error running lambda", e); Application app = Application.currentApplication(); if (app != null) { app.stop(); @@ -174,4 +188,14 @@ protected HttpURLConnection responseStream(URL url) throws IOException { return responseConnection; } + boolean abortGracefully(Exception ex) { + // if we are running in test mode, then don't output stack trace for socket errors + + boolean graceful = (ex instanceof SocketException || ex instanceof ConnectException) + && System.getProperty(AmazonLambdaApi.QUARKUS_INTERNAL_AWS_LAMBDA_TEST_API) != null; + if (graceful) + log.warn("Aborting lambda poll loop"); + return graceful; + } + } \ No newline at end of file diff --git a/extensions/azure-functions-http/runtime/src/main/java/io/quarkus/azure/functions/resteasy/runtime/BaseFunction.java b/extensions/azure-functions-http/runtime/src/main/java/io/quarkus/azure/functions/resteasy/runtime/BaseFunction.java index 4aa03f9b918d8..328c4c20db234 100644 --- a/extensions/azure-functions-http/runtime/src/main/java/io/quarkus/azure/functions/resteasy/runtime/BaseFunction.java +++ b/extensions/azure-functions-http/runtime/src/main/java/io/quarkus/azure/functions/resteasy/runtime/BaseFunction.java @@ -3,9 +3,11 @@ import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.channels.Channels; +import java.nio.channels.WritableByteChannel; import java.util.Map; import java.util.Optional; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.CompletableFuture; import org.jboss.logging.Logger; @@ -15,6 +17,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.channel.FileRegion; import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.DefaultLastHttpContent; import io.netty.handler.codec.http.HttpContent; @@ -24,6 +27,7 @@ import io.netty.handler.codec.http.LastHttpContent; import io.netty.util.ReferenceCountUtil; import io.quarkus.netty.runtime.virtual.VirtualClientConnection; +import io.quarkus.netty.runtime.virtual.VirtualResponseHandler; import io.quarkus.runtime.Application; import io.quarkus.vertx.http.runtime.VertxHttpRecorder; @@ -56,20 +60,16 @@ public class BaseFunction { } protected HttpResponseMessage dispatch(HttpRequestMessage> request) { - VirtualClientConnection connection = VirtualClientConnection.connect(VertxHttpRecorder.VIRTUAL_HTTP); try { - return nettyDispatch(connection, request); + return nettyDispatch(request); } catch (Exception e) { e.printStackTrace(); return request .createResponseBuilder(HttpStatus.valueOf(500)).build(); - } finally { - connection.close(); } } - protected HttpResponseMessage nettyDispatch(VirtualClientConnection connection, - HttpRequestMessage> request) + protected HttpResponseMessage nettyDispatch(HttpRequestMessage> request) throws Exception { String path = request.getUri().getRawPath(); String query = request.getUri().getRawQuery(); @@ -92,51 +92,83 @@ protected HttpResponseMessage nettyDispatch(VirtualClientConnection connection, requestContent = new DefaultLastHttpContent(body); } + ResponseHandler handler = new ResponseHandler(request); + VirtualClientConnection connection = VirtualClientConnection.connect(handler, VertxHttpRecorder.VIRTUAL_HTTP); + connection.sendMessage(nettyRequest); connection.sendMessage(requestContent); - HttpResponseMessage.Builder responseBuilder = null; - ByteArrayOutputStream baos = null; try { - for (;;) { - // todo should we timeout? have a timeout config? - //log.info("waiting for message"); - Object msg = connection.queue().poll(100, TimeUnit.MILLISECONDS); - try { - if (msg == null) - continue; - //log.info("Got message: " + msg.getClass().getName()); - - if (msg instanceof HttpResponse) { - HttpResponse res = (HttpResponse) msg; - responseBuilder = request.createResponseBuilder(HttpStatus.valueOf(res.status().code())); - for (Map.Entry entry : res.headers()) { - responseBuilder.header(entry.getKey(), entry.getValue()); - } + return handler.future.get(); + } finally { + connection.close(); + } + } + + private ByteArrayOutputStream createByteStream() { + ByteArrayOutputStream baos; + baos = new ByteArrayOutputStream(500); + return baos; + } + + private class ResponseHandler implements VirtualResponseHandler { + HttpResponseMessage.Builder responseBuilder; + ByteArrayOutputStream baos; + WritableByteChannel byteChannel; + CompletableFuture future = new CompletableFuture<>(); + final HttpRequestMessage> request; + + public ResponseHandler(HttpRequestMessage> request) { + this.request = request; + } + + @Override + public void handleMessage(Object msg) { + try { + //log.info("Got message: " + msg.getClass().getName()); + + if (msg instanceof HttpResponse) { + HttpResponse res = (HttpResponse) msg; + responseBuilder = request.createResponseBuilder(HttpStatus.valueOf(res.status().code())); + for (Map.Entry entry : res.headers()) { + responseBuilder.header(entry.getKey(), entry.getValue()); } - if (msg instanceof HttpContent) { - HttpContent content = (HttpContent) msg; - if (baos == null) { - // todo what is right size? - baos = new ByteArrayOutputStream(500); - } - int readable = content.content().readableBytes(); - for (int i = 0; i < readable; i++) { - baos.write(content.content().readByte()); - } + } + if (msg instanceof HttpContent) { + HttpContent content = (HttpContent) msg; + if (baos == null) { + // todo what is right size? + baos = createByteStream(); } - if (msg instanceof LastHttpContent) { - responseBuilder.body(baos.toByteArray()); - return responseBuilder.build(); + int readable = content.content().readableBytes(); + for (int i = 0; i < readable; i++) { + baos.write(content.content().readByte()); } - } finally { - if (msg != null) - ReferenceCountUtil.release(msg); } + if (msg instanceof FileRegion) { + FileRegion file = (FileRegion) msg; + if (file.count() > 0 && file.transferred() < file.count()) { + if (baos == null) + baos = createByteStream(); + if (byteChannel == null) + byteChannel = Channels.newChannel(baos); + file.transferTo(byteChannel, file.transferred()); + } + } + if (msg instanceof LastHttpContent) { + responseBuilder.body(baos.toByteArray()); + future.complete(responseBuilder.build()); + } + } catch (Throwable ex) { + future.completeExceptionally(ex); + } finally { + ReferenceCountUtil.release(msg); } - } finally { - if (baos != null) { - baos.close(); - } + } + + @Override + public void close() { + if (!future.isDone()) + future.completeExceptionally(new RuntimeException("Connection closed")); } } } diff --git a/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/virtual/VirtualChannel.java b/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/virtual/VirtualChannel.java index c0c364197ab72..ae3038493172e 100644 --- a/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/virtual/VirtualChannel.java +++ b/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/virtual/VirtualChannel.java @@ -313,7 +313,8 @@ protected void doWrite(ChannelOutboundBuffer in) throws Exception { // It is possible the peer could have closed while we are writing, and in this case we should // simulate real socket behavior and ensure the sendMessage operation is failed. if (peer.isConnected()) { - peer.queue().add(ReferenceCountUtil.retain(msg)); + ReferenceCountUtil.retain(msg); + peer.handler.handleMessage(msg); in.remove(); } else { if (exception == null) { diff --git a/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/virtual/VirtualClientConnection.java b/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/virtual/VirtualClientConnection.java index c19e91c81a01f..8b83ab1f9306a 100644 --- a/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/virtual/VirtualClientConnection.java +++ b/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/virtual/VirtualClientConnection.java @@ -1,46 +1,34 @@ package io.quarkus.netty.runtime.virtual; import java.net.SocketAddress; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; import io.netty.channel.Channel; import io.netty.util.concurrent.Future; import io.netty.util.internal.PlatformDependent; /** - * A virtual client connection to an intra-JVM netty server channel. - * - * Clients can block and wait directly for posted messages from the server channel. - * + * A virtual client connection to an intra-JVM request/response netty server channel. */ -public class VirtualClientConnection { - protected SocketAddress clientAddress; - protected BlockingQueue queue = new LinkedBlockingQueue<>(); +public class VirtualClientConnection { + protected final SocketAddress clientAddress; protected boolean connected = true; protected VirtualChannel peer; + protected final VirtualResponseHandler handler; - VirtualClientConnection(SocketAddress clientAddress) { + public VirtualClientConnection(SocketAddress clientAddress, VirtualResponseHandler handler) { this.clientAddress = clientAddress; + this.handler = handler; } public SocketAddress clientAddress() { return clientAddress; } - /** - * Blocking queue for a client to obtain messages directly from server - * - * @return - */ - public BlockingQueue queue() { - return queue; - } - public void close() { // todo more cleanup? connected = false; peer.close(); + handler.close(); } public boolean isConnected() { @@ -104,9 +92,8 @@ public void run() { * @param remoteAddress * @return */ - public static VirtualClientConnection connect(final VirtualAddress remoteAddress) { - return connect(remoteAddress, remoteAddress); - + public static VirtualClientConnection connect(VirtualResponseHandler handler, VirtualAddress remoteAddress) { + return connect(handler, remoteAddress, remoteAddress); } /** @@ -116,7 +103,8 @@ public static VirtualClientConnection connect(final VirtualAddress remoteAddress * @param clientAddress * @return */ - public static VirtualClientConnection connect(VirtualAddress remoteAddress, SocketAddress clientAddress) { + public static VirtualClientConnection connect(VirtualResponseHandler handler, VirtualAddress remoteAddress, + SocketAddress clientAddress) { if (clientAddress == null) clientAddress = remoteAddress; @@ -129,7 +117,7 @@ public static VirtualClientConnection connect(VirtualAddress remoteAddress, Sock } VirtualServerChannel serverChannel = (VirtualServerChannel) boundChannel; - VirtualClientConnection conn = new VirtualClientConnection(clientAddress); + VirtualClientConnection conn = new VirtualClientConnection(clientAddress, handler); conn.peer = serverChannel.serve(conn); return conn; } diff --git a/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/virtual/VirtualResponseHandler.java b/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/virtual/VirtualResponseHandler.java new file mode 100644 index 0000000000000..7c2892dfc2fb0 --- /dev/null +++ b/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/virtual/VirtualResponseHandler.java @@ -0,0 +1,7 @@ +package io.quarkus.netty.runtime.virtual; + +public interface VirtualResponseHandler { + void handleMessage(Object msg); + + void close(); +} diff --git a/integration-tests/amazon-lambda-http/pom.xml b/integration-tests/amazon-lambda-http/pom.xml index 8b4d7fbeed028..a3db1ddc18fa1 100644 --- a/integration-tests/amazon-lambda-http/pom.xml +++ b/integration-tests/amazon-lambda-http/pom.xml @@ -22,6 +22,10 @@ io.quarkus quarkus-resteasy + + io.quarkus + quarkus-smallrye-openapi + io.quarkus quarkus-undertow diff --git a/integration-tests/amazon-lambda-http/src/main/resources/application.properties b/integration-tests/amazon-lambda-http/src/main/resources/application.properties index 6156f0357df51..f41be18ef3090 100644 --- a/integration-tests/amazon-lambda-http/src/main/resources/application.properties +++ b/integration-tests/amazon-lambda-http/src/main/resources/application.properties @@ -1,2 +1,3 @@ quarkus.lambda.enable-polling-jvm-mode=true quarkus.http.virtual=true +quarkus.swagger-ui.always-include=true diff --git a/integration-tests/amazon-lambda-http/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleTestCase.java b/integration-tests/amazon-lambda-http/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleTestCase.java index 952c1b61d5f06..c1e203d84684c 100644 --- a/integration-tests/amazon-lambda-http/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleTestCase.java +++ b/integration-tests/amazon-lambda-http/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleTestCase.java @@ -24,6 +24,16 @@ public void testGetText() throws Exception { testGetText("/hello"); } + @Test + public void testSwaggerUi() throws Exception { + // this tests the FileRegion support in the handler + AwsProxyRequest request = request("/swagger-ui/"); + AwsProxyResponse out = LambdaClient.invoke(AwsProxyResponse.class, request); + Assertions.assertEquals(out.getStatusCode(), 200); + Assertions.assertTrue(body(out).contains("Swagger UI")); + + } + private String body(AwsProxyResponse response) { if (!response.isBase64Encoded()) return response.getBody(); @@ -31,20 +41,23 @@ private String body(AwsProxyResponse response) { } private void testGetText(String path) { - AwsProxyRequest request = new AwsProxyRequest(); - request.setHttpMethod("GET"); - request.setPath(path); + AwsProxyRequest request = request(path); AwsProxyResponse out = LambdaClient.invoke(AwsProxyResponse.class, request); Assertions.assertEquals(out.getStatusCode(), 200); Assertions.assertEquals(body(out), "hello"); Assertions.assertTrue(out.getMultiValueHeaders().getFirst("Content-Type").startsWith("text/plain")); } - @Test - public void test404() throws Exception { + private AwsProxyRequest request(String path) { AwsProxyRequest request = new AwsProxyRequest(); request.setHttpMethod("GET"); - request.setPath("/nowhere"); + request.setPath(path); + return request; + } + + @Test + public void test404() throws Exception { + AwsProxyRequest request = request("/nowhere"); AwsProxyResponse out = LambdaClient.invoke(AwsProxyResponse.class, request); Assertions.assertEquals(out.getStatusCode(), 404); } diff --git a/integration-tests/virtual-http-resteasy/pom.xml b/integration-tests/virtual-http-resteasy/pom.xml index 07812e09b691c..3dafaae9369c0 100644 --- a/integration-tests/virtual-http-resteasy/pom.xml +++ b/integration-tests/virtual-http-resteasy/pom.xml @@ -23,6 +23,10 @@ io.quarkus quarkus-azure-functions-http + + io.quarkus + quarkus-smallrye-openapi + com.microsoft.azure.functions azure-functions-java-library diff --git a/integration-tests/virtual-http-resteasy/src/main/resources/application.properties b/integration-tests/virtual-http-resteasy/src/main/resources/application.properties index 2b68e232f7695..f5683c785a5bd 100644 --- a/integration-tests/virtual-http-resteasy/src/main/resources/application.properties +++ b/integration-tests/virtual-http-resteasy/src/main/resources/application.properties @@ -1 +1,3 @@ quarkus.http.virtual=true +quarkus.swagger-ui.always-include=true + diff --git a/integration-tests/virtual-http-resteasy/src/test/java/io/quarkus/it/virtual/FunctionTest.java b/integration-tests/virtual-http-resteasy/src/test/java/io/quarkus/it/virtual/FunctionTest.java index ed41833c3d548..507198a3a94f8 100644 --- a/integration-tests/virtual-http-resteasy/src/test/java/io/quarkus/it/virtual/FunctionTest.java +++ b/integration-tests/virtual-http-resteasy/src/test/java/io/quarkus/it/virtual/FunctionTest.java @@ -27,20 +27,22 @@ @QuarkusTest public class FunctionTest { @Test - public void testJaxrs() throws Exception { - String uri = "https://foo.com/hello"; - testGET(uri); - testPOST(uri); - } - - @Test - public void testNotFound() { + public void testSwagger() { final HttpRequestMessageMock req = new HttpRequestMessageMock(); - req.setUri(URI.create("https://nowhere.com/badroute")); + req.setUri(URI.create("https://foo.com/swagger-ui/")); req.setHttpMethod(HttpMethod.GET); // Invoke - final HttpResponseMessage ret = new Function().run(req, new ExecutionContext() { + final HttpResponseMessage ret = new Function().run(req, createContext()); + + // Verify + Assertions.assertEquals(ret.getStatus(), HttpStatus.OK); + String body = new String((byte[]) ret.getBody(), StandardCharsets.UTF_8); + Assertions.assertTrue(body.contains("Swagger UI")); + } + + private ExecutionContext createContext() { + return new ExecutionContext() { @Override public Logger getLogger() { return null; @@ -55,7 +57,24 @@ public String getInvocationId() { public String getFunctionName() { return null; } - }); + }; + } + + @Test + public void testJaxrs() throws Exception { + String uri = "https://foo.com/hello"; + testGET(uri); + testPOST(uri); + } + + @Test + public void testNotFound() { + final HttpRequestMessageMock req = new HttpRequestMessageMock(); + req.setUri(URI.create("https://nowhere.com/badroute")); + req.setHttpMethod(HttpMethod.GET); + + // Invoke + final HttpResponseMessage ret = new Function().run(req, createContext()); // Verify Assertions.assertEquals(ret.getStatus(), HttpStatus.NOT_FOUND); @@ -79,22 +98,7 @@ private void testGET(String uri) { req.setHttpMethod(HttpMethod.GET); // Invoke - final HttpResponseMessage ret = new Function().run(req, new ExecutionContext() { - @Override - public Logger getLogger() { - return null; - } - - @Override - public String getInvocationId() { - return null; - } - - @Override - public String getFunctionName() { - return null; - } - }); + final HttpResponseMessage ret = new Function().run(req, createContext()); // Verify Assertions.assertEquals(ret.getStatus(), HttpStatus.OK); @@ -112,22 +116,7 @@ private void testPOST(String uri) { req.getHeaders().put("Content-Type", "text/plain"); // Invoke - final HttpResponseMessage ret = new Function().run(req, new ExecutionContext() { - @Override - public Logger getLogger() { - return null; - } - - @Override - public String getInvocationId() { - return null; - } - - @Override - public String getFunctionName() { - return null; - } - }); + final HttpResponseMessage ret = new Function().run(req, createContext()); // Verify Assertions.assertEquals(ret.getStatus(), HttpStatus.OK);