Browse Source

Merge pull request #4181 from amichair/master

added httpserver (com.sun.net.httpserver) framework
Mike Smith 6 years ago
parent
commit
219142c402

+ 1 - 0
.travis.yml

@@ -51,6 +51,7 @@ env:
      - "TESTDIR=Java/grizzly"
      - "TESTDIR=Java/grizzly-jersey"
      - "TESTDIR=Java/helidon"
+     - "TESTDIR=Java/httpserver"
      - "TESTDIR=Java/jawn"
      - "TESTDIR=Java/javalin"
      - "TESTDIR=Java/jetty"

+ 36 - 0
frameworks/Java/httpserver/README.md

@@ -0,0 +1,36 @@
+# httpserver Benchmarking Test
+
+This is the com.sun.net.httpserver portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+Package [com.sun.net.httpserver](https://docs.oracle.com/javase/8/docs/jre/api/net/httpserver/spec/com/sun/net/httpserver/HttpServer.html)
+provides a simple high-level Http server API, which can be used to build embedded HTTP servers.
+It is built-in to the Oracle JDK and OpenJDK (but is not part of the Java standard
+and is not available in other JDKs).
+
+
+
+
+### Test Type Implementation Source Code
+
+* [JSON](src/main/java/benchmarks/Server.java)
+* [Plaintext](src/main/java/benchmarks/Server.java)
+* [Fortunes](src/main/java/benchmarks/Server.java)
+
+## Important Libraries
+The tests were run with:
+* [Jackson](https://github.com/FasterXML/jackson)
+* [HikariCP](https://github.com/brettwooldridge/HikariCP)
+* [HTTL](https://httl.github.io/en/)
+
+## Test URLs
+### JSON
+
+http://localhost:8080/json
+
+### Plaintext
+
+http://localhost:8080/plaintext
+
+### Fortunes
+
+http://localhost:8080/fortunes

+ 44 - 0
frameworks/Java/httpserver/benchmark_config.json

@@ -0,0 +1,44 @@
+{
+  "framework": "httpserver",
+  "tests": [
+    {
+      "default": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Platform",
+        "database": "None",
+        "framework": "None",
+        "language": "Java",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "httpserver",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "httpserver",
+        "notes": "",
+        "versus": ""
+      },
+      "postgres": {
+        "fortune_url": "/fortunes",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Platform",
+        "database": "Postgres",
+        "framework": "None",
+        "language": "Java",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "httpserver",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "httpserver-postgres",
+        "notes": "",
+        "versus": ""
+      }
+    }
+  ]
+}

+ 10 - 0
frameworks/Java/httpserver/httpserver-postgres.dockerfile

@@ -0,0 +1,10 @@
+FROM maven:3.5.3-jdk-10-slim as maven
+WORKDIR /httpserver
+COPY pom.xml pom.xml
+COPY src src
+RUN mvn compile assembly:single -q
+
+FROM openjdk:10-jre-slim
+WORKDIR /httpserver
+COPY --from=maven /httpserver/target/httpserver-1.0-jar-with-dependencies.jar app.jar
+CMD ["java", "-server", "-Xss256k", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AggressiveOpts", "-jar", "app.jar", "postgres"]

+ 10 - 0
frameworks/Java/httpserver/httpserver.dockerfile

@@ -0,0 +1,10 @@
+FROM maven:3.5.3-jdk-10-slim as maven
+WORKDIR /httpserver
+COPY pom.xml pom.xml
+COPY src src
+RUN mvn compile assembly:single -q
+
+FROM openjdk:10-jre-slim
+WORKDIR /httpserver
+COPY --from=maven /httpserver/target/httpserver-1.0-jar-with-dependencies.jar app.jar
+CMD ["java", "-server", "-Xss256k", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AggressiveOpts", "-jar", "app.jar"]

+ 89 - 0
frameworks/Java/httpserver/pom.xml

@@ -0,0 +1,89 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.techempower</groupId>
+    <artifactId>httpserver</artifactId>
+    <version>1.0</version>
+    <packaging>jar</packaging>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>2.9.7</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.module</groupId>
+            <artifactId>jackson-module-afterburner</artifactId>
+            <version>2.9.7</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+            <version>42.2.5</version>
+        </dependency>
+        <dependency>
+            <groupId>com.zaxxer</groupId>
+            <artifactId>HikariCP</artifactId>
+            <version>3.2.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.httl</groupId>
+            <artifactId>httl</artifactId>
+            <version>1.0.11</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>1.7.25</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <inherited>true</inherited>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.0</version>
+                <configuration>
+                    <debug>false</debug>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>3.1.0</version>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>benchmarks.Server</mainClass>
+                        </manifest>
+                    </archive>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 25 - 0
frameworks/Java/httpserver/src/main/java/benchmarks/Fortune.java

@@ -0,0 +1,25 @@
+package benchmarks;
+
+public class Fortune implements Comparable<Fortune> {
+
+    private final int id;
+    private final String message;
+
+    public Fortune(int id, String message) {
+        this.id = id;
+        this.message = message;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    @Override
+    public int compareTo(Fortune other) {
+        return message.compareTo(other.message);
+    }
+}

+ 15 - 0
frameworks/Java/httpserver/src/main/java/benchmarks/Message.java

@@ -0,0 +1,15 @@
+package benchmarks;
+
+public class Message {
+
+    private final String message;
+
+    public Message(String message) {
+        this.message = message;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+}

+ 143 - 0
frameworks/Java/httpserver/src/main/java/benchmarks/Server.java

@@ -0,0 +1,143 @@
+package benchmarks;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.text.ParseException;
+import java.util.*;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import javax.sql.DataSource;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import httl.Engine;
+import httl.Template;
+
+public class Server {
+
+    private static final String HELLO_TEXT = "Hello, World!";
+    private static final byte[] HELLO_BYTES = HELLO_TEXT.getBytes();
+    private static final int HELLO_LENGTH = HELLO_BYTES.length;
+    private static final String SERVER_NAME = "httpserver";
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    static {
+        MAPPER.registerModule(new AfterburnerModule());
+    }
+
+    private static List<Fortune> queryFortunes(DataSource ds) throws SQLException {
+        List<Fortune> fortunes = new ArrayList<>();
+        try (Connection conn = ds.getConnection();
+             PreparedStatement statement = conn.prepareStatement("SELECT id, message FROM fortune");
+             ResultSet resultSet = statement.executeQuery()) {
+            while (resultSet.next())
+                fortunes.add(new Fortune(resultSet.getInt(1), resultSet.getString(2)));
+        }
+        return fortunes;
+    }
+
+    private static DataSource createPostgresDataSource() throws ClassNotFoundException {
+        Class.forName("org.postgresql.Driver");
+        HikariConfig config = new HikariConfig();
+        config.setJdbcUrl("jdbc:postgresql://tfb-database:5432/hello_world");
+        config.setUsername("benchmarkdbuser");
+        config.setPassword("benchmarkdbpass");
+        config.setMaximumPoolSize(64);
+        return new HikariDataSource(config);
+    }
+
+    private static Template loadTemplate(String filename) throws IOException, ParseException {
+        Properties props = new Properties();
+        props.put("import.packages", "java.util," + Fortune.class.getPackage().getName());
+        props.put("input.encoding", "UTF-8");
+        props.put("output.encoding", "UTF-8");
+        props.put("precompiled", "false");
+        Engine engine = Engine.getEngine(props);
+        return engine.getTemplate(filename);
+    }
+
+    private static HttpHandler createPlaintextHandler() {
+        return t -> {
+            t.getResponseHeaders().add("Content-Type", "text/plain");
+            t.getResponseHeaders().add("Server", SERVER_NAME);
+            t.sendResponseHeaders(200, HELLO_LENGTH);
+            t.getResponseBody().write(HELLO_BYTES);
+            t.getResponseBody().close();
+        };
+    }
+
+    private static HttpHandler createJSONHandler() {
+        return t -> {
+            // serialize message to JSON
+            Message msg = new Message(HELLO_TEXT);
+            byte[] bytes;
+            try {
+                bytes = MAPPER.writeValueAsBytes(msg);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+            // send response
+            t.getResponseHeaders().add("Content-Type", "application/json");
+            t.getResponseHeaders().add("Server", SERVER_NAME);
+            t.sendResponseHeaders(200, bytes.length);
+            t.getResponseBody().write(bytes);
+            t.getResponseBody().close();
+        };
+    }
+
+    private static HttpHandler createFortunesHandler(DataSource ds) throws IOException, ParseException {
+        Template template = loadTemplate("/fortunes.template.httl");
+        return t -> {
+            try {
+                // query db
+                List<Fortune> fortunes = queryFortunes(ds);
+                fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+                Collections.sort(fortunes);
+                // render template
+                Map<String, Object> context = new HashMap<>(1);
+                context.put("fortunes", fortunes);
+                ByteArrayOutputStream out = new ByteArrayOutputStream();
+                template.render(context, out);
+                byte[] bytes = out.toByteArray();
+                // send response
+                t.getResponseHeaders().add("Content-Type", "text/html; charset=utf-8");
+                t.getResponseHeaders().add("Server", SERVER_NAME);
+                t.sendResponseHeaders(200, bytes.length);
+                t.getResponseBody().write(bytes);
+                t.getResponseBody().close();
+            } catch (SQLException | ParseException e) {
+                throw new IOException(e);
+            }
+        };
+    }
+
+    public static void main(String[] args) throws Exception {
+        // parse arguments
+        String settings = args.length > 0 ? args[0] : "";
+        int port = args.length > 1 ? Integer.parseInt(args[1]) : 8080;
+        if (settings.contains("debug"))
+            System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "DEBUG");
+        // create server
+        HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
+        server.setExecutor(new ThreadPoolExecutor(
+            8, Integer.MAX_VALUE, 300, TimeUnit.SECONDS, new SynchronousQueue<>()));
+        // add context handlers
+        server.createContext("/plaintext", createPlaintextHandler());
+        server.createContext("/json", createJSONHandler());
+        if (settings.contains("postgres")) {
+            DataSource ds = createPostgresDataSource();
+            server.createContext("/fortunes", createFortunesHandler(ds));
+        }
+        // start server
+        server.start();
+    }
+}

+ 13 - 0
frameworks/Java/httpserver/src/main/resources/fortunes.template.httl

@@ -0,0 +1,13 @@
+<!--#set(List<Fortune> fortunes)-->
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+<table>
+<tr><th>id</th><th>message</th></tr>
+<!-- #for(Fortune fortune : fortunes) -->
+<tr><td>${fortune.id}</td><td>${fortune.message}</td></tr>
+<!-- #end -->
+</table>
+</body>
+</html>