-
-
Save sarnobat/52d62835b9265091f0e3 to your computer and use it in GitHub Desktop.
Java NIO Webservers
This file contains hidden or 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
import java.io.IOException; | |
import java.net.InetSocketAddress; | |
import java.nio.ByteBuffer; | |
import java.nio.CharBuffer; | |
import java.nio.channels.SelectionKey; | |
import java.nio.channels.Selector; | |
import java.nio.channels.ServerSocketChannel; | |
import java.nio.channels.SocketChannel; | |
import java.nio.charset.Charset; | |
import java.nio.charset.CharsetEncoder; | |
import java.util.Date; | |
import java.util.Iterator; | |
import java.util.LinkedHashMap; | |
import java.util.Map; | |
import java.util.StringTokenizer; | |
/** Simple Java non-blocking NIO webserver. */ | |
public class NioFileServer implements Runnable { | |
private static final String RESPONSE_STRING = "I liek cates"; | |
private static final CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder(); | |
private final Selector selector = Selector.open(); | |
private final ServerSocketChannel server = ServerSocketChannel.open(); | |
// Start listening on port | |
protected NioWebServer(InetSocketAddress address) throws IOException { | |
server.socket().bind(address); | |
server.configureBlocking(false); | |
server.register(selector, SelectionKey.OP_ACCEPT); | |
} | |
private static void startServer(NioWebServer server) throws InterruptedException { | |
while (true) { | |
server.run(); | |
Thread.sleep(100); | |
} | |
} | |
@Override | |
public final void run() { | |
try { | |
respondToQueuedRequests(selector, server); | |
} catch (IOException ex) { | |
throw new RuntimeException(ex); | |
} | |
} | |
private void respondToQueuedRequests(Selector selector, ServerSocketChannel serverSocket) throws IOException { | |
selector.selectNow(); | |
Iterator<SelectionKey> keys = selector.selectedKeys().iterator(); | |
while (keys.hasNext()) { | |
SelectionKey key = keys.next(); | |
// TODO: Our server endpoint must add a key to the selector whenever | |
// we get a request to load an image | |
keys.remove(); | |
if (!key.isValid()) { | |
continue; | |
} | |
try { | |
if (key.isAcceptable()) { | |
// New Client encountered | |
serverSocket.accept().configureBlocking(false) | |
.register(selector, SelectionKey.OP_READ); | |
} else if (key.isReadable()) { | |
// Additional data for existing client encountered | |
SocketChannel clientSocket = (SocketChannel) key.channel(); | |
ByteBuffer buffer = ByteBuffer.allocate(2048); | |
String requestedFile2 = getRequstedFile(key, clientSocket, buffer); | |
sendResponseAndCloseChannel2(clientSocket, requestedFile2); | |
} | |
} catch (Exception ex) { | |
ex.printStackTrace(); | |
if (key.attachment() instanceof HTTPSession) { | |
close((SocketChannel) key.channel()); | |
} | |
} | |
} | |
} | |
private String getRequstedFile(SelectionKey key, SocketChannel clientSocket, ByteBuffer buffer) | |
throws IOException { | |
HTTPSession httpSession = getOrCreateSession(key, buffer); | |
httpSession.collectClientDataInBuffer(clientSocket, buffer); | |
String requestedFile2 = getRequestedFile2(buffer, httpSession); | |
return requestedFile2; | |
} | |
private void sendResponseAndCloseChannel2(SocketChannel clientSocket, String filePathRequested) { | |
// Finished reading client's http request, now parse it | |
byte[] responseContent = (RESPONSE_STRING + ": " + filePathRequested).getBytes(); | |
sendResponseAndCloseChannel(addDefaultHeaders(responseContent), responseContent, | |
clientSocket); | |
} | |
void close(SocketChannel clientSocket) { | |
try { | |
clientSocket.close(); | |
} catch (IOException ex) { | |
} | |
} | |
void sendResponseAndCloseChannel(Map<String, String> headers, byte[] responseContent, | |
SocketChannel clientSocket) { | |
try { | |
writeLineTo("HTTP/1.1" + " " + 200 + " " + "OK", clientSocket); | |
for (Map.Entry<String, String> header : headers.entrySet()) { | |
writeLineTo(header.getKey() + ": " + header.getValue(), clientSocket); | |
} | |
writeLineTo("", clientSocket); | |
clientSocket.write(ByteBuffer.wrap(responseContent)); | |
} catch (IOException ex) { | |
// slow silently | |
System.err.println("NioWebServer.HTTPSession.sendResponse() - " + ex); | |
} | |
finally { | |
close(clientSocket); | |
} | |
} | |
private static void writeLineTo(String line, SocketChannel clientChannel) throws IOException { | |
clientChannel.write(encoder.encode(CharBuffer.wrap(line + "\r\n"))); | |
} | |
private static Map<String, String> addDefaultHeaders(byte[] responseContent) { | |
Map<String, String> headers = new LinkedHashMap<String, String>(); | |
headers.put("Date", new Date().toString()); | |
headers.put("Server", "Java NIO Webserver by md_5"); | |
headers.put("Connection", "close"); | |
headers.put("Content-Length", Integer.toString(responseContent.length)); | |
return headers; | |
} | |
private String getRequestedFile2(ByteBuffer buffer, HTTPSession httpSession) throws IOException { | |
String requestString2 = getRequestedFile(buffer, httpSession); | |
String parseStatic = parseStatic(requestString2); | |
return parseStatic; | |
} | |
private static String getRequestedFile(ByteBuffer buffer, HTTPSession httpSession) throws IOException { | |
String aLine; | |
StringBuffer requestString = new StringBuffer(); | |
while ((aLine = httpSession.readLineFrom(buffer)) != null) { | |
requestString.append(aLine); | |
if (aLine.isEmpty()) { | |
break; | |
} | |
} | |
return requestString | |
.toString(); | |
} | |
private static String parseStatic(String headersFromClient) { | |
StringTokenizer tokenizer = new StringTokenizer(headersFromClient); | |
// String method = | |
tokenizer.nextToken().toUpperCase(); | |
String location = tokenizer.nextToken(); | |
// String version = | |
tokenizer.nextToken(); | |
return location; | |
} | |
private HTTPSession getOrCreateSession(SelectionKey key, ByteBuffer buffer) { | |
HTTPSession httpSession = (HTTPSession) key.attachment(); | |
// create it if it doesnt exist | |
if (httpSession == null) { | |
httpSession = new HTTPSession(); | |
key.attach(httpSession); | |
} | |
return httpSession; | |
} | |
public final class HTTPSession { | |
private int mark = 0; | |
String readLineFrom(ByteBuffer buffer) throws IOException { | |
StringBuilder line = new StringBuilder(); | |
int lastChar = -1; | |
while (buffer.hasRemaining()) { | |
char currentChar = (char) buffer.get(); | |
line.append(currentChar); | |
if (currentChar == '\n' && lastChar == '\r') { | |
// mark our position | |
mark = buffer.position(); | |
// append to the total | |
// return with no line separators | |
return line.substring(0, line.length() - 2); | |
} | |
lastChar = currentChar; | |
} | |
return null; | |
} | |
/** | |
* Get more data from the stream. | |
*/ | |
ByteBuffer collectClientDataInBuffer(SocketChannel socketChannel, ByteBuffer buffer) | |
throws IOException { | |
buffer.limit(buffer.capacity()); | |
int read = socketChannel.read(buffer); | |
if (read == -1) { | |
throw new IOException("End of stream"); | |
} | |
buffer.flip(); | |
buffer.position(mark); | |
return buffer; | |
} | |
} | |
public static void main(String[] args) throws Exception { | |
NioWebServer server = new NioWebServer(new InetSocketAddress(5555)); | |
startServer(server); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment