Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): remote readonly accesses #78

Merged
merged 11 commits into from
Mar 17, 2023
6 changes: 5 additions & 1 deletion src/main/java/io/cryostat/agent/MainModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
import javax.net.ssl.X509TrustManager;

import io.cryostat.agent.Harvester.RecordingSettings;
import io.cryostat.agent.remote.RemoteContext;
import io.cryostat.agent.remote.RemoteModule;
import io.cryostat.core.net.JFRConnection;
import io.cryostat.core.net.JFRConnectionToolkit;
import io.cryostat.core.sys.Environment;
Expand All @@ -76,6 +78,7 @@
@Module(
includes = {
ConfigModule.class,
RemoteModule.class,
})
public abstract class MainModule {

Expand Down Expand Up @@ -105,12 +108,13 @@ public static ScheduledExecutorService provideExecutor(AtomicInteger threadId) {
@Provides
@Singleton
public static WebServer provideWebServer(
Lazy<Set<RemoteContext>> remoteContexts,
Lazy<CryostatClient> cryostat,
ScheduledExecutorService executor,
@Named(ConfigModule.CRYOSTAT_AGENT_WEBSERVER_HOST) String host,
@Named(ConfigModule.CRYOSTAT_AGENT_WEBSERVER_PORT) int port,
Lazy<Registration> registration) {
return new WebServer(cryostat, executor, host, port, registration);
return new WebServer(remoteContexts, cryostat, executor, host, port, registration);
}

@Provides
Expand Down
129 changes: 74 additions & 55 deletions src/main/java/io/cryostat/agent/WebServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,17 @@
import java.security.SecureRandom;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;

import io.cryostat.agent.remote.RemoteContext;

import com.sun.net.httpserver.BasicAuthenticator;
import com.sun.net.httpserver.Filter;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import dagger.Lazy;
import org.apache.http.HttpStatus;
Expand All @@ -63,6 +66,7 @@ class WebServer {

private final Logger log = LoggerFactory.getLogger(getClass());

private final Lazy<Set<RemoteContext>> remoteContexts;
private final Lazy<CryostatClient> cryostat;
private final ScheduledExecutorService executor;
private final String host;
Expand All @@ -71,18 +75,26 @@ class WebServer {
private final Lazy<Registration> registration;
private HttpServer http;

private final AgentAuthenticator agentAuthenticator;
private final RequestLoggingFilter requestLoggingFilter;

WebServer(
Lazy<Set<RemoteContext>> remoteContexts,
Lazy<CryostatClient> cryostat,
ScheduledExecutorService executor,
String host,
int port,
Lazy<Registration> registration) {
this.remoteContexts = remoteContexts;
this.cryostat = cryostat;
this.executor = executor;
this.host = host;
this.port = port;
this.credentials = new Credentials();
this.registration = registration;

this.agentAuthenticator = new AgentAuthenticator();
this.requestLoggingFilter = new RequestLoggingFilter();
}

void start() throws IOException, NoSuchAlgorithmException {
Expand All @@ -93,62 +105,16 @@ void start() throws IOException, NoSuchAlgorithmException {
this.generateCredentials();

this.http = HttpServer.create(new InetSocketAddress(host, port), 0);

this.http.setExecutor(executor);

HttpContext pingCtx =
this.http.createContext(
"/",
new HttpHandler() {
@Override
public void handle(HttpExchange exchange) throws IOException {
String mtd = exchange.getRequestMethod();
switch (mtd) {
case "POST":
synchronized (WebServer.this.credentials) {
executor.execute(registration.get()::tryRegister);
exchange.sendResponseHeaders(
HttpStatus.SC_NO_CONTENT, -1);
exchange.close();
}
break;
case "GET":
exchange.sendResponseHeaders(HttpStatus.SC_NO_CONTENT, -1);
exchange.close();
break;
default:
exchange.sendResponseHeaders(HttpStatus.SC_NOT_FOUND, -1);
exchange.close();
break;
}
}
});
pingCtx.setAuthenticator(new AgentAuthenticator());
pingCtx.getFilters()
.add(
new Filter() {
@Override
public void doFilter(HttpExchange exchange, Chain chain)
throws IOException {
long start = System.nanoTime();
String requestMethod = exchange.getRequestMethod();
String path = exchange.getRequestURI().getPath();
log.info("{} {}", requestMethod, path);
chain.doFilter(exchange);
long elapsed = System.nanoTime() - start;
log.info(
"{} {} : {} {}ms",
requestMethod,
path,
exchange.getResponseCode(),
Duration.ofNanos(elapsed).toMillis());
}

@Override
public String description() {
return "requestLog";
}
});
Set<RemoteContext> mergedContexts = new HashSet<>(remoteContexts.get());
mergedContexts.add(new PingContext());
mergedContexts.forEach(
rc -> {
HttpContext ctx = this.http.createContext(rc.path(), rc::handle);
ctx.setAuthenticator(agentAuthenticator);
ctx.getFilters().add(requestLoggingFilter);
});

this.http.start();
}
Expand All @@ -175,6 +141,59 @@ void generateCredentials() throws NoSuchAlgorithmException {
}
}

private class RequestLoggingFilter extends Filter {
@Override
public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
long start = System.nanoTime();
String requestMethod = exchange.getRequestMethod();
String path = exchange.getRequestURI().getPath();
log.info("{} {}", requestMethod, path);
chain.doFilter(exchange);
long elapsed = System.nanoTime() - start;
log.info(
"{} {} : {} {}ms",
requestMethod,
path,
exchange.getResponseCode(),
Duration.ofNanos(elapsed).toMillis());
}

@Override
public String description() {
return "requestLog";
}
}

private class PingContext implements RemoteContext {

@Override
public String path() {
return "/";
}

@Override
public void handle(HttpExchange exchange) throws IOException {
String mtd = exchange.getRequestMethod();
switch (mtd) {
case "POST":
synchronized (WebServer.this.credentials) {
executor.execute(registration.get()::tryRegister);
exchange.sendResponseHeaders(HttpStatus.SC_NO_CONTENT, -1);
exchange.close();
}
break;
case "GET":
exchange.sendResponseHeaders(HttpStatus.SC_NO_CONTENT, -1);
exchange.close();
break;
default:
exchange.sendResponseHeaders(HttpStatus.SC_NOT_FOUND, -1);
exchange.close();
break;
}
}
}

private class AgentAuthenticator extends BasicAuthenticator {

private final Logger log = LoggerFactory.getLogger(getClass());
Expand Down
Loading