/**
* This file is part of git-as-svn. It is subject to the license terms
* in the LICENSE file found in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/gpl-2.0.html. No part of git-as-svn,
* including this file, may be copied, modified, propagated, or distributed
* except according to the terms contained in the LICENSE file.
*/
package ru.bozaro.protobuf;
import org.apache.http.ConnectionClosedException;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.config.MessageConstraints;
import org.apache.http.impl.DefaultHttpRequestFactory;
import org.apache.http.impl.io.*;
import org.apache.http.io.HttpMessageParser;
import org.apache.http.io.HttpMessageWriter;
import org.apache.http.io.SessionInputBuffer;
import org.apache.http.io.SessionOutputBuffer;
import org.apache.http.message.BasicLineParser;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
/**
* Socket wrapper for Protobuf RPC
*
* @author Artem V. Navrotskiy <bozaro@users.noreply.github.com>
*/
public class ProtobufRpcSocket extends ProtobufRpcSimpleHttp implements AutoCloseable {
private static final @NotNull Logger log = LoggerFactory.getLogger(ProtobufRpcSocket.class);
@NotNull
private final Thread thread;
@NotNull
private final ServerSocket socket;
@NotNull
private final Map<Socket, Boolean> connections = new ConcurrentHashMap<>();
@NotNull
private final ExecutorService pool;
public ProtobufRpcSocket(@NotNull ServiceHolder holder, @NotNull ServerSocket socket, @NotNull ExecutorService pool) {
super(holder);
this.socket = socket;
this.pool = pool;
this.thread = new Thread(ProtobufRpcSocket.this::acceptThread);
thread.setDaemon(true);
thread.start();
}
protected void acceptThread() {
while (!socket.isClosed()) {
try {
final Socket clientSocket = socket.accept();
connections.put(clientSocket, Boolean.FALSE);
pool.execute(() -> {
try (final Socket client = clientSocket) {
acceptClient(client);
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
connections.remove(clientSocket);
}
});
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
protected void acceptClient(@NotNull Socket client) throws IOException {
final SessionInputBuffer inputBuffer = wrapInputStream(client.getInputStream());
final HttpMessageParser<HttpRequest> parser = new DefaultHttpRequestParser(inputBuffer,
new BasicLineParser(),
new DefaultHttpRequestFactory(),
MessageConstraints.DEFAULT
);
final SessionOutputBuffer outputBuffer = wrapOutputStream(client.getOutputStream());
final HttpMessageWriter<HttpResponse> writer = new DefaultHttpResponseWriter(outputBuffer);
while (!socket.isClosed()) {
try {
service(inputBuffer, outputBuffer, parser, writer);
} catch (ConnectionClosedException ignored) {
break;
} catch (HttpException e) {
log.error(e.getMessage(), e);
break;
}
}
}
@NotNull
private SessionOutputBuffer wrapOutputStream(@NotNull OutputStream outputStream) {
return new SessionOutputBufferImpl(new HttpTransportMetricsImpl(), 1024) {{
bind(outputStream);
}};
}
@NotNull
protected SessionInputBuffer wrapInputStream(@NotNull InputStream inputStream) {
return new SessionInputBufferImpl(new HttpTransportMetricsImpl(), 1024) {{
bind(inputStream);
}};
}
@Override
public void close() throws Exception {
socket.close();
for (Socket clinet : connections.keySet()) {
clinet.close();
}
thread.join();
}
}