共计 7242 个字符,预计需要花费 19 分钟才能阅读完成。
前言:对于微服务来说,如果咱们要实现一个 web 服务,
大部分人可能间接用 springboot 的 spring-boot-starter-web 了。
咱们晓得 spring-boot-starter-web 默认实现是 tomcat,当然你也能够抉择其余服务器类型,比方 Jetty、Undertow 等。
然而如果是一个非 springboot 我的项目,该如何实现呢?
这里介绍了下四种实现形式,基于 Tomcat、Jetty、JdkHttp、Netty 实现内嵌 web 容器。
Tomcat
依赖的 maven 坐标:
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<exclusions>
<!-- 避免注解抵触,排除 tomcat 自带注解,springboot 也是这样设置的 -->
<exclusion>
<artifactId>tomcat-annotations-api</artifactId>
<groupId>org.apache.tomcat</groupId>
</exclusion>
</exclusions>
</dependency>
首先看下初始化启动的代码:
Tomcat tomcatServer = new Tomcat();
// 静默形式启动
tomcatServer.setSilent(true);
tomcatServer.setPort(8080);
// 是否设置主动部署
tomcatServer.getHost().setAutoDeploy(false);
// 创立上下文, 拿到上下文后就能够设置整个拜访地址了
StandardContext standardContext = new StandardContext();
standardContext.setPath(CONTEX_PATH);
// 监听上下文
standardContext.addLifecycleListener(new Tomcat.FixContextListener());
// tomcat 容器增加 standardContext 增加整个 context
tomcatServer.getHost().addChild(standardContext);
// 创立 servlet servlet 的名字叫 IndexServlet
tomcatServer.addServlet(CONTEX_PATH, SERVLET_NAME, new JdkSimpleDispatchServlet());
// 增加 servleturl 映射
standardContext.addServletMappingDecoded("/*", SERVLET_NAME);
try {tomcatServer.start();
server = tomcatServer;
} catch (Exception e) {}
下面 tomcat 在注册 servlet 的时候,自定义了一个 Servlet,而后映射了 /* 的申请。能够查看下 JdkSimpleDispatchServlet 这个类,代码如下:
@Slf4j
public class JdkSimpleDispatchServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {log.info("path:{}, clientIp:{}", req.getRequestURL(), req.getRemoteHost());
PrintWriter writer = resp.getWriter();
writer.print("this is index");
writer.close();}
}
Jetty
Jetty 和 tomcat 十分相似,也是调用 start 办法启动。
maven 依赖如下:
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
</dependency>
<!-- 增加 servlet 模块 -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
</dependency>
jetty 服务初始化代码更简略,如下:
Server server = new Server(NetworkUtil.getHealthServerPort());
// ServletHandler 是一种简略的创立上下文处理程序的简略办法,该上下文处理程序由 Servlet 实例反对。而后,须要将该处理程序注册到 Server 对象。ServletHandler handler = new ServletHandler();
server.setHandler(handler);
// 这是原始 Servlet,而不是已配置的 Servlet,JdkSimpleDispatchServlet 和 tomcat 的相似
handler.addServletWithMapping(JdkSimpleDispatchServlet.class, "/*");
try {
// Start things up!
server.start();
jetty = server;
} catch (Exception e) {
}
Netty
说了 Tomcat 和 Jetty,咱们再看下 Netty,之前所在的一家公司就是基于 Netty 封装了 Web 服务,Netty 对 Web 反对也比较完善,默认基于 NIO 的多路复用 IO 模型反对单机上万的吞肚量。
看下 pom 依赖:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
启动形式:
public class NettyHttpServerHealthCheckServer implements IHealthCheckServer {
private static ExecutorService Pool = Executors.newSingleThreadExecutor(new NamedThreadFactory("Netty-HealthCheck-Pool", true));
ServerBootstrap bootstrap = new ServerBootstrap();
// boss 线程,只需一个
EventLoopGroup boss = new NioEventLoopGroup();
// work 线程
EventLoopGroup work = new NioEventLoopGroup();
@Override
public void start() {
try {
// 因为监听服务是阻塞的,须要线程池异步监听
Pool.execute(() -> {
try {
// 配置 channel、handle
bootstrap.group(boss, work)
// .handler(new LoggingHandler(LogLevel.INFO))
.channel(NioServerSocketChannel.class)
// HttpServerInitializer 即 http 编码解码和解决配置器
.childHandler(new HttpServerInitializer());
ChannelFuture f = bootstrap.bind(new InetSocketAddress(NetworkUtil.getHealthServerPort())).sync();
// 阻塞监听
f.channel().closeFuture().sync();} catch (Exception e) {}});
} catch (Exception e) {return;}
}
@Override
public void stop() {boss.shutdownGracefully();
work.shutdownGracefully();}
class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new HttpServerCodec());// http 编解码
pipeline.addLast("httpAggregator", new HttpObjectAggregator(512 * 1024)); // http 音讯聚合器 512*1024 为接管的最大 contentlength
pipeline.addLast(new HttpRequestHandler());// 申请处理器
}
}
class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {ctx.flush();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
/**
* 100 Continue
* 是这样的一种状况:HTTP 客户端程序有一个实体的主体局部要发送给服务器,但心愿在发送之前查看下服务器是否会
* 承受这个实体,所以在发送实体之前先发送了一个携带 100
* Continue 的 Expect 申请首部的申请。服务器在收到这样的申请后,应该用 100 Continue 或一条错误码来进行响应。*/
if (is100ContinueExpected(req)) {
ctx.write(new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.CONTINUE));
}
// 获取申请的 uri
String path = req.uri();
// 响应申请
fireResponse(ctx, HttpResponseStatus.OK,
"hello", "text/html; charset=utf-8");
}
private void fireResponse(ChannelHandlerContext ctx, HttpResponseStatus httpResponseStatus, String resp, String contentType) {
FullHttpResponse responseDown = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
httpResponseStatus,
Unpooled.copiedBuffer(resp, Charset.defaultCharset()));
responseDown.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType);
ctx.writeAndFlush(responseDown).addListener(ChannelFutureListener.CLOSE);
}
}
}
从下面代码能够得悉,Netty 默认就提供了 http 编解码和协定的实现,十分不便。
JdkHttp
最好介绍下不依赖第三方实现,应用 JDK8 内置的 Http Server 实现。
HttpServer 类
外围类 HttpServer,HttpServer 是属于 rt 包的类,须要下载 rt 包的源码,配置到 IDEA。或者间接应用 openjdk,也能够查看到源码。
rt 包能够下载 OpenJDK 的源码,https://download.java.net/openjdk/jdk8/promoted/b132/openjdk-8-src-b132-03_mar_2014.zip
HttpServer 源码:
package com.sun.net.httpserver;
@Exported
public abstract class HttpServer {protected HttpServer() { }
public static HttpServer create() throws IOException {return create((InetSocketAddress)null, 0);
}
public static HttpServer create(InetSocketAddress var0, int var1) throws IOException {HttpServerProvider var2 = HttpServerProvider.provider();
return var2.createHttpServer(var0, var1);
}
public abstract void bind(InetSocketAddress var1, int var2) throws IOException;
public abstract void start();
public abstract void setExecutor(Executor var1);
public abstract Executor getExecutor();
public abstract void stop(int var1);
public abstract HttpContext createContext(String var1, HttpHandler var2);
public abstract HttpContext createContext(String var1);
public abstract void removeContext(String var1) throws IllegalArgumentException;
public abstract void removeContext(HttpContext var1);
public abstract InetSocketAddress getAddress();}
初始化 Http 服务:
public class JDKHttpServerHealthCheckServer implements IHealthCheckServer {
HttpServer server;
@Override
public void start() {
try {
// 初始化监听
server = HttpServer.create(new InetSocketAddress(8080), 100);
// 注册 http 申请解决类
server.createContext("/", new JdkHttpHandler());
// 启动服务
server.start();} catch (Exception e) {return;}
}
@Override
public void stop() {if (server != null) {server.stop(0);
}
}
static class JdkHttpHandler implements HttpHandler {
@Override
public void handle(HttpExchange httpExchange) throws IOException {String path = httpExchange.getRequestURI() == null ? "/" : httpExchange.getRequestURI().getPath();
try {CharArrayWriter charArrayWriter = new CharArrayWriter();
charArrayWriter.write("hello");
httpExchange.getResponseHeaders().add("Content-Type", "text/plain; charset=utf-8");
// 这里必须指定字节大小,因为默认是固定大小的编码解码实现
httpExchange.sendResponseHeaders(200, charArrayWriter.size());
outputStream.write(charArrayWriter.toString().getBytes());
} catch (Exception e) {httpExchange.sendResponseHeaders(500, 0);
}
}
}
}
从下面四个实现来看,对 http servlet 标准反对比较完善的有 Jetty、Tomcat。性能高的是 Netty,实现最简略的是 JDK 默认 HttpServer。
本文由猿必过 YBG 公布