/*
* Copyright 2012 Jason Miller
*
* 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 jj.http.client;
import java.net.Inet6Address;
import java.net.InetAddress;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.resolver.dns.DnsAddressResolverGroup;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import jj.configuration.ConfigurationLoaded;
import jj.configuration.ConfigurationLoading;
import jj.event.Listener;
import jj.event.Subscriber;
/**
* @author jason
*
*/
@Singleton
@Subscriber
class HttpClient {
private static final String LOCALHOST = "localhost";
private static final InetAddress LOCALHOST_ADDRESS = Inet6Address.getLoopbackAddress();
private final HttpClientNioEventLoopGroup eventLoop;
private final HttpClientChannelInitializer initializer;
private final HttpClientConfigurationReader configuration;
private final Provider<Bootstrap> bootstrapProvider;
private volatile Bootstrap bootstrap;
// we can store this to verify that the configuration is the same
// and not restart
// no need to be volatile as it will only ever be used from the
// configuration script thread
private int configurationHashCode;
@Inject
HttpClient(
final HttpClientNioEventLoopGroup eventLoop,
final HttpClientChannelInitializer initializer,
final HttpClientConfigurationReader configuration,
final Provider<Bootstrap> bootstrapProvider
) {
this.eventLoop = eventLoop;
this.initializer = initializer;
this.configuration = configuration;
this.bootstrapProvider = bootstrapProvider;
}
@Listener
void on(ConfigurationLoading event) {
makeBootstrap();
}
@Listener
void on(ConfigurationLoaded event) {
makeBootstrap();
}
private void makeBootstrap() {
if (configuration.hashCode() != configurationHashCode) {
configurationHashCode = configuration.hashCode();
bootstrap = bootstrapProvider.get()
.group(eventLoop)
.handler(initializer)
.channel(NioSocketChannel.class)
.localAddress(configuration.localClientAddress())
.resolver(new DnsAddressResolverGroup(
NioDatagramChannel.class,
configuration.localNameserverAddress(),
configuration.nameservers()
))
.option(ChannelOption.TCP_NODELAY, true)
.validate();
}
}
ChannelFuture connect(boolean secure, String host, int port) {
assert host != null && !host.isEmpty() : "supply a host!";
assert port > 0 && port < 65536 : "supply a valid port number!";
if (LOCALHOST.equals(host)) { // skip right to the loopback, avoid any
return bootstrap.connect(LOCALHOST_ADDRESS, port);
}
// future optimization - if we're looping back onto a port bound to our http server, just
// pass things directly to it via embedded channels instead of going through the OS
return bootstrap.connect(host, port);
}
}