How to Integrate Netty with Spring MVC and Build a Simple HTTP Server
This guide explains the fundamentals of Netty, shows how to combine it with Spring MVC using DispatcherServlet, and provides complete Java code examples for a Netty server, channel pipeline, request handler, server startup, and a sample controller, along with practical configuration tips.
First, understand Netty: a client establishes a long TCP connection with the server, and both client and server process data through a ChannelPipeline using the addLast order, similar to the filter chain in Struts.
Integrating Netty with Spring MVC
After the client‑server connection is created, a class added to the pipeline forwards the request to DispatcherServlet for processing, then sends the result back to the client via ChannelHandlerContext or Channel using writeAndFlush.
1. Netty server implementation
package com.magic.netty.server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.DispatcherServlet;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import com.magic.netty.HttpServerInitializer;
public class NettyHttpServer {
private int port;
private static Logger log = LoggerFactory.getLogger(NettyHttpServer.class);
private DispatcherServlet servlet;
public NettyHttpServer(Integer port) { this.port = port; }
public NettyHttpServer(Integer port, DispatcherServlet servlet) { this.port = port; this.servlet = servlet; }
public void start() {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new HttpServerInitializer(servlet))
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
System.out.println("NettyHttpServer Run successfully");
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} catch (Exception e) {
log.error("NettyServer start fail", e);
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}2. Initialize Netty channel pipeline
package com.magic.netty;
import org.springframework.web.servlet.DispatcherServlet;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
private DispatcherServlet servlet;
public HttpServerInitializer(DispatcherServlet servlet) { this.servlet = servlet; }
public HttpServerInitializer() {}
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("aggregator", new HttpObjectAggregator(2147483647));
pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
pipeline.addLast("deflater", new HttpContentCompressor());
pipeline.addLast("handler", new HttpRequestHandler(servlet));
}
}3. Handler that processes client requests
package com.magic.netty;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;
import javax.servlet.ServletContext;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import io.netty.util.CharsetUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.util.*;
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private final DispatcherServlet servlet;
private final ServletContext servletContext;
private static final Logger logger = LoggerFactory.getLogger(HttpRequestHandler.class);
public HttpRequestHandler(DispatcherServlet servlet) {
this.servlet = servlet;
this.servletContext = servlet.getServletConfig().getServletContext();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception {
logger.error(e.getMessage(), e);
ctx.close();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
boolean isHttp = HttpMethod.POST.equals(request.method()) || HttpMethod.GET.equals(request.method());
Map<String, String> paramMap = getRequestParams(ctx, request);
if (isHttp && ctx.channel().isActive()) {
MockHttpServletResponse servletResponse = new MockHttpServletResponse();
MockHttpServletRequest servletRequest = new MockHttpServletRequest(servletContext);
// copy headers
for (String name : request.headers().names()) {
for (String value : request.headers().getAll(name)) {
servletRequest.addHeader(name, value);
}
}
// URI handling
String uri = new String(request.getUri().getBytes("ISO8859-1"), "UTF-8");
uri = URLDecoder.decode(uri, "UTF-8");
UriComponents uriComponents = UriComponentsBuilder.fromUriString(uri).build();
String path = URLDecoder.decode(uriComponents.getPath(), "UTF-8");
servletRequest.setRequestURI(path);
servletRequest.setServletPath(path);
servletRequest.setMethod(request.method().name());
if (uriComponents.getScheme() != null) servletRequest.setScheme(uriComponents.getScheme());
if (uriComponents.getHost() != null) servletRequest.setServerName(uriComponents.getHost());
if (uriComponents.getPort() != -1) servletRequest.setServerPort(uriComponents.getPort());
// body
ByteBuf content = request.content();
byte[] data = new byte[content.readableBytes()];
content.readBytes(data);
servletRequest.setContent(data);
// query string and parameters
try {
if (uriComponents.getQuery() != null) {
servletRequest.setQueryString(UriUtils.decode(uriComponents.getQuery(), "UTF-8"));
}
if (paramMap != null && !paramMap.isEmpty()) {
for (String key : paramMap.keySet()) {
servletRequest.addParameter(
UriUtils.decode(key, "UTF-8"),
UriUtils.decode(paramMap.get(key) == null ? "" : paramMap.get(key), "UTF-8")
);
}
}
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
// invoke Spring MVC
servlet.service(servletRequest, servletResponse);
// build Netty response
HttpResponseStatus status = HttpResponseStatus.valueOf(servletResponse.getStatus());
String result = StringUtils.defaultString(servletResponse.getContentAsString());
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status,
Unpooled.copiedBuffer(result, CharsetUtil.UTF_8));
response.headers().set("Content-Type", "text/json;charset=UTF-8");
response.headers().set("Access-Control-Allow-Origin", "*");
response.headers().set("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With,X-File-Name");
response.headers().set("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
response.headers().set("Content-Length", response.content().readableBytes());
response.headers().set("Connection", "keep-alive");
ChannelFuture writeFuture = ctx.writeAndFlush(response);
writeFuture.addListener(ChannelFutureListener.CLOSE);
}
}
private Map<String, String> getRequestParams(ChannelHandlerContext ctx, HttpRequest req) {
Map<String, String> requestParams = new HashMap<>();
if (req.method() == HttpMethod.GET) {
QueryStringDecoder decoder = new QueryStringDecoder(req.getUri());
for (Map.Entry<String, List<String>> entry : decoder.parameters().entrySet()) {
requestParams.put(entry.getKey(), entry.getValue().get(0));
}
}
if (req.method() == HttpMethod.POST) {
HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), req);
for (InterfaceHttpData data : decoder.getBodyHttpDatas()) {
if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
MemoryAttribute attribute = (MemoryAttribute) data;
requestParams.put(attribute.getName(), attribute.getValue());
}
}
}
return requestParams;
}
}4. Start the Netty server with a Spring DispatcherServlet
package com.magic;
import javax.servlet.ServletException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import com.magic.netty.server.NettyHttpServer;
public class MagicWebServer {
private static Logger logger = LoggerFactory.getLogger(MagicWebServer.class);
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
int port = 6001;
DispatcherServlet servlet = getDispatcherServlet(ctx);
NettyHttpServer server = new NettyHttpServer(port, servlet);
server.start();
}
public static DispatcherServlet getDispatcherServlet(ApplicationContext ctx) {
XmlWebApplicationContext mvcContext = new XmlWebApplicationContext();
mvcContext.setConfigLocation("classpath:spring-servlet.xml");
mvcContext.setParent(ctx);
MockServletConfig servletConfig = new MockServletConfig(mvcContext.getServletContext(), "dispatcherServlet");
DispatcherServlet dispatcherServlet = new DispatcherServlet(mvcContext);
try {
dispatcherServlet.init(servletConfig);
} catch (ServletException e) {
e.printStackTrace();
}
return dispatcherServlet;
}
}5. Sample Spring MVC controller and testing URL
package com.magic.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSONObject;
@Controller
@RequestMapping(value="/user", produces="text/json;charset=utf-8")
public class UserController {
@RequestMapping("/login")
@ResponseBody
public String login(String username, String pwd) {
JSONObject resultJson = new JSONObject();
Map<String, String> loginResult = new HashMap<>();
loginResult.put("username", username);
loginResult.put("age", "20");
loginResult.put("sex", "boy");
resultJson.put("code", 200);
resultJson.put("msg", "登录成功");
resultJson.put("result", loginResult);
return JSONObject.toJSONString(resultJson);
}
}Notes: Ensure that applicationContext.xml and spring-servlet.xml are configured according to standard Spring MVC conventions. If the client receives garbled Chinese characters, add produces = "text/json;charset=utf-8" to the @RequestMapping.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
