Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<Operation> {

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);
}

}
40 changes: 40 additions & 0 deletions libraries/src/main/java/com/baeldung/netty/HttpMessageHandler.java
Original file line number Diff line number Diff line change
@@ -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<FullHttpRequest> {

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");
}

}
}
49 changes: 49 additions & 0 deletions libraries/src/main/java/com/baeldung/netty/Operation.java
Original file line number Diff line number Diff line change
@@ -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 + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}

}