So I made a simple server that accepts a connection from browser using ServerSocket. When a browser connects it runs a new thread (ClientHandler) to handle that connection. In the clientHandler it responds to the GET request and delivers a html page. The page only has 1 button that sends a POST request with value "Hello from browser", this is done using AJAX so page does not refresh. Everytime I send a POST request it makes a new connection to the socket, yet the connection from the response is keep-alive?
import java.io.*;
import java.net.*;
public class Server {
private static final int PORT = 8080;
private static int count = 0;
private static final String HTML_FILE_PATH = "src/main/java/org/example/index.html";
public static void main(String[] args) throws Exception {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("Server started on port " + PORT);
while (true) {
try {
Socket clientSocket = serverSocket.accept();
System.out.println("Client: " + ++count + " Joined");
ClientHandler clientHandler = new ClientHandler(clientSocket);
// Handle the client connection in a new thread
new Thread(clientHandler).start();
} catch (IOException e) {
System.err.println("Error accepting client connection: " + e.getMessage());
}
}
}
}
}
import java.io.*;
import java.net.*;
import java.nio.file.Files;
import java.util.concurrent.atomic.AtomicInteger;
public class ClientHandler implements Runnable {
private static final AtomicInteger nextId = new AtomicInteger(0);
private Socket clientSocket;
private int clientId;
private static final String HTML_FILE_PATH = "src/main/java/org/example/index.html";
public ClientHandler(Socket clientSocket) {
this.clientSocket = clientSocket;
this.clientId = nextId.incrementAndGet();
}
@Override
public void run() {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
String line;
StringBuilder requestBuilder = new StringBuilder();
while (!(line = reader.readLine()).isBlank()) {
requestBuilder.append(line).append("\r\n");
}
String request = requestBuilder.toString();
String[] requestLines = request.split("\r\n");
String[] requestLine = requestLines[0].split(" ");
String method = requestLine[0];
String path = requestLine[1];
if ("GET".equals(method) && "/".equals(path)) {
serveFile(HTML_FILE_PATH, writer);
} else if ("POST".equals(method)) {
// Extract the Content-Length from the headers
int contentLength = getContentLength(requestLines);
char[] buffer = new char[contentLength];
// Read the POST data from the input stream
reader.read(buffer, 0, contentLength);
String postData = new String(buffer);
// Print the POST data in the server terminal
System.out.println("POST Data: " + postData);
// Continue with sending the response
String responseMessage = "Hello Client " + clientId + ", This is server.";
writeResponse(writer, "HTTP/1.1 200 OK", "text/plain", responseMessage.getBytes(), true);
} else {
writeResponse(writer, "HTTP/1.1 404 Not Found", "text/plain", "Not Found".getBytes(), false);
}
} catch (IOException e) {
System.err.println("Error handling client " + clientId + ": " + e.getMessage());
}
}
private void serveFile(String filePath, BufferedWriter writer) throws IOException {
File file = new File(filePath);
if (file.exists()) {
byte[] fileContent = Files.readAllBytes(file.toPath());
writeResponse(writer, "HTTP/1.1 200 OK", "text/html", fileContent, true);
} else {
writeResponse(writer, "HTTP/1.1 404 Not Found", "text/plain", "File not found".getBytes(), false);
}
}
private void writeResponse(BufferedWriter writer, String statusLine, String contentType, byte[] content, boolean keepAlive) throws IOException {
writer.write(statusLine + "\r\n");
writer.write("Content-Type: " + contentType + "\r\n");
writer.write("Content-Length: " + content.length + "\r\n");
if (keepAlive) {
writer.write("Connection: keep-alive\r\n");
writer.write("Keep-Alive: timeout=60, max=100\r\n");
} else {
writer.write("Connection: close\r\n");
}
writer.write("\r\n");
writer.write(new String(content));
writer.flush();
}
private int getContentLength(String[] requestHeaders) {
for (String header : requestHeaders) {
if (header.toLowerCase().startsWith("content-length:")) {
return Integer.parseInt(header.substring("content-length:".length()).trim());
}
}
return 0;
}
}
<!DOCTYPE html>
<html>
<head>
<title>Tic-Tac-Toe</title>
</head>
<body>
<button id="helloButton">Say Hello</button>
<div id="responseText"></div>
<script>
document.getElementById('helloButton').onclick = function() {
fetch('/', {
method: 'POST',
headers: {
'Content-Type': 'text/plain'
},
body: 'Hello from browser'
})
.then(response => response.text())
.then(data => document.getElementById('responseText').innerText = data)
.catch(error => console.error('Error:', error));
};
</script>
</body>
</html>
I was expecting to have persistant connection but here are the outputs when sending multiple POST request. Server Terminal Network in Development Tools
Your server does not support keep alive. It reads one request and sends one response, then it returns, closing the socket. To use keep alive the server needs to make sure it reads everything the client sends (to make room for the next request) and loop as long as there are no errors, reading new requests on the same connection.
This is non-trivial to get right, but for a simple test you may get away with adding a loop inside your try block to keep processing requests as long as there are no errors.
The browser does support keep alive, so there is probably no need to do anything on the client side. All modern browsers cache and reuse connections.