Javalin: Create javalin-without-jetty module

Created on 20 Apr 2020  ·  23Comments  ·  Source: tipsy/javalin

Updated description:
99% of Javalin users use Javalin with Jetty, but there is a small subset of users who use it with Tomcat (or other web-servers). Currently using Javalin without Jetty is made possible by excluding Jetty dependencies, since all objects that use Jetty classes are null by default.

We want to create a module, javalin-without-jetty, which makes life easier for these ”jetty haters”, but we don’t want to do this in a way that adds any complexity to the javalin module, as that would possibly annoy the 99% of users who don’t care about this at all.

Creating interfaces and different implementations is not considered an option, as it would be too annoying to maintain. It would be great if we could have javalin-without-jetty depend on javalin, but somehow just remove a couple of methods (primarily from Javalin.class).

  • Could we shade in javalin and overrwite the class files?
  • Could we use ASM somehow? (https://stackoverflow.com/questions/41968996/removing-methods-with-java-asm)

I'm very open to ideas, the hackier the better.

FEATURE REQUEST HELP WANTED

All 23 comments

Sorry @tipsy the current situation is quite crazy (kids being at home), I can't really take additional tasks right now.

No problem @mvysny ! Keep safe :)

Thank you, you too ☺️

Can you explain the feature @tipsy ?

The original feature request is probably #402 . In general, sometimes we need to embed Javalin as a part of a bigger app into a WAR running on e.g. Tomcat. However, Javalin pulls in Jetty jars, which is unwanted in such scenario.

My first idea would be to extract parts of Javalin not directly depending on Jetty into e.g. a javalin-core Maven module, then have the javalin module which depends on javalin-core and adds Jetty. When embedding Javalin, only the javalin-core artifact would be used. However, this requires a lot of work moving code around.

Simpler solution would be to have javalin-standalone which depends on current javalin but removes the Jetty dependency. Anyone using javalin-standalone needs to take care not to use Jetty-dependent functionality otherwise you'll get NoClassDefFoundError or similar.

The option with javalin-core offers better evolutivity (sun server, undertow, tomcat, netty ?), but need a lot more work. I can look at this without promise of success ;)

@chsfleury Unless you have a lot of time to work on this, I would advise against creating javalin-core at this point, Jetty was intended to be a hard dependency, and 99% of users are happy with it. Untangling it will be a lot of work, but if you are determined to do it I can tell you where are all hard dependencies are :)

Ok :sweat_smile:

I'll have a look at this now.

I added javalin-without-jetty in https://github.com/tipsy/javalin/commit/6b9a3b4f54521cf25acbc5b93ab9e39279faf5cc. I'll see if there's some sneaky way to remove the methods that we don't want.

I receive this suggestion:

  • Javalin-without-jetty pom file declares a provided dependency on javalin
  • javalin-without-jetty defines the shade plugin including all the contents of the javalin artifact excluding Javalin.class
  • The Javalin source file defines start and end blocks which will not be included in the compilation for Javalin. Something like //@start-jetty and //@stop-jetty around those methods
  • Add an ant-run maven plugin declaration to javalin-without-jetty bound to generate-sources phase which will copy the original javalin source files appling a standard filter which will remove the source code inside the tokens https://ant.apache.org/manual/api/org/apache/tools/ant/filters/ReplaceTokens.html into the target/generated directory under javalin-without-jetty so it will be discovered by the compiler on the compile phase and will be ended up in the .jar file
  • The end user adds the javalin-without-jetty to the pom file getting only one single Javalin class

Which is almost perfect, but I would like to avoid having annotations in the main module source code. I'm wondering if it would be possible to copy the file, do search/replace and make the methods we don't want private (ex public void ws( -> private void ws()

🤔

Ouch, it's getting more complicated and hacky imho... At this point perhaps the javalin-core approach will ultimately produce the most simple result, even though it's more work? We can maybe do this in stages:

  1. Create javalin-core, move all code there and make javalin an empty jar with just a dependency on javalin-core. We can do this in one huge commit.
  2. Slowly start moving Jetty-related code to javalin. We can do this gradually with PRs as usual.
  3. At some point move the jetty dependency to javalin.

@tipsy if you could do the step 1 in one quick sweep, I can then help with step 2 and try creating a couple of PRs?

Ouch, it's getting more complicated and hacky imho...

Definitely, but I would much rather have some nasty hacks in javalin-without-jetty than make the overall project structure cater to the 1% who need this 🤔

Unless everything (including WebSockets and static files) will work without Jetty, I don't think splitting is a good idea.

Amazingly, this seems to work:

<properties>
    <javalin.original.path>../javalin/src/main/java/io/javalin/Javalin.java</javalin.original.path>
    <javalin.hack.path>../javalin-without-jetty/target/generated-sources/src/main/java/io/javalin/Javalin.java</javalin.hack.path>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>3.2.3</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <artifactSet>
                            <excludes>
                                <exclude>org.slf4j</exclude>
                                <exclude>javax.servlet</exclude>
                                <exclude>org.jetbrains.kotlin</exclude>
                                <exclude>org.jetbrains:annotations</exclude>
                                <exclude>org.jetbrains:annotations</exclude>
                                <exclude>org.eclipse.*</exclude>
                                <exclude>javax.servlet</exclude>
                            </excludes>
                        </artifactSet>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-antrun-plugin</artifactId>
            <version>3.0.0</version>
            <executions>
                <execution>
                    <id>generate-sources</id>
                    <phase>generate-sources</phase>
                    <configuration><!-- @formatter:off -->
                        <target name="hacky-mchackface">
                            <copy file="${javalin.original.path}" tofile="${javalin.hack.path}"/>
                            <replace file="${javalin.hack.path}" token="public Javalin ws("
                                                                 value="private Javalin ws("/>
                            <replace file="${javalin.hack.path}" token="public Javalin wsBefore("
                                                                 value="private Javalin wsBefore("/>
                            <replace file="${javalin.hack.path}" token="public Javalin wsAfter("
                                                                 value="private Javalin wsAfter("/>
                            <replace file="${javalin.hack.path}" token="public &lt;T extends Exception&gt; Javalin wsException("
                                                                 value="private &lt;T extends Exception&gt; Javalin wsException("/>
                        </target>
                    </configuration><!-- @formatter:on -->
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <executions>
                <execution>
                    <phase>generate-sources</phase>
                    <goals>
                        <goal>add-source</goal>
                    </goals>
                    <configuration>
                        <sources>
                            <source>${project.build.directory}/generated-sources/</source>
                        </sources>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

I almost can't believe it.

@mvysny I know you're not a fan of the approach, but can you test the module and see if you're happy with the result? :)

@tipsy Haha :-) I don't mind the solution unless I have to maintain it :-p Sure thing, I'll give it a go and let you know. Today, hopefully tomorrow latest.

Tried to quickly integrate javalin-without-jetty with vok and found one potential issue.

I have a vok-rest module which is a reusable library, adding functionality on top of Javalin. Naturally vok-rest will now depend on javalin-without-jetty since the module should be usable in both scenarios.

However, at the same time I wish to test the module. I use full-blown Javalin with Jetty to test the module, and so I add testCompile dependency on javalin. And now the problematic part: on test classpath there are both 'javalin' and 'javalin-without-jetty' present; the tests won't compile anymore if javalin-without-jetty happens to be first on the classpath since the tests call Javalin.create().start().

I'm not sure how important this use-case is though. A workaround would be to use javalin with excluded jetty deps, then add jetty on the test classpath (current solution), or to move tests to separate module.

I'm not sure how important this use-case is though.

This sounds a bit like it's an edge-case on top of another edge-case 😅

I don't really know how the methods can both be there and not be there at the same time. You could write a small Jetty server and attach the JavalinServlet to it manually for your tests?

import io.javalin.Javalin
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.ServerConnector
import org.eclipse.jetty.servlet.ServletContextHandler
import org.eclipse.jetty.servlet.ServletHolder

class JavalinServer(val server: Server = Server(), val port: Int = 7000) {

    fun start(javalin: Javalin, port: Int) = server.apply {
        handler = object : ServletContextHandler(null, javalin.config.contextPath, SESSIONS) {}.apply {
            addServlet(ServletHolder(javalin.servlet()), "/*")
        }
        connectors = arrayOf(ServerConnector(server).apply { this.port = port })
    }.start()

    fun stop() = server.stop();

}


fun main() {
    val app = Javalin.create().get("/hello") { it.result("Hello, tests!") }
    JavalinServer().start(app, 7777)
}

I don't really know how the methods can both be there and not be there at the same time.

That's because there are both javalin-without-jetty.jar and javalin.jar on test classpath; the classpath order now tells which Javalin class from which jar gets loaded, which is quite fragile.

I agree that my example is a bit far-fetched. More realistic example would be to build a reusable library on top of Javalin, used by some app. If the library uses javalin-without-jetty and the app uses javalin, we have the same situation: whether your app crashes or not depends on the classpath order.

If we had javalin-core and javalin depending on javalin-core and adding Jetty-specific bits, the reusable library could simply depend on javalin-core and the app can then decide whether to use javalin-core or javalin.

Of course the workaround is to use the library, exclude javalin-without-jetty and depend on javalin.

Thank you so much for your example code, that's a very nice example of getting Javalin embedded quickly.

I was imagining one project wanting to both depend on javalin-without-jetty and javalin directly, which made no sense to me. I suppose you have a somewhat valid case with your tests, but IMO it makes more sense to mount Javalin manually to Jetty there.

For javalin-without-jetty being pulled in as a transitive dependency, I think using exclusions is fine. I feel like we're approaching a territory beyond edge-cases at this point 😄

If we had javalin-core and javalin depending on javalin-core and adding Jetty-specific bits, the reusable library could simply depend on javalin-core and the app can then decide whether to use javalin-core or javalin.

Yeah, I get that, I'm just very reluctant to introduce this abstraction layer for this use-case. I can re-evaluate it (again) for Javalin 4.0, but I doubt it will be included.

Thank you so much for your example code, that's a very nice example of getting Javalin embedded quickly.

You're very welcome!

I will close this issue now, but please let me know if you think of any other problems with this implementation.

I'm trying to use Javalin in an AWS lambda, and changing the dependency to the w/o Jetty version saves me 3-4 MB on the jar size. Which is a help.

Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

valtterip picture valtterip  ·  5Comments

gane5h picture gane5h  ·  3Comments

spinscale picture spinscale  ·  3Comments

mkpaz picture mkpaz  ·  4Comments

vikascn picture vikascn  ·  4Comments