Skip to content

Instantly share code, notes, and snippets.

@a-rekkusu
Last active September 1, 2020 09:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save a-rekkusu/b98ecd201d25102ca3e118a2fa38fbb4 to your computer and use it in GitHub Desktop.
Save a-rekkusu/b98ecd201d25102ca3e118a2fa38fbb4 to your computer and use it in GitHub Desktop.

Google Summer of Code 2020 Report

Building an injectable IoC centric, small and lightweight HTTP server and making it runnable as a native executable

1. Initial Project description

CDI (Contexts and Dependency Injection) is an IoC (Inversion of Control) container, that is implemented in Apache OWB (OpenWebBeans). Even though there is an existing micro HTTP server that supports CDI capabilities in the Apache world (Meecrowave), it is not IoC centric and it has a lot more features than the basics. Furthermore, it is not runnable as a native executable – there’s always the big JVM necessary. The proposal was to build a much slimmer HTTP API and implement it in a project with CDI (OpenWebBeans) as the starting point and webserver components (Netty) on top. This can then be injected as a managed bean in the web application. It would be an even more minimalistic microserver and IoC centric. Making this then natively runnable as a native executable for Windows is delivering important work for future JakartaEE projects within Apache, that need to be turned into a native image (via GraalVM). Before this GSOC project, there was little to none such experience in OWB.

For the project three separate repositories were created:

2. Warm-up with related technologies:

In the initial work period I got to know several technologies around this topic and used them in small sample projects: Maven, Netty, CDI/OWB, Servlets/Tomcat and CDI extensions. The repository with the separate sample projects can be found here.

3. Http Server: Peeco

As the requested HTTP server should be even slimmer and more basic than i. e. Meecrowave, we settled on the simple name Peeco (as pico < micro) for now, but that might change later.

The checked boxes were the successfully implemented features. The unchecked boxes could not be finished within the project time.

  • basic HTTP API definition for writing and handling requests/responses
  • marry this API, CDI/OWB and Netty
  • implement basic HTTP functionalities: GET and POST requests work
  • exact and wildcard url matching
  • reactive request/response behavior with CompletionStage
  • make the server configurable with a builder pattern
  • deposit the code in a dedicated ASF repository
  • enable file upload
  • enable chunking
  • server configuration with properties file

Here's an example for how to configure and build this server:

public HttpServer init()
{
	return new HttpServer.Builder()
			.port(0) //applies a random valid port
			.ssl(false)
			.host("localhost")
			.build();
}

And here's how you can use the API with the HttpHandler annotation:

@Inject private HttpServer httpServer;

@HttpHandler(method = {HttpMethod.GET}, url = "/hello/*", matching = Matching.WILDCARD)
public CompletionStage<Response> helloWorld(Request request)
{
	String responseContent = "Hello World from " + httpServer.getHost() + ":" + httpServer.getPort() + " !";
	ByteArrayInputStream output = new ByteArrayInputStream(responseContent.getBytes(StandardCharsets.UTF_8));

	return CompletableFuture.supplyAsync(() ->
	{
		Response response = new Response();
		response.addHeader("statusCode", "200");
		response.addHeader("content-type", "text/html");
		response.setOutput(output);
		return response;
	});
}

This is a reactive example with CompletionStage; you can also straight up use Response as the method return type. The formal requirements of a valid handler method and annotation are:

  • return type must be CompletionStage<Response> or Response
  • first method parameter must be Request
  • Matching.WILDCARD needs a star at the end of the given url

For more examples please refer to the respective showcase. The implementation is found here.

PeecoExtension.java collects all methods that are annotated with our @HttpHandler and starts up the Netty Server. The flow of the request/response handling is then happening in PeecoChannelHandler.java.

4. GraalVM native-image

After the server was usable, next up was making it runnable as a native-image.

The work progress on the tasks is as followed after the GSOC deadline:

  • setup native-image building and build simple HelloWorld app
  • build simple native-image with CDI and proxy usage
  • build runnable native-image from server-showcase module

Unfortunately, the showcase was not running natively at the end of the project. As GraalVM native-image offers lots of configuration and mistake opportunities, the project time just wasn't quite enough to finish it. The main native-image problems in the end were Logging Frameworks, Netty and build-time vs. runtime instantiation, as well as reflectable availability in the native-image.

The first obstacle was setting up the correct developing environment on my machine with Windows 10, so the GraalVM native-image command would compile correctly. I only managed to make it work by downloading and installing the Windows 7.1 SDK (specifically the GRMSDKX_EN_DVD.iso). After the installation, I had the Windows 7.1 SDK Command Prompt available, through which the native-image command from GraalVM worked successfully and gave me a simple HelloWorld.exe out of a .jar file. Also, a basic installation of Visual Studio 2017 (C++ build tools workload) or later with its x64 Native Tools Command Prompt might be necessary. For instructions on a Linux machine, refer to the information provided on the GraalVM website.

Next up was enabling OWB to generate its proxy classes at build-time, as the regular runtime-generation cannot be done at native-image runtime. Hence, the proxy classes must already be created and available in the classpath at runtime. This can be achieved by using OWB's ClassLoaderProxyService.Spy to generate at build-time (like so), and ClassLoaderProxyService.LoadFirst at runtime like so.

In order to build a native-image, some special resources are needed in the .jar, namely for configuring the use cases of reflection, dynamic proxies, jni and other resources. These files can be generated by using a command on the .jar file:

java -agentlib:native-image-agent=config-output-dir=OUTPUT_PATH (-classpath optional.jar) -jar myJar.jar

Merging updated config files is also possible, by stating config-merge-dirinstead of config-output-dir:

                                         VVVVV
java -agentlib:native-image-agent=config-merge-dir=OUTPUT_PATH (-classpath optional.jar) -jar myJar.jar

The resulting config files might have to be manually corrected, as some execution paths may be missing. It is recommended to place the config files under resources/META-INF/native-image, as the native-image command searches here by default. Then package this again and build it from the Windows 7.1 SDK Command Prompt with:

native-image --no-fallback -jar myJar.jar

The --no-fallback argument prevents the image to create a fallback image that's basically using the JVM again in case the config files are not correct. This way we get a true native-image or none at all, if it fails.

5. Making a sample project natively runnable

Before the showcase, we started with a basic graal-vm native-image example project, that used OWB. To test the proxying, this was the workflow:

  1. generate proxy class and deposit it in a given path (we toggled that manually with a separate static void main(String[] args) )
  2. add the generated proxy class manually into the .jar with jar uf myJar.jar package/proxyClass.class
  3. run the .jar in the console with the -agentlib command described before, for the auto-generation of GraalVM config files, with the mainClass accessing the proxy class
  4. re-package the module with the new config files, re-add the proxy classes, re-test its runnability in console
  5. build native-image
  6. run the native image .exe in console

Some problems and difficulties arose in that workflow - the app wouldn't run in the console (we had to remove the MANIFEST.MF file from the .jar and run the mainClass itself to generate our native-image config files) and some classes were missing in the auto-generated config files, for example. Also, a resolution error came from the CDI container, as multiple beans were found for one class (the class itself and its proxy class). Therefore, all proxy classes should have the @Vetoed annotation, so they are not being scanned at runtime, as they are already available. This Pull Request applied this change in the openwebbeans-impl.

With the @Vetoed proxy class, finally, the example project worked as it should in native.

6. Making the server showcase natively runnable

The server showcase was a bit more complex, as it had more dependencies, mainly because of Netty. This meant that the quite timely workflow from before had to be run more often, and it took even longer as the bigger components affected the native-image buildtime, of course. Applying possible fixes to exceptions and errors at various stages in the workflow often means running through that whole workflow. It seemed most promising to run the native image command with the following arguments like so:

native-image --no-fallback --initialize-at-run-time=<classes or packages> --initialize-at-build-time=<classes or packages> --report-unsupported-elements-at-runtime -H:+TraceClassInitialization --allow-incomplete-classpath --class-path (all dependencies) -jar peeco-showcase.jar

Also, researching took away a lot of the time here, as the native-image command has a massive documentation from GraalVM and lots of possibly helpful blog entries on the web. Unfortunately, the time was just not enough to master it all in the end. Maybe some code substitution regarding Netty will solve the current problem, as proposed in this blog entry.

7. Conclusion

Even though I was not able to finish every last thing in time, not much was missing in the end. I learned lots of new technologies and how they work. The project threw me into state-of-the-art java development with web driven technologies such as servlets, CDI, microservers and native images, as well as teached me a more basic understanding of the java world in exposing me to bytecode enhancement, proxy class generations, JakartaEE and maven project structures. I'm positive that this broad sense of understanding will be quite valuable in my professional future.

My mentors were very helpful throughout the whole program timeline. They were very responsive and didn't give away too much of the solutions, most of the time, and instead just gave hints and directions in which to research and try further. When necessary, we would meet up in person and go through problems together. I couldn't really think of anything to improve here.

I intend to stay active in the open source community and finish the remaining open points of the project eventually.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment