Java tutorial
/* * Copyright (c) 2011-2015 Pivotal Software Inc, All Rights Reserved. * * Licensed 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 reactor.io.net.impl.netty.http; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.*; import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory; import io.netty.handler.codec.http.websocketx.WebSocketVersion; import io.netty.handler.logging.LoggingHandler; import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.Environment; import reactor.core.Dispatcher; import reactor.core.support.Assert; import reactor.fn.tuple.Tuple2; import reactor.io.buffer.Buffer; import reactor.io.codec.Codec; import reactor.io.net.ChannelStream; import reactor.io.net.ReactorChannelHandler; import reactor.io.net.Reconnect; import reactor.io.net.config.ClientSocketOptions; import reactor.io.net.config.SslOptions; import reactor.io.net.http.HttpChannel; import reactor.io.net.http.HttpClient; import reactor.io.net.http.model.Method; import reactor.io.net.impl.netty.NettyChannelStream; import reactor.io.net.impl.netty.tcp.NettyTcpClient; import reactor.rx.Promise; import reactor.rx.Promises; import reactor.rx.Stream; import reactor.rx.Streams; import java.net.InetSocketAddress; import java.net.URI; /** * A Netty-based {@code TcpClient}. * * @param <IN> The type that will be received by this client * @param <OUT> The type that will be sent by this client * @author Jon Brisbin * @author Stephane Maldini */ public class NettyHttpClient<IN, OUT> extends HttpClient<IN, OUT> { private final static Logger log = LoggerFactory.getLogger(NettyHttpClient.class); private final NettyTcpClient<IN, OUT> client; private final Promise<NettyHttpChannel<IN, OUT>> reply; private URI lastURI = null; /** * Creates a new NettyTcpClient that will use the given {@code env} for configuration and the given {@code * reactor} to * send events. The number of IO threads used by the client is configured by the environment's {@code * reactor.tcp.ioThreadCount} property. In its absence the number of IO threads will be equal to the {@link * reactor.Environment#PROCESSORS number of available processors}. </p> The client will connect to the given {@code * connectAddress}, configuring its socket using the given {@code opts}. The given {@code codec} will be used for * encoding and decoding of data. * * @param env The configuration environment * @param dispatcher The dispatcher used to send events * @param connectAddress The root host and port to connect relatively from in http handlers * @param options The configuration options for the client's socket * @param sslOptions The SSL configuration options for the client's socket * @param codec The codec used to encode and decode data */ public NettyHttpClient(final Environment env, final Dispatcher dispatcher, final InetSocketAddress connectAddress, final ClientSocketOptions options, final SslOptions sslOptions, final Codec<Buffer, IN, OUT> codec) { super(env, dispatcher, codec, options); this.client = new NettyTcpClient<IN, OUT>(env, dispatcher, connectAddress, options, sslOptions, codec) { @Override protected void bindChannel(ReactorChannelHandler<IN, OUT, ChannelStream<IN, OUT>> handler, SocketChannel nativeChannel) { URI currentURI = lastURI; try { if (currentURI.getScheme() != null && (currentURI.getScheme().toLowerCase().equals(HttpChannel.HTTPS_SCHEME) || currentURI.getScheme().toLowerCase().equals(HttpChannel.WSS_SCHEME))) { addSecureHandler(nativeChannel); } } catch (Exception e) { nativeChannel.pipeline().fireExceptionCaught(e); } NettyHttpClient.this.bindChannel(handler, nativeChannel); } @Override public InetSocketAddress getConnectAddress() { if (connectAddress != null) return connectAddress; try { URI url = lastURI; String host = url != null && url.getHost() != null ? url.getHost() : "localhost"; int port = url != null ? url.getPort() : -1; if (port == -1) { if (url != null && url.getScheme() != null && (url.getScheme().toLowerCase().equals(HttpChannel.HTTPS_SCHEME) || url.getScheme().toLowerCase().equals(HttpChannel.WSS_SCHEME))) { port = 443; } else { port = 80; } } return new InetSocketAddress(host, port); } catch (Exception e) { throw new IllegalArgumentException(e); } } }; this.reply = Promises.prepare(); } @Override protected Promise<Void> doStart(final ReactorChannelHandler<IN, OUT, HttpChannel<IN, OUT>> handler) { return client.start(new ReactorChannelHandler<IN, OUT, ChannelStream<IN, OUT>>() { @Override public Publisher<Void> apply(ChannelStream<IN, OUT> inoutChannelStream) { final NettyHttpChannel<IN, OUT> ch = ((NettyHttpChannel<IN, OUT>) inoutChannelStream); return handler.apply(ch); } }); } @Override protected Stream<Tuple2<InetSocketAddress, Integer>> doStart( final ReactorChannelHandler<IN, OUT, HttpChannel<IN, OUT>> handler, final Reconnect reconnect) { return client.start(new ReactorChannelHandler<IN, OUT, ChannelStream<IN, OUT>>() { @Override public Publisher<Void> apply(ChannelStream<IN, OUT> inoutChannelStream) { final NettyHttpChannel<IN, OUT> ch = ((NettyHttpChannel<IN, OUT>) inoutChannelStream); return handler.apply(ch); } }, reconnect); } @Override public Promise<? extends HttpChannel<IN, OUT>> request(final Method method, final String url, final ReactorChannelHandler<IN, OUT, HttpChannel<IN, OUT>> handler) { final URI currentURI; try { Assert.isTrue(method != null && url != null); currentURI = parseURL(method, url); lastURI = currentURI; } catch (Exception e) { return Promises.error(e); } start(new ReactorChannelHandler<IN, OUT, HttpChannel<IN, OUT>>() { @Override public Publisher<Void> apply(HttpChannel<IN, OUT> inoutHttpChannel) { try { final NettyHttpChannel<IN, OUT> ch = ((NettyHttpChannel<IN, OUT>) inoutHttpChannel); ch.getNettyRequest() .setUri(currentURI.getPath() + (currentURI.getQuery() == null ? "" : "?" + currentURI.getQuery())) .setMethod(new HttpMethod(method.getName())).headers() .add(HttpHeaders.Names.HOST, currentURI.getHost()).add(HttpHeaders.Names.ACCEPT, "*/*"); if (handler != null) { Publisher<Void> p = handler.apply(ch); reply.onNext(ch); return p; } else { reply.onNext(ch); return Streams.empty(); } } catch (Throwable t) { reply.onError(t); return Promises.error(t); } } }); return reply; } private URI parseURL(Method method, String url) throws Exception { if (!url.startsWith(HttpChannel.HTTP_SCHEME) && !url.startsWith(HttpChannel.WS_SCHEME)) { final String parsedUrl = (method.equals(Method.WS) ? HttpChannel.WS_SCHEME : HttpChannel.HTTP_SCHEME) + "://"; if (url.startsWith("/")) { return new URI(parsedUrl + (lastURI != null && lastURI.getHost() != null ? lastURI.getHost() : "localhost") + url); } else { return new URI(parsedUrl + url); } } else { return new URI(url); } } @Override protected final Promise<Void> doShutdown() { return client.shutdown(); } protected void bindChannel(ReactorChannelHandler<IN, OUT, ChannelStream<IN, OUT>> handler, Object nativeChannel) { SocketChannel ch = (SocketChannel) nativeChannel; NettyChannelStream<IN, OUT> netChannel = new NettyChannelStream<IN, OUT>(getDefaultEnvironment(), getDefaultCodec(), getDefaultPrefetchSize(), getDefaultDispatcher(), ch); ChannelPipeline pipeline = ch.pipeline(); if (log.isDebugEnabled()) { pipeline.addLast(new LoggingHandler(NettyHttpClient.class)); } pipeline.addLast(new HttpClientCodec()); URI currentURI = lastURI; if (currentURI.getScheme() != null && currentURI.getScheme().toLowerCase().startsWith(HttpChannel.WS_SCHEME)) { pipeline.addLast(new HttpObjectAggregator(8192)).addLast( new NettyHttpWSClientHandler<IN, OUT>(handler, netChannel, WebSocketClientHandshakerFactory .newHandshaker(lastURI, WebSocketVersion.V13, null, false, new DefaultHttpHeaders()))); } else { pipeline.addLast(new NettyHttpClientHandler<IN, OUT>(handler, netChannel)); } } }