pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.94.Final</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.35</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
HttpServer.java netty服务类
package com.example.springfileupload3.netty.http;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
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 io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
/**
* httpServer
*
* @Description
* @Author wzq
**/
@Slf4j
public class HttpServer {
static final int PORT = 8888;
public void run() throws Exception {
// Configure the server.
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.option(ChannelOption.SO_BACKLOG, 1024);
b.childOption(ChannelOption.TCP_NODELAY, true);
b.childOption(ChannelOption.SO_KEEPALIVE, true);
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new HttpServerInitializer());
ChannelFuture cf = b.bind(PORT).sync();
cf.addListener(future -> {
if (future.isSuccess()) {
log.info("netty服务器绑定端口成功:" + PORT);
} else {
log.error("netty服务器绑定端口失败:" + PORT);
}
});
log.info("netty启动完成!");
//等待netty完成,会堵塞主线程
//cf.channel().closeFuture().sync();
} catch (Exception e){
log.info("netty启动异常了!");
e.printStackTrace();
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
HttpServerInitializer.java netty初始化配置类
package com.example.springfileupload3.netty.http;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpServerExpectContinueHandler;
/**
* HttpServerInitializer
*
* @Description
* @Author wzq
**/
public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
/**
* 或者使用HttpRequestDecoder & HttpResponseEncoder
*/
p.addLast(new HttpServerCodec());
/**
* 在处理Post消息时需要加上
*/
p.addLast(new HttpObjectAggregator(1024*1024));
p.addLast(new HttpServerExpectContinueHandler());
p.addLast(new HttpServerHandler());
}
}
HttpServerInitializer.java 处理类
package com.example.springfileupload3.netty.http;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import io.netty.util.AsciiString;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* HttpServerHandler
*
* @Description
* @Author wzq
**/
@Slf4j
public class HttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
private static final String FAVICON_ICO = "/favicon.ico";
private HttpRequest request;
private HttpHeaders headers;
private FullHttpRequest fullRequest;
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject httpObject) throws Exception {
User user = new User();
user.setUserName("GoslingWu");
user.setDate(new Date());
if (httpObject instanceof HttpRequest) {
request = (HttpRequest) httpObject;
headers = request.headers();
String uri = request.uri();
log.info("http uri:" + uri);
if (FAVICON_ICO.equals(uri)) {
return;
}
HttpMethod method = request.method();
if (method.equals(HttpMethod.GET)) {
log.info("http method GET");
QueryStringDecoder queryDecoder = new QueryStringDecoder(uri, StandardCharsets.UTF_8);
Map<String, List<String>> uriParameters = queryDecoder.parameters();
// 打印
uriParameters.forEach((key, value) -> {
log.info("key:{} , value:{}", key, value);
});
user.setMethod("get");
} else if (method.equals(HttpMethod.POST)) {
log.info("http method POST");
fullRequest = (FullHttpRequest) httpObject;
//根据不同的Content_Type处理body数据
dealWithContentType();
user.setMethod("post");
}
}
JSONSerializer jsonSerializer = new JSONSerializer();
byte[] content = jsonSerializer.serialize(user);
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(content));
response.headers().set("Content-Type", "text/plain");
response.headers().setInt("Content-Length", response.content().readableBytes());
boolean keepAlive = HttpUtil.isKeepAlive(request);
if (!keepAlive) {
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
} else {
response.headers().set("Connection","keep-alive");
ctx.write(response);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.info("exceptionCaught...");
cause.printStackTrace();
ctx.close();
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
log.info("channelReadComplete...");
ctx.flush();
}
/**
* 解析ContentType
*
* @throws Exception
*/
private void dealWithContentType() throws Exception {
String contentType = getContentType();
// 可以使用HttpJsonDecoder
if ("application/json".equals(contentType)) {
String jsonStr = fullRequest.content().toString(StandardCharsets.UTF_8);
JSONObject jsonObject = JSON.parseObject(jsonStr);
jsonObject.forEach((key, value) -> {
log.info("key:{} , value:{}", key, value);
});
} else if ("application/x-www-form-urlencoded".equals(contentType)) {
//方式一:使用 QueryStringDecoder
String jsonStr = fullRequest.content().toString(StandardCharsets.UTF_8);
QueryStringDecoder queryDecoder = new QueryStringDecoder(jsonStr, false);
Map<String, List<String>> uriAttributes = queryDecoder.parameters();
for (Map.Entry<String, List<String>> attr : uriAttributes.entrySet()) {
for (String attrVal : attr.getValue()) {
log.info(attr.getKey() + "=" + attrVal);
}
}
} else if ("multipart/form-data".equals(contentType)) {
//TODO 用于文件上传
} else {
//do nothing...
}
}
/**
* 从请求头中获取 Content-Type
*
* @return
*/
private String getContentType() {
String typeStr = headers.get("Content-Type").toString();
String[] list = typeStr.split(";");
return list[0];
}
}
序列化接口
package com.example.springfileupload3.netty.http;
/**
* 序列化接口类
*
* @Description
* @Author wzq
**/
public interface Serializer {
/**
* java 对象转换成二进制
* @param object
* @return
*/
byte[] serialize(Object object);
/**
* 二进制转换成 java 对象
* @param clazz
* @param bytes
* @return
* @param <T>
*/
<T> T deserialize(Class<T> clazz, byte[] bytes);
}
JSONSerializer.java 序列化类
package com.example.springfileupload3.netty.http;
import com.alibaba.fastjson2.JSON;
import java.util.Date;
/**
* JSON序列化
*
* @Description
* @Author wzq
**/
public class JSONSerializer implements Serializer {
@Override
public byte[] serialize(Object object) {
return JSON.toJSONBytes(object);
}
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) {
return JSON.parseObject(bytes, clazz);
}
}
User.java response返回类
package com.example.springfileupload3.netty.http;
import lombok.Data;
import java.util.Date;
/**
* User
*
* @Description
* @Author wzq
**/
@Data
public class User {
private String userName;
private String method;
private Date date;
}
ProjectCommandLineRunner.java 项目启动时启动类
package com.example.springfileupload3.config;
import com.example.springfileupload3.netty.http.HttpServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Configuration;
/**
* ProjectCommandLineRunner
*
* @Description
* @Author wzq
* @Date 2023/7/20 11:47
**/
@Configuration
@Slf4j
public class ProjectCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
log.info("ProjectCommandLineRunner run...");
HttpServer httpServer = new HttpServer();
httpServer.run();
}
}