Java tutorial
/* * Copyright (c) 2016 Catavolt Inc. All rights reserved. * * This file is part of Ozy. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ozy.client; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.*; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.util.CharsetUtil; import ozy.fp.Future; import ozy.util.ObjectError; import ozy.fp.Promise; import java.util.List; import java.util.Map; /** */ class Client { private static final EventLoopGroup _globalEventLoopGroup = new NioEventLoopGroup(); private final Object LOCK = new Object(); private boolean active = false; private final String _body; private final String _host; private final HttpMethod _method; private final String _path; private final Map<String, List<String>> _params; private final int _port; private final Promise<String> _promise = new Promise<>(); private final String _scheme; //-------------------------------------------------------------------------- // INNER CLASSES //-------------------------------------------------------------------------- static class ClientHandler extends SimpleChannelInboundHandler<HttpObject> { private Promise<String> _promise; private StringBuilder _responseBuf = new StringBuilder(); @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); _promise.failure(new ObjectError("Unhandled exception", new RuntimeException(cause))); } public void setPromise(Promise<String> promise) { _promise = promise; } @Override protected final void channelRead0(ChannelHandlerContext ctx, HttpObject msg) { System.out.println("[Client] channel read 0"); if (msg instanceof HttpContent) { HttpContent content = (HttpContent) msg; System.out.println("[Client] appending content"); _responseBuf.append(content.content().toString(CharsetUtil.UTF_8)); if (content instanceof LastHttpContent) { System.out.println("[Client] last content received"); System.out.println("[Client] fulfilling promise"); _promise.success(_responseBuf.toString()); } } } } static class ClientInitializer extends ChannelInitializer<SocketChannel> { private final SslContext sslCtx; public ClientInitializer(SslContext sslCtx) { this.sslCtx = sslCtx; } @Override public void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline(); if (sslCtx != null) { p.addLast(sslCtx.newHandler(ch.alloc())); } p.addLast("clientCodec", new HttpClientCodec()); p.addLast("objectAggregator", new HttpObjectAggregator(1048576)); p.addLast("clientHandler", new ClientHandler()); } } //-------------------------------------------------------------------------- // CLASS METHODS //-------------------------------------------------------------------------- public static void shutdown() { _globalEventLoopGroup.shutdownGracefully(); } //-------------------------------------------------------------------------- // CONSTRUCTORS //-------------------------------------------------------------------------- public Client(HttpMethod method, String scheme, String host, int port, String path, Map<String, List<String>> params, String body) { if (method == null) { throw new NullPointerException("method"); } if (scheme == null) { scheme = "http"; } if (!scheme.equals("http") && !scheme.equals("https")) { throw new IllegalArgumentException("Scheme must be http or https: " + scheme); } if (host == null) { host = "127.0.0.1"; } if (port == -1) { if (scheme.equalsIgnoreCase("http")) { port = 80; } else if (scheme.equalsIgnoreCase("https")) { port = 443; } } if (path == null || path.trim().equals("")) { path = "/"; } if (body == null) { body = ""; } _method = method; _scheme = scheme; _host = host; _port = port; _path = path; _params = params; _body = body; } //-------------------------------------------------------------------------- // INSTANCE METHODS //-------------------------------------------------------------------------- public final String body() { return _body; } public final String host() { return _host; } public final HttpMethod method() { return _method; } public final String path() { return _path; } public final Map<String, List<String>> params() { return _params; } public final Future<String> perform() throws Exception { System.out.println("[Client] performing"); synchronized (LOCK) { if (active) { throw new IllegalStateException("Already active"); } active = true; } try { perform(_globalEventLoopGroup); } catch (Exception exc) { _promise.failure(new ObjectError("Unhandled exception", exc)); } return _promise.future(); } public final int port() { return _port; } public final String scheme() { return _scheme; } // ~~~~~~~~~~~~~~~~~~~~~~~~~ PROTECTED ~~~~~~~~~~~~~~~~~~~~~~~~~ // protected void setCookies(HttpHeaders headers) { //headers.set( // HttpHeaders.Names.COOKIE, // ClientCookieEncoder.LAX.encode( // new DefaultCookie("my-cookie", "foo"), // new DefaultCookie("another-cookie", "bar"))); } protected void setHeaders(HttpHeaders headers) { } // ~~~~~~~~~~~~~~~~~~~~~~~~~ PRIVATE ~~~~~~~~~~~~~~~~~~~~~~~~~ // private void perform(EventLoopGroup group) throws Exception { final boolean ssl = scheme().equalsIgnoreCase("https"); final SslContext sslCtx; if (ssl) { sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build(); } else { sslCtx = null; } System.out.println("[Client] creating bootstrap"); Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class).handler(new ClientInitializer(sslCtx)); System.out.println("[Client] connecting to host"); Channel ch = b.connect(host(), port()).sync().channel(); ClientHandler clientHandler = (ClientHandler) ch.pipeline().get("clientHandler"); clientHandler.setPromise(_promise); ByteBuf requestBytes; if (!body().isEmpty()) { requestBytes = Unpooled.copiedBuffer(body(), CharsetUtil.UTF_8); } else { requestBytes = Unpooled.buffer(0); } String pathWithParams = path(); if (params() != null) { pathWithParams += "?"; for (Map.Entry<String, List<String>> e : params().entrySet()) { for (String v : e.getValue()) { if (!pathWithParams.endsWith("?")) { pathWithParams += "&"; } pathWithParams += e.getKey() + "=" + v; } } } DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, method(), pathWithParams, requestBytes); HttpHeaders.setHost(request, host()); HttpHeaders.setKeepAlive(request, false); HttpHeaders.setContentLength(request, request.content().readableBytes()); setCookies(request.headers()); System.out.println("[Client] writeAndFlush"); ch.writeAndFlush(request); } }