Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.3k views
in Technique[技术] by (71.8m points)

kotlin - A simple Java HTTP server fails with ApacheBench but works fine on a browser

As part of a concurrency blog series, I was building the simplest HTTP server in different languages (Java, Kotlin, Rust, Go, JS, TS) and everything works fine for everything except Java/Kotlin, aka on the JVM. All the code can be found here. The below is the server code in Java, I tried a traditional Thread based one and an AsynchronousServerSocketChannel based one, but regardless when I run a benchmark with ApacheBench it fails with Broken pipe and apr_socket_recv: Connection reset by peer (104) this is weird as similar setup in other languages works fine. The problem here happens only with ApacheBench, coz when I access the URL in a browser it just works fine. SO I'm banging my head to figure out what is going on. I tried to play with keep-alive etc but doesn't seem to help. I looked at a bunch of examples of something similar and I don't see anything special being done anywhere. I'm hoping someone can figure out what is going wrong here as it definitely seems to be something to do with JVM + APacheBench. I have tried this with Java 11 and 15 but it's the same result.

Java Thread Sample (hello.html can be any HTML file)

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class JavaHTTPServerCopy {
    public static void main(String[] args) {
        int port = 8080;
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("Server is listening on port " + port);
            while (true) {
                new ServerThreadCopy(serverSocket.accept()).start();
            }
        } catch (IOException ex) {
            System.out.println("Server exception: " + ex.getMessage());
        }
    }
}

class ServerThreadCopy extends Thread {

    private final Socket socket;

    public ServerThreadCopy(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        var file = new File("hello.html");
        try (
                // we get character output stream to client (for headers)
                var out = new PrintWriter(socket.getOutputStream());
                // get binary output stream to client (for requested data)
                var dataOut = new BufferedOutputStream(socket.getOutputStream());
                var fileIn = new FileInputStream(file)
        ) {
            var fileLength = (int) file.length();
            var fileData = new byte[fileLength];
            int read = fileIn.read(fileData);
            System.out.println("Responding with Content-length: " + read);
            var contentMimeType = "text/html";
            // send HTTP Headers
            out.println("HTTP/1.1 200 OK");
            out.println("Connection: keep-alive");
            out.println("Content-type: " + contentMimeType);
            out.println("Content-length: " + fileLength);
            out.println(); // blank line between headers and content, very important !
            out.flush(); // flush character output stream buffer

            dataOut.write(fileData, 0, fileLength);
            dataOut.flush();
        } catch (Exception ex) {
            System.err.println("Error with exception : " + ex);
        } finally {
            try {
                socket.close(); // we close socket connection
            } catch (Exception e) {
                System.err.println("Error closing stream : " + e.getMessage());
            }
        }
    }
}

Error on console

Responding with Content-length: 176
Error with exception : java.net.SocketException: Broken pipe (Write failed)
Error with exception : java.net.SocketException: Broken pipe (Write failed)
Error with exception : java.net.SocketException: Broken pipe (Write failed)
Error with exception : java.net.SocketException: Broken pipe (Write failed)
Error with exception : java.net.SocketException: Broken pipe (Write failed)

ApacheBench output

ab -c 100 -n 1000 http://localhost:8080/ 

This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
apr_socket_recv: Connection reset by peer (104)

Java Async Sample

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class JavaAsyncHTTPServer {

    public static void main(String[] args) throws Exception {
        new JavaAsyncHTTPServer().go();
        Thread.currentThread().join();//Wait forever
    }

    private void go() throws IOException {
        AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open();
        InetSocketAddress hostAddress = new InetSocketAddress("localhost", 8080);
        server.bind(hostAddress);
        server.setOption(StandardSocketOptions.SO_REUSEADDR, true);
        System.out.println("Server channel bound to port: " + hostAddress.getPort());

        if (server.isOpen()) {
            server.accept(null, new CompletionHandler<>() {
                @Override
                public void completed(final AsynchronousSocketChannel result, final Object attachment) {
                    if (server.isOpen()) {
                        server.accept(null, this);
                    }
                    handleAcceptConnection(result);
                }

                @Override
                public void failed(final Throwable exc, final Object attachment) {
                    if (server.isOpen()) {
                        server.accept(null, this);
                        System.out.println("Connection handler error: " + exc);
                    }
                }
            });
        }
    }

    private void handleAcceptConnection(final AsynchronousSocketChannel ch) {
        var content = "Hello Java!";
        var message = ("HTTP/1.0 200 OK
" +
                "Connection: keep-alive
" +
                "Content-length: " + content.length() + "
" +
                "Content-Type: text/html; charset=utf-8

" +
                content).getBytes();
        var buffer = ByteBuffer.wrap(message);
        ch.write(buffer);
        try {
            ch.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

No error on console

ApacheBench output

? ab -c 100 -n 1000 http://localhost:8080/

This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
apr_socket_recv: Connection reset by peer (104)

ApacheBench output with keep-alive

 ab -k -c 100 -n 1000 http://localhost:8080/
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
apr_socket_recv: Connection reset by peer (104)
Total of 37 requests completed
question from:https://stackoverflow.com/questions/65849156/a-simple-java-http-server-fails-with-apachebench-but-works-fine-on-a-browser

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Looks like a bug on your side to me.

Your response is HTTP/1.0 + "Connection: keep-alive", meaning you're advertising the client that it can reuse the connection for performing other requests. And yet, you're closing the socket right after writing the response.

As a result, as network is not instantaneous, the client is trying to reuse the socket and write a second request, just to get the door slammed on its nose.

Either stop closing the socket on each response, or stop enforcing "Connection: keep-alive" (close is the default on HTTP/1.0).


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...