Ultra Fast Java NIO Webserver - Less than 350 lines

Discussion in 'Resources' started by md_5, Sep 16, 2012.

Thread Status:
Not open for further replies.
  1. Offline

    md_5

    Code:java
    1. package net.shadowraze.vote4diamondz;
    2.  
    3. import java.io.IOException;
    4. import java.net.InetSocketAddress;
    5. import java.nio.ByteBuffer;
    6. import java.nio.CharBuffer;
    7. import java.nio.channels.SelectionKey;
    8. import java.nio.channels.Selector;
    9. import java.nio.channels.ServerSocketChannel;
    10. import java.nio.channels.SocketChannel;
    11. import java.nio.charset.Charset;
    12. import java.nio.charset.CharsetEncoder;
    13. import java.util.Date;
    14. import java.util.HashMap;
    15. import java.util.Iterator;
    16. import java.util.LinkedHashMap;
    17. import java.util.Map;
    18. import java.util.StringTokenizer;
    19.  
    20. /**
    21.  * Simple Java non-blocking NIO webserver.
    22.  *
    23.  * @author md_5
    24.  */
    25. public class WebServer implements Runnable {
    26.  
    27. private Charset charset = Charset.forName("UTF-8");
    28. private CharsetEncoder encoder = charset.newEncoder();
    29. private Selector selector = Selector.open();
    30. private ServerSocketChannel server = ServerSocketChannel.open();
    31. private boolean isRunning = true;
    32. private boolean debug = true;
    33.  
    34. /**
    35.   * Create a new server and immediately binds it.
    36.   *
    37.   * @param address the address to bind on
    38.   * @throws IOException if there are any errors creating the server.
    39.   */
    40. protected WebServer(InetSocketAddress address) throws IOException {
    41. server.socket().bind(address);
    42. server.configureBlocking(false);
    43. server.register(selector, SelectionKey.OP_ACCEPT);
    44. }
    45.  
    46. /**
    47.   * Core run method. This is not a thread safe method, however it is non
    48.   * blocking. If an exception is encountered it will be thrown wrapped in a
    49.   * RuntimeException, and the server will automatically be {@link #shutDown}
    50.   */
    51. @Override
    52. public final void run() {
    53. if (isRunning) {
    54. try {
    55. selector.selectNow();
    56. Iterator<SelectionKey> i = selector.selectedKeys().iterator();
    57. while (i.hasNext()) {
    58. SelectionKey key = i.next();
    59. i.remove();
    60. if (!key.isValid()) {
    61. continue;
    62. }
    63. try {
    64. // get a new connection
    65. if (key.isAcceptable()) {
    66. // accept them
    67. SocketChannel client = server.accept();
    68. // non blocking please
    69. client.configureBlocking(false);
    70. // show out intentions
    71. client.register(selector, SelectionKey.OP_READ);
    72. // read from the connection
    73. } else if (key.isReadable()) {
    74. // get the client
    75. SocketChannel client = (SocketChannel) key.channel();
    76. // get the session
    77. HTTPSession session = (HTTPSession) key.attachment();
    78. // create it if it doesnt exist
    79. if (session == null) {
    80. session = new HTTPSession(client);
    81. key.attach(session);
    82. }
    83. // get more data
    84. session.readData();
    85. // decode the message
    86. String line;
    87. while ((line = session.readLine()) != null) {
    88. // check if we have got everything
    89. if (line.isEmpty()) {
    90. HTTPRequest request = new HTTPRequest(session.readLines.toString());
    91. session.sendResponse(handle(session, request));
    92. session.close();
    93. }
    94. }
    95. }
    96. } catch (Exception ex) {
    97. System.err.println("Error handling client: " + key.channel());
    98. if (debug) {
    99. ex.printStackTrace();
    100. } else {
    101. System.err.println(ex);
    102. System.err.println("\tat " + ex.getStackTrace()[0]);
    103. }
    104. if (key.attachment() instanceof HTTPSession) {
    105. ((HTTPSession) key.attachment()).close();
    106. }
    107. }
    108. }
    109. } catch (IOException ex) {
    110. // call it quits
    111. shutdown();
    112. // throw it as a runtime exception so that Bukkit can handle it
    113. throw new RuntimeException(ex);
    114. }
    115. }
    116. }
    117.  
    118. /**
    119.   * Handle a web request.
    120.   *
    121.   * @param session the entire http session
    122.   * @return the handled request
    123.   */
    124. protected HTTPResponse handle(HTTPSession session, HTTPRequest request) throws IOException {
    125. HTTPResponse response = new HTTPResponse();
    126. response.setContent("I liek cates".getBytes());
    127. return response;
    128. }
    129.  
    130. /**
    131.   * Shutdown this server, preventing it from handling any more requests.
    132.   */
    133. public final void shutdown() {
    134. isRunning = false;
    135. try {
    136. selector.close();
    137. server.close();
    138. } catch (IOException ex) {
    139. // do nothing, its game over
    140. }
    141. }
    142.  
    143. public final class HTTPSession {
    144.  
    145. private final SocketChannel channel;
    146. private final ByteBuffer buffer = ByteBuffer.allocate(2048);
    147. private final StringBuilder readLines = new StringBuilder();
    148. private int mark = 0;
    149.  
    150. public HTTPSession(SocketChannel channel) {
    151. this.channel = channel;
    152. }
    153.  
    154. /**
    155.   * Try to read a line.
    156.   */
    157. public String readLine() throws IOException {
    158. StringBuilder sb = new StringBuilder();
    159. int l = -1;
    160. while (buffer.hasRemaining()) {
    161. char c = (char) buffer.get();
    162. sb.append(c);
    163. if (c == '\n' && l == '\r') {
    164. // mark our position
    165. mark = buffer.position();
    166. // append to the total
    167. readLines.append(sb);
    168. // return with no line separators
    169. return sb.substring(0, sb.length() - 2);
    170. }
    171. l = c;
    172. }
    173. return null;
    174. }
    175.  
    176. /**
    177.   * Get more data from the stream.
    178.   */
    179. public void readData() throws IOException {
    180. buffer.limit(buffer.capacity());
    181. int read = channel.read(buffer);
    182. if (read == -1) {
    183. throw new IOException("End of stream");
    184. }
    185. buffer.flip();
    186. buffer.position(mark);
    187. }
    188.  
    189. private void writeLine(String line) throws IOException {
    190. channel.write(encoder.encode(CharBuffer.wrap(line + "\r\n")));
    191. }
    192.  
    193. public void sendResponse(HTTPResponse response) {
    194. response.addDefaultHeaders();
    195. try {
    196. writeLine(response.version + " " + response.responseCode + " " + response.responseReason);
    197. for (Map.Entry<String, String> header : response.headers.entrySet()) {
    198. writeLine(header.getKey() + ": " + header.getValue());
    199. }
    200. writeLine("");
    201. channel.write(ByteBuffer.wrap(response.content));
    202. } catch (IOException ex) {
    203. // slow silently
    204. }
    205. }
    206.  
    207. public void close() {
    208. try {
    209. channel.close();
    210. } catch (IOException ex) {
    211. }
    212. }
    213. }
    214.  
    215. public static class HTTPRequest {
    216.  
    217. private final String raw;
    218. private String method;
    219. private String location;
    220. private String version;
    221. private Map<String, String> headers = new HashMap<String, String>();
    222.  
    223. public HTTPRequest(String raw) {
    224. this.raw = raw;
    225. parse();
    226. }
    227.  
    228. private void parse() {
    229. // parse the first line
    230. StringTokenizer tokenizer = new StringTokenizer(raw);
    231. method = tokenizer.nextToken().toUpperCase();
    232. location = tokenizer.nextToken();
    233. version = tokenizer.nextToken();
    234. // parse the headers
    235. String[] lines = raw.split("\r\n");
    236. for (int i = 1; i < lines.length; i++) {
    237. String[] keyVal = lines[i].split(":", 2);
    238. headers.put(keyVal[0], keyVal[1]);
    239. }
    240. }
    241.  
    242. public String getMethod() {
    243. return method;
    244. }
    245.  
    246. public String getLocation() {
    247. return location;
    248. }
    249.  
    250. public String getHead(String key) {
    251. return headers.get(key);
    252. }
    253. }
    254.  
    255. public static class HTTPResponse {
    256.  
    257. private String version = "HTTP/1.1";
    258. private int responseCode = 200;
    259. private String responseReason = "OK";
    260. private Map<String, String> headers = new LinkedHashMap<String, String>();
    261. private byte[] content;
    262.  
    263. private void addDefaultHeaders() {
    264. headers.put("Date", new Date().toString());
    265. headers.put("Server", "Java NIO Webserver by md_5");
    266. headers.put("Connection", "close");
    267. headers.put("Content-Length", Integer.toString(content.length));
    268. }
    269.  
    270. public int getResponseCode() {
    271. return responseCode;
    272. }
    273.  
    274. public String getResponseReason() {
    275. return responseReason;
    276. }
    277.  
    278. public String getHeader(String header) {
    279. return headers.get(header);
    280. }
    281.  
    282. public byte[] getContent() {
    283. return content;
    284. }
    285.  
    286. public void setResponseCode(int responseCode) {
    287. this.responseCode = responseCode;
    288. }
    289.  
    290. public void setResponseReason(String responseReason) {
    291. this.responseReason = responseReason;
    292. }
    293.  
    294. public void setContent(byte[] content) {
    295. this.content = content;
    296. }
    297.  
    298. public void setHeader(String key, String value) {
    299. headers.put(key, value);
    300. }
    301. }
    302.  
    303. public static void main(String[] args) throws Exception {
    304. WebServer server = new WebServer(new InetSocketAddress(5555));
    305. while (true) {
    306. server.run();
    307. Thread.sleep(100);
    308. }
    309. }
    310. }
    311. [/i]

    This server is designed to be executed as a syncDelayedRepeatingTask every so often. You can call it as often or not as you like. It will return straight away and being sync you can call Bukkit methods in your handler.


    Old Versions: https://gist.github.com/3731609


    Finally got NIO readline working and made it look like a server. This is the new Jibble!

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 28, 2016
  2. Is there a live version up and running? I would love to test the effects of network I/O (like blocking the connection). ^^
    Also: Are you planning to implement more state codes than "200 OK" and am I allowed to use this with BukkitHTTPD (even if the currently core is fine it would be nice to be synced instead of using locks/buffers/caches between the web server and bukkit).

    BTW: Feel free to have a look at BukkitHTTPD (sources included in the jar) which uses com.sun.net.httpserver
     
  3. Offline

    simontifo

    Hi,

    actually I devellop a web application with Java EE, Spring3 JSF2, hibernate and maven in eclipse, can I use your server during the development to accelere it, if yes, please explain me more

    thank you in davance
     
Thread Status:
Not open for further replies.

Share This Page