Netty是一个基于反应器模式、高性能的网络编程框架,提供了丰富的编解码器及各种协议的支持,包括http、http2、mqtt、dns等等。
以学习为目的,基于Netty实现一个简单的静态web服务器。
使用到的技术及开发环境:
基于netty实现静态web服务器。
能够提供基本的http服务,仅支持静态资源服务即可。
支持静的态资源包括:HTML、JS、CSS、常见的图片(包括但不限于jpeg、png、svg、webp、gif等格式)、字体文件以及其他文件。
以上资源在响应到客户端时必须给与正确的MimeType(http中称MediaType),例如:html文件响应时,必须指定Content-Type:text/html
响应头。
其他资源文件:非web资源文件,例如压缩文件,返回响应头Content-type:application/octet-stream
。(大多数浏览器对a标签Content-Type:application/octet-stream
响应头的处理方式为直接下载)。
支持HTTP1.1、HTTPS(HTTP over TLS)和H2。
返回静态资源时,生成last-modified
和Etag
响应头,使浏览器能够缓存资源。支持If-modified-since
和If-match
请求头,后续请求依此判断缓存的资源是否有效。
支持Range
请求头,能够依据此头返回部分内容(状态码:206)。
web服务器的一些配置能够可配置,支持的配置方式包括但不限于:
支持的配置项,包括但不限于:
能够记录服务器运行日志且日志参数可配置。
程序打包后以类似于Tomcat的方式提供一个启动脚本,一键启动服务器。
至少支持Windows和linux系统。
该编解码器是通道双向编解码器,包含了请求解码器和响应编码器,是Netty中Http服务器实现的核心类。下面是最全的构造方法:
public HttpServerCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders,
int initialBufferSize, boolean allowDuplicateContentLengths, boolean allowPartialChunks) {
init(new HttpServerRequestDecoder(maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders,
initialBufferSize, allowDuplicateContentLengths, allowPartialChunks),
new HttpServerResponseEncoder());
}
各参数解释:
控制内存使用的参数:
控制解析行为的参数:
Content-Length
头。如果设为false将拒绝该报文,设为true也只允许Content-Length的值为相同的十进制,否则拒绝。该处理器作用是聚合多个Http请求或响应部分为一个完整的FullHttpRequest 或FullHttpResponse。
构造函数如下:
public HttpObjectAggregator(int maxContentLength, boolean closeOnExpectationFailed) {
super(maxContentLength);
this.closeOnExpectationFailed = closeOnExpectationFailed;
}
参数解释:
Netty使用SSLHandler来实现高层应用的数据加密。
SSLHandler的初始化:
SslContext sslContext = SslContextBuilder
.forServer(sslConfig.getCert(), sslConfig.getPrivateKey())
.clientAuth(ClientAuth.NONE)
.build();
SSLEngine sslEngine = sslContext.newEngine(channel.alloc()); sslEngine.setUseClientMode(false);
SslHandler sslHandler = new SslHandler(sslEngine, false);
channel.pipeline().addLast("ssl", sslHandler);
package com.github.cloudgyb.webserver;
import com.github.cloudgyb.webserver.config.SSLConfig;
import com.github.cloudgyb.webserver.config.WebServerConfig;
import com.github.cloudgyb.webserver.http.HttpRequestHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.ssl.*;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
/**
* 用于启动web服务器
*
* @author cloudgyb
*/
public class WebServer {
private final static Logger logger = LoggerFactory.getLogger(WebServer.class);
public static void start(WebServerConfig config) {
ServerBootstrap serverBootstrap = new ServerBootstrap();
NioEventLoopGroup boosGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
HttpRequestHandler httpRequestHandler = new HttpRequestHandler(config);
SSLConfig sslConfig = config.getSslConfig();
boolean hasSSLConfig = false;
SslContext sslContext = null;
if (sslConfig != null) {
try {
sslContext = SslContextBuilder
.forServer(sslConfig.getCert(), sslConfig.getPrivateKey())
.clientAuth(ClientAuth.NONE)
.build();
hasSSLConfig = true;
} catch (SSLException e) {
throw new RuntimeException(e);
}
}
try {
boolean finalHasSSLConfig = hasSSLConfig;
SslContext finalSslContext = sslContext;
ChannelFuture cf = serverBootstrap.group(boosGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, config.getTcpBacklog())
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel channel) {
if (finalHasSSLConfig) {
SSLEngine sslEngine = finalSslContext.newEngine(channel.alloc());
sslEngine.setUseClientMode(false);
SslHandler sslHandler = new SslHandler(sslEngine, false);
channel.pipeline()
.addLast("ssl", sslHandler);
}
channel.pipeline()
.addLast("httpRequestDecoder", new HttpServerCodec())
.addLast("httpAgg", new HttpObjectAggregator(512))
.addLast("chunked", new ChunkedWriteHandler())
.addLast("httpRequestHandler", httpRequestHandler);
}
})
.bind(config.getHost(), config.getPort());
logger.info("Web Server listen at " + config.getHost() + ":" + config.getPort() + "...");
cf.sync().channel().closeFuture().sync();
} catch (InterruptedException e) {
logger.info("被中断退出...");
throw new RuntimeException(e);
} finally {
workerGroup.shutdownGracefully();
boosGroup.shutdownGracefully();
}
}
}
完整代码在github:https://github.com/cloudgyb/static-webserver
RFC7230: Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
RFC7231: Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
RFC7232: Hypertext Transfer Protocol (HTTP/1.1): Conditional Requests
RFC7233: Hypertext Transfer Protocol (HTTP/1.1): Range Requests
MDN: MDN HTTP