/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.kafka.common.network;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.kafka.common.config.AbstractConfig;
import org.apache.kafka.common.metrics.Metrics;
import org.apache.kafka.common.protocol.SecurityProtocol;
import org.apache.kafka.common.security.authenticator.CredentialCache;
import org.apache.kafka.common.security.scram.ScramCredentialUtils;
import org.apache.kafka.common.security.scram.ScramMechanism;
import org.apache.kafka.common.utils.MockTime;
/**
* Non-blocking EchoServer implementation that uses ChannelBuilder to create channels
* with the configured security protocol.
*
*/
public class NioEchoServer extends Thread {
private final int port;
private final ServerSocketChannel serverSocketChannel;
private final List<SocketChannel> newChannels;
private final List<SocketChannel> socketChannels;
private final AcceptorThread acceptorThread;
private final Selector selector;
private volatile WritableByteChannel outputChannel;
private final CredentialCache credentialCache;
public NioEchoServer(ListenerName listenerName, SecurityProtocol securityProtocol, AbstractConfig config, String serverHost) throws Exception {
super("echoserver");
setDaemon(true);
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(serverHost, 0));
this.port = serverSocketChannel.socket().getLocalPort();
this.socketChannels = Collections.synchronizedList(new ArrayList<SocketChannel>());
this.newChannels = Collections.synchronizedList(new ArrayList<SocketChannel>());
this.credentialCache = new CredentialCache();
if (securityProtocol == SecurityProtocol.SASL_PLAINTEXT || securityProtocol == SecurityProtocol.SASL_SSL)
ScramCredentialUtils.createCache(credentialCache, ScramMechanism.mechanismNames());
ChannelBuilder channelBuilder = ChannelBuilders.serverChannelBuilder(listenerName, securityProtocol, config, credentialCache);
this.selector = new Selector(5000, new Metrics(), new MockTime(), "MetricGroup", channelBuilder);
acceptorThread = new AcceptorThread();
}
public int port() {
return port;
}
public CredentialCache credentialCache() {
return credentialCache;
}
@Override
public void run() {
try {
acceptorThread.start();
while (serverSocketChannel.isOpen()) {
selector.poll(1000);
for (SocketChannel socketChannel : newChannels) {
String id = id(socketChannel);
selector.register(id, socketChannel);
socketChannels.add(socketChannel);
}
newChannels.clear();
List<NetworkReceive> completedReceives = selector.completedReceives();
for (NetworkReceive rcv : completedReceives) {
KafkaChannel channel = channel(rcv.source());
channel.mute();
NetworkSend send = new NetworkSend(rcv.source(), rcv.payload());
if (outputChannel == null)
selector.send(send);
else {
for (ByteBuffer buffer : send.buffers)
outputChannel.write(buffer);
channel.unmute();
}
}
for (Send send : selector.completedSends())
selector.unmute(send.destination());
}
} catch (IOException e) {
// ignore
}
}
private String id(SocketChannel channel) {
return channel.socket().getLocalAddress().getHostAddress() + ":" + channel.socket().getLocalPort() + "-" +
channel.socket().getInetAddress().getHostAddress() + ":" + channel.socket().getPort();
}
private KafkaChannel channel(String id) {
KafkaChannel channel = selector.channel(id);
return channel == null ? selector.closingChannel(id) : channel;
}
/**
* Sets the output channel to which messages received on this server are echoed.
* This is useful in tests where the clients sending the messages don't receive
* the responses (eg. testing graceful close).
*/
public void outputChannel(WritableByteChannel channel) {
this.outputChannel = channel;
}
public Selector selector() {
return selector;
}
public void closeConnections() throws IOException {
for (SocketChannel channel : socketChannels)
channel.close();
socketChannels.clear();
}
public void close() throws IOException, InterruptedException {
this.serverSocketChannel.close();
closeConnections();
acceptorThread.interrupt();
acceptorThread.join();
interrupt();
join();
}
private class AcceptorThread extends Thread {
public AcceptorThread() throws IOException {
setName("acceptor");
}
public void run() {
try {
java.nio.channels.Selector acceptSelector = java.nio.channels.Selector.open();
serverSocketChannel.register(acceptSelector, SelectionKey.OP_ACCEPT);
while (serverSocketChannel.isOpen()) {
if (acceptSelector.select(1000) > 0) {
Iterator<SelectionKey> it = acceptSelector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isAcceptable()) {
SocketChannel socketChannel = ((ServerSocketChannel) key.channel()).accept();
socketChannel.configureBlocking(false);
newChannels.add(socketChannel);
selector.wakeup();
}
it.remove();
}
}
}
} catch (IOException e) {
// ignore
}
}
}
}