From 9693d029e3cbd42410f2e693c2af98abd5d46242 Mon Sep 17 00:00:00 2001 From: Radu Tamas Date: Tue, 10 Apr 2018 14:19:52 +0300 Subject: [PATCH] BAEL-1372: Adding jUnits for Testing Netty with EmbeddedChannel article --- .../netty/CalculatorOperationHandler.java | 49 ++++++++ .../baeldung/netty/HttpMessageHandler.java | 40 +++++++ .../java/com/baeldung/netty/Operation.java | 49 ++++++++ .../netty/EmbeddedChannelUnitTest.java | 107 ++++++++++++++++++ 4 files changed, 245 insertions(+) create mode 100644 libraries/src/main/java/com/baeldung/netty/CalculatorOperationHandler.java create mode 100644 libraries/src/main/java/com/baeldung/netty/HttpMessageHandler.java create mode 100644 libraries/src/main/java/com/baeldung/netty/Operation.java create mode 100644 libraries/src/test/java/com/baeldung/netty/EmbeddedChannelUnitTest.java diff --git a/libraries/src/main/java/com/baeldung/netty/CalculatorOperationHandler.java b/libraries/src/main/java/com/baeldung/netty/CalculatorOperationHandler.java new file mode 100644 index 000000000000..4d4cba259bae --- /dev/null +++ b/libraries/src/main/java/com/baeldung/netty/CalculatorOperationHandler.java @@ -0,0 +1,49 @@ +package com.baeldung.netty; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.*; +import io.netty.util.CharsetUtil; + +public class CalculatorOperationHandler extends SimpleChannelInboundHandler { + + protected void channelRead0(ChannelHandlerContext ctx, Operation msg) throws Exception { + Long result = calculateEndpoint(msg); + sendHttpResponse(ctx, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CREATED), result.toString()); + ctx.fireChannelRead(result); + } + + private long calculateEndpoint(Operation operation) { + + String operator = operation.getOperator().toLowerCase().trim(); + switch (operator) { + case "add": + return operation.getNumber1() + operation.getNumber2(); + case "multiply": + return operation.getNumber1() * operation.getNumber2(); + default: + throw new IllegalArgumentException("Operation not defined"); + } + } + + public static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpResponse res, String content) { + + // Generate an error page if response getStatus code is not OK (200). + ByteBuf buf = Unpooled.copiedBuffer(content, CharsetUtil.UTF_8); + res.content().writeBytes(buf); + + HttpUtil.setContentLength(res, res.content().readableBytes()); + + ctx.channel().writeAndFlush(res); + } + + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) + throws Exception { + + sendHttpResponse(ctx, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR), "Operation not defined"); + ctx.fireExceptionCaught(cause); + } + +} diff --git a/libraries/src/main/java/com/baeldung/netty/HttpMessageHandler.java b/libraries/src/main/java/com/baeldung/netty/HttpMessageHandler.java new file mode 100644 index 000000000000..58a81432b6e3 --- /dev/null +++ b/libraries/src/main/java/com/baeldung/netty/HttpMessageHandler.java @@ -0,0 +1,40 @@ +package com.baeldung.netty; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMethod; + +public class HttpMessageHandler extends SimpleChannelInboundHandler { + + protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception { + + String uri = msg.uri(); + HttpMethod httpMethod = msg.method(); + HttpHeaders headers = msg.headers(); + + if (HttpMethod.GET == httpMethod) { + + String[] uriComponents = uri.split("[?]"); + String endpoint = uriComponents[0]; + String[] queryParams = uriComponents[1].split("&"); + + if ("/calculate".equalsIgnoreCase(endpoint)) { + + String[] firstQueryParam = queryParams[0].split("="); + String[] secondQueryParam = queryParams[1].split("="); + + Integer a = Integer.valueOf(firstQueryParam[1]); + Integer b = Integer.valueOf(secondQueryParam[1]); + String operator = headers.get("operator"); + + Operation operation = new Operation(a, b, operator); + ctx.fireChannelRead(operation); + } + } else { + throw new UnsupportedOperationException("HTTP method not supported"); + } + + } +} diff --git a/libraries/src/main/java/com/baeldung/netty/Operation.java b/libraries/src/main/java/com/baeldung/netty/Operation.java new file mode 100644 index 000000000000..136316ed7c4f --- /dev/null +++ b/libraries/src/main/java/com/baeldung/netty/Operation.java @@ -0,0 +1,49 @@ +package com.baeldung.netty; + +import java.io.Serializable; + +public class Operation implements Serializable { + + private Integer number1; + private Integer number2; + private String operator; + + public Operation(Integer number1, Integer number2, String operator) { + this.number1 = number1; + this.number2 = number2; + this.operator = operator; + } + + public Integer getNumber1() { + return number1; + } + + public void setNumber1(Integer number1) { + this.number1 = number1; + } + + public Integer getNumber2() { + return number2; + } + + public void setNumber2(Integer number2) { + this.number2 = number2; + } + + public String getOperator() { + return operator; + } + + public void setOperator(String operator) { + this.operator = operator; + } + + @Override + public String toString() { + return "Operation{" + + "number1=" + number1 + + ", number2=" + number2 + + ", operator='" + operator + '\'' + + '}'; + } +} diff --git a/libraries/src/test/java/com/baeldung/netty/EmbeddedChannelUnitTest.java b/libraries/src/test/java/com/baeldung/netty/EmbeddedChannelUnitTest.java new file mode 100644 index 000000000000..8a324ebc72ef --- /dev/null +++ b/libraries/src/test/java/com/baeldung/netty/EmbeddedChannelUnitTest.java @@ -0,0 +1,107 @@ +package com.baeldung.netty; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.nio.charset.Charset; + +import org.junit.Assert; +import org.junit.Test; + +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; + +public class EmbeddedChannelUnitTest { + + @Test + public void givenTwoChannelHandlers_testPipeline() { + + final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/calculate?a=10&b=5"); + httpRequest.headers().add("Operator", "Add"); + + EmbeddedChannel channel = new EmbeddedChannel( + new HttpMessageHandler(), new CalculatorOperationHandler()); + + channel.pipeline() + .addFirst(new HttpMessageHandler()) + .addLast(new CalculatorOperationHandler()); + + + // send HTTP request to server and check that the message is on the inbound pipeline + assertTrue(channel.writeInbound(httpRequest)); + + long inboundChannelResponse = channel.readInbound(); + assertEquals(15, inboundChannelResponse); + + // we should have an outbound message in the form of a HTTP response + assertEquals(1, channel.outboundMessages().size()); + // Object response = channel.readOutbound(); + + FullHttpResponse httpResponse = channel.readOutbound(); + String httpResponseContent = httpResponse.content().toString(Charset.defaultCharset()); + assertTrue("15".equalsIgnoreCase(httpResponseContent)); + } + + @Test + public void givenTwoChannelHandlers_testExceptionHandlingInHttpMessageHandler() { + + EmbeddedChannel channel = new EmbeddedChannel( + new HttpMessageHandler(), new CalculatorOperationHandler()); + + final FullHttpRequest wrongHttpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/calculate?a=10&b=5"); + wrongHttpRequest.headers().add("Operator", "Add"); + + try { + // send invalid HTTP request to server and expect and error + channel.pipeline().fireChannelRead(wrongHttpRequest); + channel.checkException(); + // channel.writeInbound(wrongHttpRequest); + Assert.fail(); + + } catch (Exception ex) { + + // the HttpMessageHandler does not handle the exception and throws it down the pipeline + assertTrue(ex instanceof UnsupportedOperationException); + assertTrue(ex.getMessage().equalsIgnoreCase("HTTP method not supported")); + + FullHttpResponse errorHttpResponse = channel.readOutbound(); + String errorHttpResponseContent = errorHttpResponse.content().toString(Charset.defaultCharset()); + assertTrue("Operation not defined".equalsIgnoreCase(errorHttpResponseContent)); + assertEquals(HttpResponseStatus.INTERNAL_SERVER_ERROR, errorHttpResponse.status()); + } + } + + + @Test + public void givenTwoChannelHandlers_testExceptionHandlingInCalculatorOperationHandler() { + EmbeddedChannel channel = new EmbeddedChannel( + new HttpMessageHandler(), new CalculatorOperationHandler()); + + final FullHttpRequest wrongHttpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/calculate?a=10&b=5"); + wrongHttpRequest.headers().add("Operator", "Invalid_operation"); + + try { + // send invalid HTTP request to server and expect and error + channel.writeInbound(wrongHttpRequest); + Assert.fail(); + + } catch (Exception ex) { + + // the HttpMessageHandler does not handle the exception and throws it down the pipeline + assertTrue(ex instanceof IllegalArgumentException); + assertTrue(ex.getMessage().equalsIgnoreCase("Operation not defined")); + + // the outbound message is a HTTP response with the status code 500 + FullHttpResponse errorHttpResponse = channel.readOutbound(); + String errorHttpResponseContent = errorHttpResponse.content().toString(Charset.defaultCharset()); + assertTrue("Operation not defined".equalsIgnoreCase(errorHttpResponseContent)); + assertEquals(HttpResponseStatus.INTERNAL_SERVER_ERROR, errorHttpResponse.status()); + } + } + +}