convex.restapi.RESTServer Maven / Gradle / Ivy
The newest version!
package convex.restapi;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.HashMap;
import org.eclipse.jetty.server.ServerConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import convex.api.Convex;
import convex.api.ConvexLocal;
import convex.core.crypto.AKeyPair;
import convex.core.data.Keyword;
import convex.core.data.Keywords;
import convex.core.util.Utils;
import convex.peer.API;
import convex.peer.ConfigException;
import convex.peer.LaunchException;
import convex.peer.Server;
import convex.restapi.api.ChainAPI;
import convex.restapi.api.DLAPI;
import convex.restapi.api.DepAPI;
import convex.restapi.api.PeerAdminAPI;
import io.javalin.Javalin;
import io.javalin.community.ssl.SslPlugin;
import io.javalin.config.JavalinConfig;
import io.javalin.http.staticfiles.Location;
import io.javalin.openapi.JsonSchemaLoader;
import io.javalin.openapi.JsonSchemaResource;
import io.javalin.openapi.plugin.OpenApiPlugin;
import io.javalin.openapi.plugin.redoc.ReDocPlugin;
import io.javalin.openapi.plugin.swagger.SwaggerPlugin;
import io.javalin.util.JavalinBindException;
public class RESTServer implements Closeable {
protected static final Logger log = LoggerFactory.getLogger(RESTServer.class.getName());
protected final Server server;
protected final Convex convex;
protected Javalin javalin;
private RESTServer(Server server) {
this.server = server;
this.convex = ConvexLocal.create(server, server.getPeerController(), server.getKeyPair());
}
private Javalin buildApp() {
SslPlugin sslPlugin = getSSLPlugin(server.getConfig());
Javalin app = Javalin.create(config -> {
config.staticFiles.enableWebjars();
config.bundledPlugins.enableCors(cors -> {
cors.addRule(corsConfig -> {
// replacement for enableCorsForAllOrigins()
corsConfig.anyHost();
});
});
if (sslPlugin!=null) {
config.registerPlugin(sslPlugin);
}
addOpenApiPlugins(config);
config.staticFiles.add(staticFiles -> {
staticFiles.hostedPath = "/";
staticFiles.location = Location.CLASSPATH; // Specify resources from classpath
staticFiles.directory = "/convex/restapi/pub"; // Resource location in classpath
staticFiles.precompress = false; // if the files should be pre-compressed and cached in memory
// (optimization)
staticFiles.aliasCheck = null; // you can configure this to enable symlinks (=
// ContextHandler.ApproveAliases())
staticFiles.skipFileFunction = req -> false; // you can use this to skip certain files in the dir, based
// on the HttpServletRequest
});
});
app.exception(Exception.class, (e, ctx) -> {
e.printStackTrace();
String message = "Unexpected error: " + e;
ctx.result(message);
ctx.status(500);
});
addAPIRoutes(app);
return app;
}
// Get an SSL plugin, or null if SSL cannot be configured
protected SslPlugin getSSLPlugin(HashMap config) {
boolean useSSL=true;
SslPlugin sslPlugin=null;
if (useSSL) try {
Path certFile=Utils.getHomePath().resolve(".convex/ssl/certificate.pem");
Path privateFile=Utils.getHomePath().resolve(".convex/ssl/private.pem");
if (Files.exists(certFile)&&Files.exists(privateFile)) {
InputStream certS=Files.newInputStream(certFile);
InputStream privateS=Files.newInputStream(privateFile);
sslPlugin = new SslPlugin(conf -> {
conf.pemFromInputStream(certS, privateS);
conf.http2=true;
});
} else {
log.warn("Failed to find SSL cerificates, defaulting back to HTTP");
}
} catch (InvalidPathException | IOException e) {
log.warn("Failed to create SSL plugin, will use insecure HTTP only", e);
}
return sslPlugin;
}
protected void addOpenApiPlugins(JavalinConfig config) {
String docsPath="openapi-plugin/openapi-default.json";
config.registerPlugin(new OpenApiPlugin(pluginConfig -> {
pluginConfig
.withDocumentationPath(docsPath)
.withDefinitionConfiguration((version, definition) -> {
definition.withInfo(info -> {
info.setTitle("Convex REST API");
info.setVersion("0.7.0");
});
});
}));
config.registerPlugin(new SwaggerPlugin(swaggerConfiguration->{
swaggerConfiguration.setDocumentationPath(docsPath);
}));
config.registerPlugin(new ReDocPlugin(reDocConfiguration -> {
reDocConfiguration.setDocumentationPath(docsPath);
}));
for (JsonSchemaResource generatedJsonSchema : new JsonSchemaLoader().loadGeneratedSchemes()) {
System.out.println(generatedJsonSchema.getName());
}
}
protected ChainAPI chainAPI;
protected DepAPI depAPI;
protected DLAPI dlAPI;
protected WebApp webApp;
protected PeerAdminAPI peerAPI;
private void addAPIRoutes(Javalin app) {
chainAPI = new ChainAPI(this);
chainAPI.addRoutes(app);
depAPI = new DepAPI(this);
depAPI.addRoutes(app);
peerAPI = new PeerAdminAPI(this);
peerAPI.addRoutes(app);
webApp = new WebApp(this);
webApp.addRoutes(app);
dlAPI = new DLAPI(this);
dlAPI.addRoutes(app);
}
/**
* Create a RESTServer connected to a local Convex Peer Server instance.
* Defaults to using the Peer Controller account.
*
* @param server Server instance
* @return New {@link RESTServer} instance
*/
public static RESTServer create(Server server) {
RESTServer newServer = new RESTServer(server);
return newServer;
}
/**
* Create a RESTServer connected to a Convex Client instance. Defaults to using
* the Peer Controller account.
*
* @param convex Convex client connection instance
* @return New {@link RESTServer} instance
*/
public static RESTServer create(Convex convex) {
return create(convex.getLocalServer());
}
protected void setupJettyServer(org.eclipse.jetty.server.Server jettyServer, Integer port) {
if (port==null) port=8080;
ServerConnector connector = new ServerConnector(jettyServer);
connector.setPort(port);
jettyServer.addConnector(connector);
}
/**
* Start app with default port
*/
public void start() {
start(null);
}
/**
* Start app with specific port
*/
public synchronized void start(Integer port) {
close();
try {
javalin=buildApp();
start(javalin,port);
} catch (JavalinBindException e) {
if (port!=null) throw e; // only try again if port unspecified
log.warn("Specified port "+port+"already in use, chosing another at random");
close();
port=0; // use random port
javalin=buildApp();
start(javalin,port);
}
}
protected void start(Javalin app, Integer port) {
org.eclipse.jetty.server.Server jettyServer=app.jettyServer().server();
setupJettyServer(jettyServer,port);
app.start();
}
public synchronized void close() {
if (javalin!=null) javalin.stop();
javalin=null;
// we don't own the Convex server, so do nothing to it
}
public Convex getConvex() {
return convex;
}
/**
* Gets the local Convex Server instance, or null if not using a local connection.
*
* @return Server instance, or null if not available.
*/
public Server getServer() {
return server;
}
public int getPort() {
if (javalin==null) throw new Error("Javalin ot started");
return javalin.port();
}
public HashMap getConfig() {
return server.getConfig();
}
public static void main(String[] args) throws InterruptedException, ConfigException, LaunchException {
HashMap config=new HashMap<>();
config.put(Keywords.KEYPAIR, AKeyPair.generate());
config.put(Keyword.create("faucet"), true);
Server s=API.launchPeer(config);
System.out.println("Using Ed25519 seed: "+s.getKeyPair().getSeed());
System.out.println("Using peer port: "+s.getPort());
try (RESTServer rs=RESTServer.create(s)) {
rs.start();
System.out.println("Started on REST port: "+rs.getPort());
while (s.isRunning()) {
Thread.sleep(1000);
}
}
System.out.println("Server shutting down.... bye!");
}
}