Java tutorial
/* * Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except * in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.ballerinalang.test.agent.server; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.ServerChannel; import io.netty.channel.SimpleChannelInboundHandler; 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.DefaultFullHttpResponse; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponseEncoder; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; /** * The WebServer class is a convenience wrapper around the Netty HTTP server. * * @since 0.982.0 */ public class WebServer { private static final String TYPE_PLAIN = "text/plain; charset=UTF-8"; private static final String SERVER_NAME = "BallerinaAgent"; private final RouteTable routeTable; private final String host; private final int port; /** * Creates a new WebServer. * * @param host hostname. * @param port port number. */ public WebServer(String host, int port) { this.routeTable = new RouteTable(); this.host = host; this.port = port; } /** * Adds a GET route. * * @param path The URL path. * @param handler The request handler. * @return This WebServer. */ public WebServer get(final String path, final BHandler handler) { this.routeTable.addRoute(new Route(HttpMethod.GET, path, handler)); return this; } /** * Adds a POST route. * * @param path The URL path. * @param handler The request handler. * @return This WebServer. */ public WebServer post(final String path, final BHandler handler) { this.routeTable.addRoute(new Route(HttpMethod.POST, path, handler)); return this; } /** * Starts the web server. * * @throws Exception in case anything fails */ public void start() throws Exception { start(new NioEventLoopGroup(), NioServerSocketChannel.class); } /** * Initializes the server, socket, and channel. * * @param loopGroup The event loop group. * @param serverChannelClass The socket channel class. * @throws InterruptedException on interruption. */ private void start(final EventLoopGroup loopGroup, final Class<? extends ServerChannel> serverChannelClass) throws InterruptedException { try { final InetSocketAddress inet = new InetSocketAddress(host, port); final ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024); serverBootstrap.option(ChannelOption.SO_REUSEADDR, true); serverBootstrap.group(loopGroup).channel(serverChannelClass).childHandler(new WebServerInitializer()); serverBootstrap.option(ChannelOption.MAX_MESSAGES_PER_READ, Integer.MAX_VALUE); serverBootstrap.childOption(ChannelOption.ALLOCATOR, new PooledByteBufAllocator(true)); serverBootstrap.childOption(ChannelOption.SO_REUSEADDR, true); serverBootstrap.childOption(ChannelOption.MAX_MESSAGES_PER_READ, Integer.MAX_VALUE); final Channel ch = serverBootstrap.bind(inet).sync().channel(); ch.closeFuture().sync(); } finally { loopGroup.shutdownGracefully().sync(); } } /** * The Initializer class initializes the HTTP channel. */ private class WebServerInitializer extends ChannelInitializer<SocketChannel> { /** * Initializes the channel pipeline with the HTTP response handlers. * * @param ch The Channel which was registered. */ @Override public void initChannel(SocketChannel ch) throws Exception { final ChannelPipeline p = ch.pipeline(); p.addLast("decoder", new HttpRequestDecoder(4096, 8192, 8192, false)); p.addLast("aggregator", new HttpObjectAggregator(100 * 1024 * 1024)); p.addLast("encoder", new HttpResponseEncoder()); p.addLast("handler", new WebServerHandler()); } } /** * The Handler class handles all inbound channel messages. */ private class WebServerHandler extends SimpleChannelInboundHandler<Object> { /** * Handles an exception caught. Closes the context. * * @param ctx The channel context. * @param cause The exception. */ @Override public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) { ctx.close(); } /** * Handles read complete event. Flushes the context. * * @param ctx The channel context. */ @Override public void channelReadComplete(final ChannelHandlerContext ctx) { ctx.flush(); } /** * Handles a new message. * * @param channelHandlerContext The channel context. * @param o The HTTP request message. */ @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception { if (!(o instanceof FullHttpRequest)) { return; } final FullHttpRequest request = (FullHttpRequest) o; final HttpMethod method = request.method(); final String uri = request.uri(); final Route route = WebServer.this.routeTable.findRoute(method, uri); if (route == null) { writeNotFound(channelHandlerContext); return; } try { //here we write the response back and then execute the handle method in the server //this is done because, within the handle method, server may get killed, so with this //we respond first and execute the method. final String content = "ok"; writeResponse(channelHandlerContext, HttpResponseStatus.OK, TYPE_PLAIN, content); new Thread(() -> route.getHandler().handle()).start(); } catch (final Exception ex) { ex.printStackTrace(); writeInternalServerError(channelHandlerContext); } } } /** * Writes a 404 Not Found response. * * @param ctx The channel context. */ private static void writeNotFound(ChannelHandlerContext ctx) { writeErrorResponse(ctx, HttpResponseStatus.NOT_FOUND); } /** * Writes a 500 Internal Server Error response. * * @param ctx The channel context. */ private static void writeInternalServerError(ChannelHandlerContext ctx) { writeErrorResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR); } /** * Writes a HTTP error response. * * @param ctx The channel context. * @param status The error status. */ private static void writeErrorResponse(ChannelHandlerContext ctx, HttpResponseStatus status) { writeResponse(ctx, status, TYPE_PLAIN, status.reasonPhrase()); } /** * Writes a HTTP response. * * @param ctx The channel context. * @param status The HTTP status code. * @param contentType The response content type. * @param content The response content. */ private static void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status, CharSequence contentType, String content) { byte[] bytes = content.getBytes(StandardCharsets.UTF_8); ByteBuf entity = Unpooled.wrappedBuffer(bytes); writeResponse(ctx, status, entity, contentType, bytes.length); } /** * Writes a HTTP response. * * @param ctx The channel context. * @param status The HTTP status code. * @param buf The response content buffer. * @param contentType The response content type. * @param contentLength The response content length; */ private static void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status, ByteBuf buf, CharSequence contentType, int contentLength) { // Build the response object. final FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, buf, false); final ZonedDateTime dateTime = ZonedDateTime.now(); final DateTimeFormatter formatter = DateTimeFormatter.RFC_1123_DATE_TIME; final DefaultHttpHeaders headers = (DefaultHttpHeaders) response.headers(); headers.set(HttpHeaderNames.SERVER, SERVER_NAME); headers.set(HttpHeaderNames.DATE, dateTime.format(formatter)); headers.set(HttpHeaderNames.CONTENT_TYPE, contentType); headers.set(HttpHeaderNames.CONTENT_LENGTH, Integer.toString(contentLength)); // Close the non-keep-alive connection after the write operation is done. ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } }