javamultithreadingserversocketjava-threadsjava-server

How to handle HTTP request using Java Socket?


I am trying to implement sample HTTP server using Java socket and executor service for concurrency. However every 2nd request is failing when I run the test using JMeter with 2 or more requests or browser for example.

How to properly handle the request? Here is the sample source code:

public class Service {
    public static void main(String[] args) throws Exception {
        var serverSocket = new ServerSocket(8080);
        var executors = Executors.newFixedThreadPool(4);
        while(true) {
            try {
                var server = serverSocket.accept();

                executors.submit(() -> {
                    try {
                        var text = "sample";
                        System.out.println("Waiting for client on port " +
                                serverSocket.getLocalPort() + "...");

                        System.out.println("Getting empty request");
                        var response = "HTTP/1.1 200 OK\r\n" +
                                    "Content-Type: text/plain\r\n" +
                                    "Content-Length: " + text.length() + "\r\n\r\n"
                                    + text;
                        server.getOutputStream().write(response.getBytes(StandardCharsets.UTF_8));
                    } catch (Exception e) {
                        System.out.println("Executor error:" + e.toString());
                        e.printStackTrace();
                    } finally {
                        try {
                            System.out.println("Closing server");
                            server.close();
                        } catch (Exception e) {
                            System.out.println("Executor error2: ");
                            e.printStackTrace();
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
                break;
            }
        }

        serverSocket.close();
    }
}

Solution

  • Your first problem lies in your response.

    "HTTP/1.1 200 OK\r\n"
    

    That allows for keep-alive, which you're not handling. A basic JMeter sampler tries to use keep alive, that is why you always fail on the second attempt.

    You can change it to

    "HTTP/1.0 200 OK\r\n"
    

    That does not support keep alive, so you'll get a lot more successes with your current code. For me I only get a couple 1000 responses before JMeter has another error, but I don't know what the error is.

    To support keep alive, I need to parse the request. Here is an example.

        int clients = 0;
        while(true) {
            try {
                System.out.println("Waiting for client on port " +
                                serverSocket.getLocalPort() + "...");
                var server = serverSocket.accept();
                final int client_no = clients++;
                System.out.println("handling: " + client_no);
                executors.submit(() -> {
                    int sent = 0;
                    try {
                        var is = server.getInputStream();
                        var os = server.getOutputStream();
                        
                        var text = "sample";
                        byte[] tb = text.getBytes(StandardCharsets.UTF_8);
    
                        char[] buffer = new char[256];
                        int cr_count = 0;
                        while( true ){
                            
                            int i=0;
                            int r = is.read();
                            if(r == -1) break;
                            
                            while( r != -1 ){
                                char c = (char)r;
                                if( c == '\n' ){
                                    cr_count++;
                                } else if( c != '\r' ){
                                    cr_count = 0;
                                }
                                buffer[i++] = c;
                                if(cr_count == 2) break;
                                r = is.read();
                            }
                            //System.out.println("request: " + new String(buffer));
                            var response = "HTTP/1.1 200 OK\r\n" +
                                        "Content-Type: text/plain\r\n" +
                                        "Content-Length: " + tb.length + "\r\n\r\n";
                            os.write(response.getBytes(StandardCharsets.UTF_8));
                            os.write(tb);
                            os.flush();
                            sent++;
                        }
                    } catch (Exception e) {
                        System.out.println("Executor error:" + e.toString());
                        e.printStackTrace();
                    } finally {
                        try {
                            System.out.println("Closing connection!");
                            server.close();
                        } catch (Exception e) {
                            System.out.println("Executor error2: ");
                            e.printStackTrace();
                        }
                        System.out.println("sent " + sent + " responses to client " + client_no);
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
                break;
            }
        }
    

    This will run a JMeter test for me. It can use either 1.0 or 1.1 and finish 10's of thousands of requests. If I use keep alive (1.1) each client handles many requests, if I don't use keep alive (1.0) each client handles 1 request.

    If I dont read the request header, then the http 1.0 version will stop after a couple thousand requests.

    It is also a separate issue from your original "dies after the second request." which is because your are not actually using HTTP 1.1 !