Search code examples
javahttpserverhttpserver

HTTP/1.1 header parser received no bytes


I get this error all the time tried everything switching JVMs and stuff changing code itself but nothing really works Chat GPT also doesn't help. If someone know what's the dead with this please help. I really want to understand what's wrong with it.

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class HttpServer {
    private final int port;

    public HttpServer(final int port) {
        this.port = port;
    }

    public void run() {
        try(var serverSocket = new ServerSocket(port)) {
                var socket = serverSocket.accept();
                System.out.println("Socket accepted");
                processSocket(socket);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void processSocket(Socket socket) throws IOException {
        try(socket;
            var inputStream = new DataInputStream(socket.getInputStream());
            var outputStream = new DataOutputStream(socket.getOutputStream())) {

            System.out.println(new String(inputStream.readNBytes(100)));
            byte[] body = Files.readAllBytes(Path.of("src/main/resources/example.html"));
            outputStream.write("""
                    HTTP/1.1 200 OK
                    content-type: text/html
                    content-length: %s
                    """.formatted(body.length).getBytes());
            outputStream.write(System.lineSeparator().getBytes());
            outputStream.write(body);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}



import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Path;
import java.util.concurrent.ExecutionException;

import static java.net.http.HttpRequest.BodyPublishers.ofFile;

public class HttpClientRunner {
    public static void main(final String[] args) throws IOException, InterruptedException, ExecutionException {
        var httpClient = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_1_1)
                .build();
        var request = HttpRequest.newBuilder()
                .uri(URI.create("http://localhost:8082"))
                .header("content-type", "application/json")
                .POST(ofFile(Path.of("src/main/resources/example.json")))
                .build();

        var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(response.headers());
        System.out.println(response.body());
    }
}

Tried to search everywhere but found literally nothing.


Solution

  • I'm going to guess here. As others have noted your question is half formed. The obvious issue is that you haven't posted the error you are receiving or described what behavior you are encountering that is unexpected. It's always good to include the expected behavior too. That being said if I execute your code as is (I removed the 2 external files and hard coded strings in their place). I get this:

    Exception in thread "main" java.net.ConnectException
        at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:951)
        at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:133)
        at Scratch.main(Scratch.java:64)
    Caused by: java.net.ConnectException
        at java.net.http/jdk.internal.net.http.common.Utils.toConnectException(Utils.java:1028)
        at java.net.http/jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:227)
        at java.net.http/jdk.internal.net.http.PlainHttpConnection.checkRetryConnect(PlainHttpConnection.java:280)
        at java.net.http/jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$2(PlainHttpConnection.java:238)
        at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934)
        at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:911)
        at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
        at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
        at java.base/java.lang.Thread.run(Thread.java:1583)
    Caused by: java.nio.channels.ClosedChannelException
        at java.base/sun.nio.ch.SocketChannelImpl.ensureOpen(SocketChannelImpl.java:202)
        at java.base/sun.nio.ch.SocketChannelImpl.beginConnect(SocketChannelImpl.java:786)
        at java.base/sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:874)
        at java.net.http/jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$1(PlainHttpConnection.java:210)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:571)
        at java.net.http/jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:212)
        ... 9 more
    

    The reason for this is your code never starts the HTTP server. There are 2 solutions for this.

    • Create a new thread and have the server run on that thread
    • Use a separate process for the server (ie two JVMs)

    Option 1: New Thread: Make your HttpServer implement Runnable so that we can pass it to a thread instance and it'll know how to execute it.

    public class HttpServer implements Runnable {
    
    

    Main method:

    
        public static void main(String[] args) throws IOException, InterruptedException {
            HttpServer server = new HttpServer(8088);
            Thread serverThread = new Thread( server );
            try {
                serverThread.start();
                
                var httpClient = HttpClient.newBuilder()
                        .version(HttpClient.Version.HTTP_1_1)
                        .build();
                var request = HttpRequest.newBuilder()
                        .uri(URI.create("http://localhost:8088"))
                        .header("content-type", "application/json")
                        .POST(ofString("{\"test\":\"this is a test\"}"))
                        .build();
        
                var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        
                System.out.println(response.headers());
                System.out.println(response.body());
            } finally {
                serverThread.interrupt(); // cause the server to shut itself down gracefully
            }
        }
    

    Option 2 - Separate processes

    Add a main method to HttpServer to start the server.

        public class HttpServer implements Runnable {
    
            private final int port;
    
            public HttpServer(final int port) {
                this.port = port;
            }
    
            public void run() {
                try(var serverSocket = new ServerSocket(port)) {
                    var socket = serverSocket.accept();
                    processSocket( socket );  // you weren't calling the processSocket method!
                    System.out.println("Socket accepted");
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("Shutdown");
                }
            }
    
            private void processSocket(Socket socket) throws IOException {
                try(socket;
                    var inputStream = new DataInputStream(socket.getInputStream());
                    var outputStream = new DataOutputStream(socket.getOutputStream())) {
    
                    System.out.println(new String(inputStream.readNBytes(100)));
                    byte[] body = "<html><body>Hello World</body></html>\n".getBytes("UTF-8");
    
                    outputStream.write("""
                        HTTP/1.1 200 OK
                        content-type: text/html
                        content-length: %s
                        """.formatted(body.length).getBytes());
                    outputStream.write(System.lineSeparator().getBytes());
                    outputStream.write(body);
                }
            }
    
            public static void main(String[] args) {
                HttpServer server = new HttpServer(8088);
                server.run();
            }
        }
    

    I had to invoke the processSocket method because it wasn't being done in your code. Without that nothing would happen, and probably everything would hang.

    If you go with option 2 which is probably the better idea. You'll need to modify the code from option 1 to remove the section where I started a thread. The only changes I made for that code block were the port (8082 -> 8088), hard coded the JSON payload, and starting up/shutting down of the server. You can use your code instead, but just remember I changed the port.

    After doing these changes I did get your code to execute and run to completion.