io.vertx.ext.shell.impl.ShellServerImpl Maven / Gradle / Ivy
/*
* Copyright 2015 Red Hat, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*
*
* Copyright (c) 2015 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*
*/
package io.vertx.ext.shell.impl;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.ext.shell.Shell;
import io.vertx.ext.shell.ShellServer;
import io.vertx.ext.shell.ShellServerOptions;
import io.vertx.ext.shell.command.CommandBuilder;
import io.vertx.ext.shell.command.CommandResolver;
import io.vertx.ext.shell.session.impl.SessionImpl;
import io.vertx.ext.shell.system.Process;
import io.vertx.ext.shell.system.impl.InternalCommandManager;
import io.vertx.ext.shell.term.Term;
import io.vertx.ext.shell.term.TermServer;
import io.vertx.ext.shell.term.impl.SSHServer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Julien Viet
*/
public class ShellServerImpl implements ShellServer {
private final Vertx vertx;
private final CopyOnWriteArrayList resolvers;
private final InternalCommandManager commandManager;
private final List termServers;
private final long timeoutMillis;
private final long reaperInterval;
private final String welcomeMessage;
private boolean closed = true;
private long timerID = -1;
private final Map sessions;
private final Promise sessionsClosed = Promise.promise();
private Handler shellHandler;
public ShellServerImpl(Vertx vertx, ShellServerOptions options) {
this.vertx = vertx;
this.welcomeMessage = options.getWelcomeMessage();
this.termServers = new ArrayList<>();
this.timeoutMillis = options.getSessionTimeout();
this.sessions = new ConcurrentHashMap<>();
this.reaperInterval = options.getReaperInterval();
this.resolvers = new CopyOnWriteArrayList<>();
this.commandManager = new InternalCommandManager(resolvers);
// Register builtin commands so they are listed in help
resolvers.add(() -> Arrays.asList(
CommandBuilder.command("exit").processHandler(process -> {}).build(vertx),
CommandBuilder.command("logout").processHandler(process -> {}).build(vertx),
CommandBuilder.command("jobs").processHandler(process -> {}).build(vertx),
CommandBuilder.command("fg").processHandler(process -> {}).build(vertx),
CommandBuilder.command("bg").processHandler(process -> {}).build(vertx)
));
}
@Override
public synchronized ShellServer registerCommandResolver(CommandResolver resolver) {
resolvers.add(0, resolver);
return this;
}
@Override
public synchronized ShellServer registerTermServer(TermServer termServer) {
termServers.add(termServer);
return this;
}
private void handleTerm(Term term) {
synchronized (this) {
// That might happen with multiple ser
if (closed) {
term.close();
return;
}
}
ShellImpl session = createShell(term);
session.setWelcome(welcomeMessage);
session.closedPromise.future().onComplete(ar -> {
boolean completeSessionClosed;
synchronized (ShellServerImpl.this) {
sessions.remove(session.id);
completeSessionClosed = sessions.isEmpty() && closed;
}
if (completeSessionClosed) {
sessionsClosed.complete();
}
});
session.init();
if (shellHandler != null) {
shellHandler.handle(session);
}
sessions.put(session.id, session); // Put after init so the close handler on the connection is set
session.readline(); // Now readline
}
@Override
public ShellServer listen(Handler> listenHandler) {
List toStart;
synchronized (this) {
if (!closed) {
throw new IllegalStateException("Server listening");
}
toStart = termServers;
}
AtomicInteger count = new AtomicInteger(toStart.size());
if (count.get() == 0) {
synchronized (this) {
closed = false;
}
listenHandler.handle(Future.succeededFuture());
return this;
}
AtomicBoolean failed = new AtomicBoolean();
Handler> handler = ar -> {
if (ar.failed()) {
failed.set(true);
}
if (count.decrementAndGet() == 0) {
if (failed.get()) {
listenHandler.handle(Future.failedFuture(ar.cause()));
toStart.forEach(TermServer::close);
} else {
synchronized (this) {
closed = false;
}
setTimer();
listenHandler.handle(Future.succeededFuture());
}
}
};
toStart.forEach(termServer -> {
if (termServer instanceof SSHServer) {
((SSHServer)termServer).setExecHandler(exec -> {
Process process = commandManager.createProcess(exec.command());
process.setSession(new SessionImpl());
process.setTty(exec);
process.terminatedHandler(exec::end);
process.run(true);
});
}
termServer.termHandler(this::handleTerm);
termServer.listen(handler);
});
return this;
}
private void evictSessions(long timerID) {
long now = System.currentTimeMillis();
Set toClose = new HashSet<>();
for (ShellImpl session: sessions.values()) {
if (now - session.lastAccessedTime() > timeoutMillis) {
toClose.add(session);
}
}
for (ShellImpl session: toClose) {
session.close();
}
setTimer();
}
private synchronized void setTimer() {
if (!closed && reaperInterval > 0) {
timerID = vertx.setTimer(reaperInterval, this::evictSessions);
}
}
@Override
public synchronized Shell createShell() {
return createShell(null);
}
@Override
public synchronized ShellImpl createShell(Term term) {
if (closed) {
throw new IllegalStateException("Closed");
}
return new ShellImpl(term, commandManager);
}
@Override
public void close(Handler> completionHandler) {
List toStop;
List toClose;
synchronized (this) {
if (closed) {
toStop = Collections.emptyList();
toClose = Collections.emptyList();
} else {
closed = true;
if (timerID != -1) {
vertx.cancelTimer(timerID);
}
toStop = termServers;
toClose = new ArrayList<>(sessions.values());
if (toClose.isEmpty()) {
sessionsClosed.complete();
}
}
}
if (toStop.isEmpty() && toClose.isEmpty()) {
completionHandler.handle(Future.succeededFuture());
} else {
AtomicInteger count = new AtomicInteger(1 + toClose.size());
Handler> handler = ar -> {
if (count.decrementAndGet() == 0) {
completionHandler.handle(Future.succeededFuture());
}
};
toClose.forEach(ShellImpl::close);
toStop.forEach(termServer -> termServer.close(handler));
sessionsClosed.future().onComplete(handler);
}
}
@Override
public void shellHandler(Handler shellHandler) {
this.shellHandler = shellHandler;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy