Coder Social home page Coder Social logo

microhttp's Introduction

Microhttp

Microhttp is a fast, scalable, event-driven, self-contained Java web server that is small enough for a programmer to understand and reason about. It does not rely on any classpath dependencies or native code.

It is capable of serving over 1,000,000 requests per second on a commodity EC2 host (c5.2xlarge). TechEmpower continuous benchmarking results consistently show Microhttp achieves over 2,000,000 requests per second.

Comprehensibility is the highest priority. This library is intended to be an alternative to commonly used frameworks with overwhelming complexity.

Microhttp discretizes all requests and responses. Streaming is not supported. This aligns well with transactional web services that exchange small payloads.

Microhttp supports aspects of HTTP 1.0 and HTTP 1.1, but it is not fully compliant with the spec (RFC 2616, RFC 7230, etc.) 100-Continue (RFC 2616 8.2.3) is not implemented, for example.

TLS is not supported. Edge proxies and load balancers provide this capability. The last hop to Microhttp typically does not require TLS.

HTTP 2 is not supported for a similar reason. Edge proxies can support HTTP 2 while using HTTP 1.1 on the last hop to Microhttp.

Microhttp is 100% compatible with Project Loom virtual threads. Simply handle each request in a separate virtual thread, invoking the callback function upon completion.

Principles:

  • No dependencies
  • Small, targeted codebase (~500 LOC)
  • Highly concurrent
  • Single-threaded event loops
  • Event-driven non-blocking NIO
  • No TLS support
  • No streaming support
  • Traceability via log events

Includes:

  • HTTP 1.0 and 1.1
  • Chunked transfer encoding
  • Persistent connections
  • Pipelining

Intended Use:

  • Teaching or learning scalable concurrency, NIO, HTTP, networking
  • Mock or stub servers for testing
  • Internal web servers not exposed to the internet
  • Web server behind an internet-facing reverse proxy (Nginx, HAProxy, AWS ELB, etc)

Dependency

Microhttp is available in the Maven Central repository with group org.microhttp and artifact microhttp.

<dependency>
    <groupId>org.microhttp</groupId>
    <artifactId>microhttp</artifactId>
    <version>0.11</version>
</dependency>

Getting Started

The snippet below represents a minimal starting point. Default options and debug logging.

The application consists of an event loop running in a background thread.

Responses are handled immediately in the Handler.handle method.

Response response = new Response(
        200,
        "OK",
        List.of(new Header("Content-Type", "text/plain")),
        "hello world\n".getBytes());
Handler handler = (req, callback) -> callback.accept(response);
EventLoop eventLoop = new EventLoop(handler);
eventLoop.start();
eventLoop.join();

The following example demonstrates the full range of configuration options.

Response response = new Response(
        200,
        "OK",
        List.of(new Header("Content-Type", "text/plain")),
        "hello world\n".getBytes());
Options options = Options.builder()
        .withHost("localhost")
        .withPort(8080)
        .withRequestTimeout(Duration.ofSeconds(60))
        .withResolution(Duration.ofMillis(100))
        .withReadBufferSize(1_024 * 64)
        .withMaxRequestSize(1_024 * 1_024)
        .withAcceptLength(0)
        .withConcurrency(4)
        .build();
Logger logger = new DebugLogger();
Handler handler = (req, callback) -> callback.accept(response);
EventLoop eventLoop = new EventLoop(options, logger, handler);
eventLoop.start();
eventLoop.join();

The example below demonstrates asynchronous request handling.

Responses are handled in a separate background thread after an artificial one-second delay.

Response response = new Response(
        200,
        "OK",
        List.of(new Header("Content-Type", "text/plain")),
        "hello world\n".getBytes());
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
Handler handler = (req, callback) -> executorService.schedule(() -> callback.accept(response), 1, TimeUnit.SECONDS);
EventLoop eventLoop = new EventLoop(handler);
eventLoop.start();
eventLoop.join();

Benchmarks

These benchmark were performed on July 12, 2022 with commit 78f54e84e86cdd038c87baaf45b7973a8f088cf7.

The experiments detailed below were conducted on a pair of EC2 instances in AWS, one running the server and another running the client.

  • Region: us-west-2
  • Instance type: c5.2xlarge compute optimized instance 8 vCPU and 16 GB of memory
  • OS: Amazon Linux 2 with Linux Kernel 5.10, AMI ami-00f7e5c52c0f43726
  • OpenJDK 18.0.1.1 from https://jdk.java.net/18/

The wrk HTTP benchmarking tool was used to generate load on the client EC2 instance.

Throughput

The goal of throughput benchmarks is to gauge the maximum request-per-second rate that can be supported by Microhttp. These experiments are intended to surface the costs and limitations of Microhttp alone. They are not intended to provide a real world estimate of throughput in an integrated system with many components and dependencies.

Server

ThroughputServer.java was used for throughput tests.

It simply returns "hello world" in a tiny, plain-text response to every request. Requests are handled in the context of the event loop thread, directly within the Handler.handle method.

./jdk-18.0.1.1/bin/java -cp microhttp-0.8-SNAPSHOT.jar org.microhttp.ThroughputServer

Benchmark

With HTTP pipelining, a request rate of over 1,000,000 requests per second was consistently reproducible.

In the 1-minute run below, a rate of 1,098,810 requests per second was achieved.

  • 100 concurrent connections
  • 1 wrk worker threads
  • 10 second timeout
  • 16 pipelined requests

No custom kernel parameters were set beyond the AMI defaults for this test.

No errors occurred and the 99th percentile response time was quite reasonable, given that client and server were both CPU-bound.

$ date
Tue Jul 12 17:11:05 UTC 2022

$ ./wrk -H "Host: 10.39.196.71:8080" -H "Accept: text/plain" -H "Connection: keep-alive" --latency -d 60s -c 100 --timeout 10 -t 1 http://10.39.196.71:8080/ -s pipeline.lua -- 16
Running 1m test @ http://10.39.196.71:8080/
  1 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    18.44ms   13.95ms  52.12ms   53.25%
    Req/Sec     1.10M    22.87k    1.14M    87.83%
  Latency Distribution
     50%   18.37ms
     75%   31.47ms
     90%   39.33ms
     99%    0.00us
  65929433 requests in 1.00m, 4.73GB read
Requests/sec: 1098810.79
Transfer/sec:     80.69MB

Without HTTP pipelining, a request rate of over 450,000 requests per second was consistently reproducible.

In the 1-minute run below, a rate of 454,796 requests per second was achieved.

  • 100 concurrent connections
  • 8 wrk worker threads
  • 10 second timeout

No errors occurred and the 99th percentile response time was exceptional.

$ date
Tue Jul 12 17:16:49 UTC 2022
 
$ ./wrk -H "Host: 10.39.196.71:8080" -H "Accept: text/plain" -H "Connection: keep-alive" --latency -d 60s -c 100 --timeout 10 -t 8 http://10.39.196.71:8080/
Running 1m test @ http://10.39.196.71:8080/
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   218.65us    1.64ms 212.93ms   99.97%
    Req/Sec    57.15k     4.68k   69.47k    85.19%
  Latency Distribution
     50%  188.00us
     75%  229.00us
     90%  277.00us
     99%  372.00us
  27332950 requests in 1.00m, 1.96GB read
Requests/sec: 454796.26
Transfer/sec:     33.40MB

Concurrency

The goal of concurrency benchmarks is to gauge the number of concurrent connections and clients that can be supported by Microhttp.

In order to facilitate the rapid creation of 50,000 connections, the following sysctl kernel parameter changes were committed on both hosts prior to the start of the experiment:

sysctl net.ipv4.ip_local_port_range="2000 64000"
sysctl net.ipv4.tcp_fin_timeout=30
sysctl net.core.somaxconn=8192
sysctl net.core.netdev_max_backlog=8000
sysctl net.ipv4.tcp_max_syn_backlog=8192

Server

ConcurrencyServer.java was used for concurrency tests.

"hello world" responses are handled in a separate background thread after an injected one-second delay. The one-second delay dramatically reduces the resource footprint since requests and responses aren't speeding over each connection continuously. This leaves room to scale up connections, which is the metric of interest.

./jdk-18.0.1.1/bin/java -cp microhttp-0.8-SNAPSHOT.jar org.microhttp.ConcurrencyServer 8192

Benchmark

A concurrency level of 50,000 connections without error was consistently reproducible.

  • 50,000 concurrent connections
  • 16 wrk worker threads
  • 10 second timeout

No errors occurred.

The quality of service is stellar. The 99% percentile response time it 1.01 seconds, just 0.01 above the target latency introduced on the server.

$ date
Tue Jul 12 17:26:53 UTC 2022

$ ./wrk -H "Host: 10.39.196.71:8080" -H "Accept: text/plain" -H "Connection: keep-alive" --latency -d 60s -c 50000 --timeout 10 -t 16 http://10.39.196.71:8080/
Running 1m test @ http://10.39.196.71:8080/
  16 threads and 50000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.00s     2.74ms   1.21s    95.44%
    Req/Sec     8.52k    11.02k   31.56k    73.64%
  Latency Distribution
     50%    1.00s 
     75%    1.00s 
     90%    1.00s 
     99%    1.01s 
  2456381 requests in 1.00m, 180.38MB read
Requests/sec:  40875.87
Transfer/sec:      3.00MB

microhttp's People

Contributors

beatngu13 avatar bowbahdoe avatar brucemelo avatar ebarlas avatar filiphr avatar gustavosdelgado avatar matteobaccan avatar varunu28 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

microhttp's Issues

Discussion point - any features of jdk.httpserver desired? managing idle connections perhaps?

I presume that we are aware of jdk.httpserver so I am having a quick look at the features of jdk.httpserver and just opening this as a discussion point to see / confirm if there are any features in there that might be considered desirable for microhttp.

Glancing at the jdk.httpserver code and the thing that popped out was the management of idle connections. It has a background timertask to manage idle connections (plus an optional one to manage request exceeding MAX_REQ_TIME and MAX_RSP_TIME).

Are there any features in jdk.httpserver that are desired to be added to microhttp?

GET Request leads to null body.

Making this issue to track. Sending a request without a content-length and that isn't a chunked transfer will lead to a Request that has a null value for .body()

Discussion point - Buffer recycling on readBuffer but not writeBuffer (maybe)

I could be wrong or low on caffeine but it looks to me like there is effective buffer recycling with readBuffer but not with writeBuffer at the moment? Am I reading that incorrectly or maybe it doesn't matter?

That is, it looks like with writeBuffer there is a decent amount of copying in Response.merge() and then wrap(). I get a sense we could avoid the byte[] result = new byte[size]; and look to recycle writeBuffer instead?

Create tagged GitHub releases

There are currently two releases of Microhttp on Maven Central:

https://search.maven.org/artifact/org.microhttp/microhttp

v0.1 and v0.2 appear to have no corresponding Git tags:

https://github.com/ebarlas/microhttp/tags

There are also no GitHub releases:

https://github.com/ebarlas/microhttp/releases

Here are some release examples from a personal project:

https://github.com/beatngu13/pdf-zoom-wizard/releases

PR #6 will add a basic GitHub Actions build. Based on this, another workflow could be created to automatically set the project's version, tag the commit, and create a GitHub release. It is also possible to use something like JReleaser.

What do you think? I'm happy to help if you are interested.

Demo using todobackend

https://todobackend.com/ is a an initiative to show that a bunch of REST services can pass a known frontend test suite.

That test suite is a Jasmine suite and runs in the browser. There' a series of requests performed in order. GET, POST and others. It is very cool. Can microhttp stand in the middle and delegate requests to one of the other backends, and still pass the test suite.

Or maybe a native TodoBackend using microhttp. Easiest to fork one of the existing Java ones perhaps - and there's no DB requirement - a HashMap would do.

Discussion - style option rather than using new EventLoop directly/explicitly for usual expected use?

So instead of having this style ...

    public static void main(String[] args) throws IOException, InterruptedException {

        Options options = new Options()
                .withPort(8080)
                .withSocketTimeout(Duration.ofSeconds(60))
                .withReadBufferSize(1_024 * 64)
                .withMaxRequestSize(1_024 * 1_024);

  
        Handler handler = ...;

        EventLoop eventLoop = new EventLoop(options, new DisabledLogger(), handler);
        Thread eventLoopThread = new Thread(() -> {
            try {
                eventLoop.start();
            } catch (IOException e) {
                // thread terminates
            }
        });
        eventLoopThread.start();

       ...
        
        eventLoopThread.join();
    }

Have a MicroHttp.Builder where we with the optional config options, pass the mandatory params to a start() ... under the hood create the event loop etc.

Return a MicroHttp with a stop() method?

    public static void main(String[] args) throws IOException, InterruptedException {

        Handler handler = ...;

        var microHttp = new MicroHttp.newBuilder()
                .withPort(8080)
                .withSocketTimeout(Duration.ofSeconds(60))
                .withReadBufferSize(1_024 * 64)
                .withMaxRequestSize(1_024 * 1_024)

               .withLogger(...)

               .start(handler);  // creates event loop, starts
  

       microHttp.stop();

    }

Just using "defaults" would be like:

public static void main(String[] args) {

  Handler handler = ...;

  var microHttp = new MicroHttp
      .newBuilder()
      .start(handler);
  

    // programmatically stop ..
    microHttp.stop();

}

The thinking would be to make the "usual use" a little bit more obvious (in theory).

'SO_REUSEPORT' not supported

Exception in thread "main" java.lang.UnsupportedOperationException: 'SO_REUSEPORT' not supported
at java.base/sun.nio.ch.ServerSocketChannelImpl.setOption(ServerSocketChannelImpl.java:212)
at org.microhttp.EventLoop.(EventLoop.java:80)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.