内嵌式服务器不需要我们单独部署,列如SpringBoot默认内嵌服务器Tomcat,它运行在服务内部。使用Netty 编写一个 Http 服务器的程序,类似SpringMvc处理http请求那样。举例:xxl-job项目的核心包没有SpringMvc的Controller层,客户端却可以发送http请求,好奇怪!!!其实xxl-job-core 内部使用Netty做了HttpServer。
package com.xxl.job.executor.test.dto;
import lombok.Getter;
import lombok.Setter;
/**
* User: ldj
* Date: 2024/10/11
* Time: 11:23
* Description: No Description
*/
@Getter
@Setter
public class RequestDTO<T> {
/**
* 请求ur
*/
private String uri;
/**
* 请求参数
*/
private T param;
}
package com.xxl.job.executor.test.netty;
import com.xxl.job.executor.test.handler.NettyHttpServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.concurrent.TimeUnit;
/**
* User: ldj
* Date: 2024/10/11
* Time: 10:57
* Description: Netty Http服务
*/
public class NettyHttpServer {
public static void main(String[] args) {
// 服务端口
int port = 9990;
// 接收请求线程池
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 处理请求线程池
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS))
.addLast(new HttpServerCodec())
.addLast(new HttpObjectAggregator(5 * 1024 * 1024))
.addLast(new NettyHttpServerHandler(new BaseService())); // 自定义handler
}
});
// bind 绑定端口,启动服务
ChannelFuture future = bootstrap.bind(port).sync();
System.out.println("remote server started!");
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
// 关闭 EventLoopGroup
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
} catch (Exception e) {
System.out.println(e.getMessage() + e);
}
}
}
}
package com.xxl.job.executor.test.netty;
/**
* User: ldj
* Date: 2024/10/11
* Time: 12:55
* Description: No Description
*/
public class BaseService {
public String test(String param) {
return "netty http test--> " + param;
}
}
package com.xxl.job.executor.test.handler;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xxl.job.executor.test.dto.RequestDTO;
import com.xxl.job.executor.test.netty.BaseService;
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.concurrent.DefaultThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* User: ldj
* Date: 2024/10/11
* Time: 11:11
* Description: 处理请求逻辑
*/
public class NettyHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private static final Logger logger = LoggerFactory.getLogger(NettyHttpServerHandler.class);
private BaseService baseService;
public NettyHttpServerHandler(BaseService baseService) {
this.baseService = baseService;
}
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(
200,
300,
5,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2000),
new DefaultThreadFactory("netty-http-server"),
new ThreadPoolExecutor.AbortPolicy());
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest) throws Exception {
RequestDTO<String> requestDTO = parseReqParam(fullHttpRequest);
executor.execute(() -> {
String response = getResponse(requestDTO);
boolean keepAlive = HttpUtil.isKeepAlive(fullHttpRequest);
writeToClient(channelHandlerContext, keepAlive, response);
});
}
private void writeToClient(ChannelHandlerContext channel, boolean keepAlive, String response) {
ByteBuf byteBuf = Unpooled.copiedBuffer(response, StandardCharsets.UTF_8);
DefaultFullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,byteBuf);
HttpHeaders headers = fullHttpResponse.headers();
headers.set(HttpHeaderNames.CONTENT_TYPE,HttpHeaderValues.TEXT_HTML);
headers.set(HttpHeaderNames.CONTENT_LENGTH,fullHttpResponse.content().readableBytes());
if(keepAlive){
headers.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
}
channel.writeAndFlush(fullHttpResponse);
}
private String getResponse(RequestDTO<String> requestDTO) {
String uri = requestDTO.getUri();
String param = requestDTO.getParam();
try {
// 硬编码!更好的做法可参考SpringMvc的解析注解的value放进一个Map<url,Method>
switch (uri){
case "/test":
return baseService.test(param);
default:
return "请求路径不存在!";
}
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
}
private RequestDTO<String> parseReqParam(FullHttpRequest fullHttpRequest) throws JsonProcessingException {
String uri = fullHttpRequest.uri();
String param = null;
logger.info("有参数uri:{}", uri);
HttpMethod method = fullHttpRequest.method();
if (HttpMethod.GET.equals(method)) {
QueryStringDecoder decoder = new QueryStringDecoder(uri);
Map<String, List<String>> parameters = decoder.parameters();
param = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(parameters);
logger.info("parameters -> {}", param);
uri = decoder.rawPath();
logger.info("不带参数uri:{}", uri);
}
if (HttpMethod.POST.equals(method)) {
String contentTypeValue = fullHttpRequest.headers().getAsString(HttpHeaderNames.CONTENT_TYPE);
if(contentTypeValue.contains(HttpHeaderValues.APPLICATION_JSON.toString())){
param = fullHttpRequest.content().toString(StandardCharsets.UTF_8);
}
}
RequestDTO<String> reqData = new RequestDTO<>();
reqData.setUri(uri);
reqData.setParam(param);
return reqData;
}
}