1. Gambaran keseluruhan
Dalam tutorial ini, kita akan melaksanakan pelayan casing atas sederhana melalui HTTP dengan Netty , kerangka tak segerak yang memberi kita fleksibiliti untuk mengembangkan aplikasi jaringan di Java.
2. Pelayan Bootstrapping
public class HttpServer { private int port; private static Logger logger = LoggerFactory.getLogger(HttpServer.class); // constructor // main method, same as simple protocol server public void run() throws Exception { ... ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new HttpRequestDecoder()); p.addLast(new HttpResponseEncoder()); p.addLast(new CustomHttpServerHandler()); } }); ... } }
Kami menambah tiga pengendali ke saluran paip pelayan:
- Netty's HttpResponseEncoder - untuk bersiri
- Netty's HttpRequestDecoder - untuk deserialisasi
- CustomHttpServerHandler kami sendiri - untuk menentukan tingkah laku pelayan kami
Mari lihat pengendali terakhir secara terperinci seterusnya.
3. CustomHttpServerHandler
Tugas pengendali khusus kami adalah memproses data masuk dan menghantar respons.
Mari memecahkannya untuk memahami cara kerjanya.
3.1. Struktur Penangan
public class CustomHttpServerHandler extends SimpleChannelInboundHandler { private HttpRequest request; StringBuilder responseData = new StringBuilder(); @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); } @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) { // implementation to follow } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } }
Setakat ini, semua yang kita lihat ialah kod plat boiler
Sekarang mari kita teruskan dengan perkara menarik, pelaksanaan channelRead0 .
3.2. Membaca Saluran
if (msg instanceof HttpRequest) { HttpRequest request = this.request = (HttpRequest) msg; if (HttpUtil.is100ContinueExpected(request)) { writeResponse(ctx); } responseData.setLength(0); responseData.append(RequestUtils.formatParams(request)); } responseData.append(RequestUtils.evaluateDecoderResult(request)); if (msg instanceof HttpContent) { HttpContent httpContent = (HttpContent) msg; responseData.append(RequestUtils.formatBody(httpContent)); responseData.append(RequestUtils.evaluateDecoderResult(request)); if (msg instanceof LastHttpContent) { LastHttpContent trailer = (LastHttpContent) msg; responseData.append(RequestUtils.prepareLastResponse(request, trailer)); writeResponse(ctx, trailer, responseData); } }
private void writeResponse(ChannelHandlerContext ctx) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, CONTINUE, Unpooled.EMPTY_BUFFER); ctx.write(response); }
StringBuilder formatParams(HttpRequest request) { StringBuilder responseData = new StringBuilder(); QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri()); Map
params = queryStringDecoder.parameters(); if (!params.isEmpty()) { for (Entry
p : params.entrySet()) { String key = p.getKey(); List vals = p.getValue(); for (String val : vals) { responseData.append("Parameter: ").append(key.toUpperCase()).append(" = ") .append(val.toUpperCase()).append("\r\n"); } } responseData.append("\r\n"); } return responseData; }
StringBuilder formatBody(HttpContent httpContent) { StringBuilder responseData = new StringBuilder(); ByteBuf content = httpContent.content(); if (content.isReadable()) { responseData.append(content.toString(CharsetUtil.UTF_8).toUpperCase()) .append("\r\n"); } return responseData; }
StringBuilder prepareLastResponse(HttpRequest request, LastHttpContent trailer) { StringBuilder responseData = new StringBuilder(); responseData.append("Good Bye!\r\n"); if (!trailer.trailingHeaders().isEmpty()) { responseData.append("\r\n"); for (CharSequence name : trailer.trailingHeaders().names()) { for (CharSequence value : trailer.trailingHeaders().getAll(name)) { responseData.append("P.S. Trailing Header: "); responseData.append(name).append(" = ").append(value).append("\r\n"); } } responseData.append("\r\n"); } return responseData; }
3.3. Menulis Respons
private void writeResponse(ChannelHandlerContext ctx, LastHttpContent trailer, StringBuilder responseData) { boolean keepAlive = HttpUtil.isKeepAlive(request); FullHttpResponse httpResponse = new DefaultFullHttpResponse(HTTP_1_1, ((HttpObject) trailer).decoderResult().isSuccess() ? OK : BAD_REQUEST, Unpooled.copiedBuffer(responseData.toString(), CharsetUtil.UTF_8)); httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8"); if (keepAlive) { httpResponse.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, httpResponse.content().readableBytes()); httpResponse.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); } ctx.write(httpResponse); if (!keepAlive) { ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); } }
Untuk menguji pelayan kami, mari hantar beberapa arahan cURL dan lihat tindak balasnya.
Sudah tentu, kita perlu memulakan pelayan dengan menjalankan kelas HttpServer sebelum ini .
4.1. DAPATKAN Permintaan
Mari mula-mula memanggil pelayan, memberikan permintaan dengan kuki:
curl //127.0.0.1:8080?param1=one
Sebagai tindak balas, kami mendapat:
Parameter: PARAM1 = ONE Good Bye!
Kita juga boleh menekan //127.0.0.1:8080?param1=one dari mana-mana penyemak imbas untuk melihat hasil yang sama.
4.2. Permintaan POST
Sebagai ujian kedua kami, mari hantar POST dengan kandungan sampel badan :
curl -d "sample content" -X POST //127.0.0.1:8080
Inilah tindak balasnya:
SAMPLE CONTENT Good Bye!
Kali ini, kerana permintaan kami berisi isi, pelayan menghantarnya kembali dengan huruf besar .
5. Kesimpulan
Dalam tutorial ini, kami melihat bagaimana menerapkan protokol HTTP, terutama pelayan HTTP menggunakan Netty.
HTTP / 2 di Netty menunjukkan pelaksanaan pelayan klien dari protokol HTTP / 2.
Seperti biasa, kod sumber tersedia di GitHub.