Last active
February 6, 2023 02:23
-
-
Save mcroteau/59df6303efe5b6c1365a784bc91ad610 to your computer and use it in GitHub Desktop.
Mutlithreaded Server in Java with Gradle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
build.gradle | |
plugins { | |
id 'application' | |
} | |
group 'foo.bar' | |
version '1.0' | |
repositories { | |
mavenCentral() | |
} | |
application{ | |
mainClass.set("foo.FooServer") | |
} | |
dependencies {} | |
*/ | |
package foo; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import java.net.ServerSocket; | |
import java.net.Socket; | |
import java.nio.ByteBuffer; | |
import java.util.concurrent.ConcurrentHashMap; | |
import java.util.concurrent.ConcurrentMap; | |
import java.util.concurrent.ExecutorService; | |
import java.util.concurrent.Executors; | |
public class FooServer { | |
public static void main(String[] args) throws IOException { | |
ServerSocket serverSocket = new ServerSocket(7654); | |
serverSocket.setPerformancePreferences(0, 1, 2); | |
/* the higher the numbers, the better the concurrent performance, ha! | |
we found that a 3:7 ratio to be optimal | |
3 partitioned executors to 7 network executors */ | |
ExecutorService executors = Executors.newFixedThreadPool(3); | |
executors.execute(new PartitionedExecutor(serverSocket)); | |
} | |
public static class PartitionedExecutor implements Runnable { | |
ServerSocket serverSocket; | |
public PartitionedExecutor(ServerSocket serverSocket) { | |
this.serverSocket = serverSocket; | |
} | |
@Override | |
public void run() { | |
ExecutorService executors = Executors.newFixedThreadPool(30); | |
executors.execute(new NetworkRequestExecutor(serverSocket, executors)); | |
} | |
} | |
public static class NetworkRequestExecutor implements Runnable{ | |
String IGNORE_CHROME = "/favicon.ico"; | |
String BREAK = "\r\n"; | |
String DOUBLEBREAK = "\r\n\r\n"; | |
Integer REQUEST_METHOD = 0; | |
Integer REQUEST_PATH = 1; | |
Integer REQUEST_VERSION = 2; | |
String RENDERER; | |
Socket socketClient; | |
ExecutorService executors; | |
ServerSocket serverSocket; | |
public NetworkRequestExecutor(ServerSocket serverSocket, ExecutorService executors){ | |
this.serverSocket = serverSocket; | |
this.executors = executors; | |
} | |
@Override | |
public void run() { | |
try { | |
socketClient = serverSocket.accept(); | |
Thread.sleep(19);//do this for safari, its a hack but safari requires something like this. | |
InputStream requestInputStream = socketClient.getInputStream(); | |
OutputStream clientOutput = socketClient.getOutputStream(); | |
if (requestInputStream.available() == 0) { | |
requestInputStream.close(); | |
clientOutput.flush(); | |
clientOutput.close(); | |
executors.execute(new NetworkRequestExecutor(serverSocket, executors)); | |
return; | |
} | |
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); | |
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); | |
int bytesRead; | |
while ((bytesRead = requestInputStream.read(byteBuffer.array())) != -1) { | |
byteArrayOutputStream.write(byteBuffer.array(), 0, bytesRead); | |
if (requestInputStream.available() == 0) break; | |
} | |
String completeRequestContent = byteArrayOutputStream.toString(); | |
String[] requestBlocks = completeRequestContent.split(DOUBLEBREAK, 2); | |
String headerComponent = requestBlocks[0]; | |
String[] methodPathComponentsLookup = headerComponent.split(BREAK); | |
String methodPathComponent = methodPathComponentsLookup[0]; | |
String[] methodPathVersionComponents = methodPathComponent.split("\\s"); | |
String requestVerb = methodPathVersionComponents[REQUEST_METHOD]; | |
String requestPath = methodPathVersionComponents[REQUEST_PATH]; | |
String requestVersion = methodPathVersionComponents[REQUEST_VERSION]; | |
if (requestPath.equals(IGNORE_CHROME)) { | |
requestInputStream.close(); | |
clientOutput.flush(); | |
clientOutput.close(); | |
executors.execute(new NetworkRequestExecutor(serverSocket, executors)); | |
return; | |
} | |
ConcurrentMap<String, String> headers = new ConcurrentHashMap<>(); | |
String[] headerComponents = headerComponent.split(BREAK); | |
for (String headerLine : headerComponents) { | |
String[] headerLineComponents = headerLine.split(":"); | |
if (headerLineComponents.length == 2) { | |
String fieldKey = headerLineComponents[0].trim(); | |
String content = headerLineComponents[1].trim(); | |
headers.put(fieldKey.toLowerCase(), content); | |
} | |
} | |
clientOutput.write("HTTP/1.1 200 OK".getBytes()); | |
clientOutput.write(BREAK.getBytes()); | |
Integer bytesLength = "hi".length(); | |
String contentLengthBytes = "Content-Length:" + bytesLength; | |
clientOutput.write(contentLengthBytes.getBytes()); | |
clientOutput.write(BREAK.getBytes()); | |
clientOutput.write("FooServer: foo server".getBytes()); | |
clientOutput.write(BREAK.getBytes()); | |
clientOutput.write("Content-Type: text/html".getBytes()); | |
clientOutput.write(DOUBLEBREAK.getBytes()); | |
clientOutput.write("hi".getBytes()); | |
clientOutput.close(); | |
socketClient.close(); | |
executors.execute(new NetworkRequestExecutor(serverSocket, executors)); | |
} catch (IOException ex) { | |
ex.printStackTrace(); | |
} catch (InterruptedException ioException) { | |
ioException.printStackTrace(); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment