├── LICENSE ├── README.md └── src ├── main └── java │ └── com │ └── jenkov │ └── nioserver │ ├── IMessageProcessor.java │ ├── IMessageReader.java │ ├── IMessageReaderFactory.java │ ├── Message.java │ ├── MessageBuffer.java │ ├── MessageWriter.java │ ├── QueueIntFlip.java │ ├── Server.java │ ├── Socket.java │ ├── SocketAccepter.java │ ├── SocketProcessor.java │ ├── WriteProxy.java │ ├── example │ └── Main.java │ └── http │ ├── HttpHeaders.java │ ├── HttpMessageReader.java │ ├── HttpMessageReaderFactory.java │ └── HttpUtil.java └── test └── java └── com.jenkov.nioserver ├── MessageBufferTest.java ├── MessageTest.java ├── SelectorTest.java └── http └── HttpUtilTest.java /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java NIO Server 2 | A Java NIO Server using non-blocking IO all the way through. 3 | 4 | Note: 5 | This is NOT intended for reuse "as is". This is an example of how you could design a Java NIO server yourself. 6 | The design is explained in this tutorial: 7 | 8 | [Java NIO Non-blocking Server](http://tutorials.jenkov.com/java-nio/non-blocking-server.html) 9 | 10 | 11 | Because this is an example app - this project will NOT accept feature requests. If there are any obvious bugs in the code, I can fix those, but apart from that, the code has to stay as it is. 12 | 13 | By the way, I am working a real, usable non-blocking client-server API called "Nanosai Net Ops" - based on the designs of this project. 14 | That project contains both a client and a server, and you can use both client and server using both blocking and non-blocking methods, 15 | and switch between the two modes as you see fit. You can find Net Ops here: 16 | 17 | [https://github.com/nanosai/net-ops-java](https://github.com/nanosai/net-ops-java) 18 | 19 | I have been able to "echo" around 200.000 messages per second with Net Ops (in early versions), coming from 3 clients running on the same machine, against a single-threaded server - on a Quad core CPU. 20 | 21 | Net Ops has several smaller improvements in the design and functionality over the server you see in this project, so if you really want 22 | to study a more robust non-blocking IO client / server design, look at Net Ops too. 23 | -------------------------------------------------------------------------------- /src/main/java/com/jenkov/nioserver/IMessageProcessor.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver; 2 | 3 | /** 4 | * Created by jjenkov on 16-10-2015. 5 | */ 6 | public interface IMessageProcessor { 7 | 8 | public void process(Message message, WriteProxy writeProxy); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/jenkov/nioserver/IMessageReader.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver; 2 | 3 | import java.io.IOException; 4 | import java.nio.ByteBuffer; 5 | import java.util.List; 6 | 7 | /** 8 | * Created by jjenkov on 16-10-2015. 9 | */ 10 | public interface IMessageReader { 11 | 12 | public void init(MessageBuffer readMessageBuffer); 13 | 14 | public void read(Socket socket, ByteBuffer byteBuffer) throws IOException; 15 | 16 | public List getMessages(); 17 | 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/jenkov/nioserver/IMessageReaderFactory.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver; 2 | 3 | /** 4 | * Created by jjenkov on 16-10-2015. 5 | */ 6 | public interface IMessageReaderFactory { 7 | 8 | public IMessageReader createMessageReader(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/jenkov/nioserver/Message.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | /** 6 | * Created by jjenkov on 16-10-2015. 7 | */ 8 | public class Message { 9 | 10 | private MessageBuffer messageBuffer = null; 11 | 12 | public long socketId = 0; // the id of source socket or destination socket, depending on whether is going in or out. 13 | 14 | public byte[] sharedArray = null; 15 | public int offset = 0; //offset into sharedArray where this message data starts. 16 | public int capacity = 0; //the size of the section in the sharedArray allocated to this message. 17 | public int length = 0; //the number of bytes used of the allocated section. 18 | 19 | public Object metaData = null; 20 | 21 | public Message(MessageBuffer messageBuffer) { 22 | this.messageBuffer = messageBuffer; 23 | } 24 | 25 | /** 26 | * Writes data from the ByteBuffer into this message - meaning into the buffer backing this message. 27 | * 28 | * @param byteBuffer The ByteBuffer containing the message data to write. 29 | * @return 30 | */ 31 | public int writeToMessage(ByteBuffer byteBuffer){ 32 | int remaining = byteBuffer.remaining(); 33 | 34 | while(this.length + remaining > capacity){ 35 | if(!this.messageBuffer.expandMessage(this)) { 36 | return -1; 37 | } 38 | } 39 | 40 | int bytesToCopy = Math.min(remaining, this.capacity - this.length); 41 | byteBuffer.get(this.sharedArray, this.offset + this.length, bytesToCopy); 42 | this.length += bytesToCopy; 43 | 44 | return bytesToCopy; 45 | } 46 | 47 | 48 | 49 | 50 | /** 51 | * Writes data from the byte array into this message - meaning into the buffer backing this message. 52 | * 53 | * @param byteArray The byte array containing the message data to write. 54 | * @return 55 | */ 56 | public int writeToMessage(byte[] byteArray){ 57 | return writeToMessage(byteArray, 0, byteArray.length); 58 | } 59 | 60 | 61 | /** 62 | * Writes data from the byte array into this message - meaning into the buffer backing this message. 63 | * 64 | * @param byteArray The byte array containing the message data to write. 65 | * @return 66 | */ 67 | public int writeToMessage(byte[] byteArray, int offset, int length){ 68 | int remaining = length; 69 | 70 | while(this.length + remaining > capacity){ 71 | if(!this.messageBuffer.expandMessage(this)) { 72 | return -1; 73 | } 74 | } 75 | 76 | int bytesToCopy = Math.min(remaining, this.capacity - this.length); 77 | System.arraycopy(byteArray, offset, this.sharedArray, this.offset + this.length, bytesToCopy); 78 | this.length += bytesToCopy; 79 | return bytesToCopy; 80 | } 81 | 82 | 83 | 84 | 85 | /** 86 | * In case the buffer backing the nextMessage contains more than one HTTP message, move all data after the first 87 | * message to a new Message object. 88 | * 89 | * @param message The message containing the partial message (after the first message). 90 | * @param endIndex The end index of the first message in the buffer of the message given as parameter. 91 | */ 92 | public void writePartialMessageToMessage(Message message, int endIndex){ 93 | int startIndexOfPartialMessage = message.offset + endIndex; 94 | int lengthOfPartialMessage = (message.offset + message.length) - endIndex; 95 | 96 | System.arraycopy(message.sharedArray, startIndexOfPartialMessage, this.sharedArray, this.offset, lengthOfPartialMessage); 97 | } 98 | 99 | public int writeToByteBuffer(ByteBuffer byteBuffer){ 100 | return 0; 101 | } 102 | 103 | 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/jenkov/nioserver/MessageBuffer.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver; 2 | 3 | /** 4 | * A shared buffer which can contain many messages inside. A message gets a section of the buffer to use. If the 5 | * message outgrows the section in size, the message requests a larger section and the message is copied to that 6 | * larger section. The smaller section is then freed again. 7 | * 8 | * 9 | * Created by jjenkov on 18-10-2015. 10 | */ 11 | public class MessageBuffer { 12 | 13 | public static int KB = 1024; 14 | public static int MB = 1024 * KB; 15 | 16 | private static final int CAPACITY_SMALL = 4 * KB; 17 | private static final int CAPACITY_MEDIUM = 128 * KB; 18 | private static final int CAPACITY_LARGE = 1024 * KB; 19 | 20 | //package scope (default) - so they can be accessed from unit tests. 21 | byte[] smallMessageBuffer = new byte[1024 * 4 * KB]; //1024 x 4KB messages = 4MB. 22 | byte[] mediumMessageBuffer = new byte[128 * 128 * KB]; // 128 x 128KB messages = 16MB. 23 | byte[] largeMessageBuffer = new byte[16 * 1 * MB]; // 16 * 1MB messages = 16MB. 24 | 25 | QueueIntFlip smallMessageBufferFreeBlocks = new QueueIntFlip(1024); // 1024 free sections 26 | QueueIntFlip mediumMessageBufferFreeBlocks = new QueueIntFlip(128); // 128 free sections 27 | QueueIntFlip largeMessageBufferFreeBlocks = new QueueIntFlip(16); // 16 free sections 28 | 29 | //todo make all message buffer capacities and block sizes configurable 30 | //todo calculate free block queue sizes based on capacity and block size of buffers. 31 | 32 | public MessageBuffer() { 33 | //add all free sections to all free section queues. 34 | for(int i=0; i writeQueue = new ArrayList<>(); 14 | private Message messageInProgress = null; 15 | private int bytesWritten = 0; 16 | 17 | public MessageWriter() { 18 | } 19 | 20 | public void enqueue(Message message) { 21 | if(this.messageInProgress == null){ 22 | this.messageInProgress = message; 23 | } else { 24 | this.writeQueue.add(message); 25 | } 26 | } 27 | 28 | public void write(Socket socket, ByteBuffer byteBuffer) throws IOException { 29 | byteBuffer.put(this.messageInProgress.sharedArray, this.messageInProgress.offset + this.bytesWritten, this.messageInProgress.length - this.bytesWritten); 30 | byteBuffer.flip(); 31 | 32 | this.bytesWritten += socket.write(byteBuffer); 33 | byteBuffer.clear(); 34 | 35 | if(bytesWritten >= this.messageInProgress.length){ 36 | if(this.writeQueue.size() > 0){ 37 | this.messageInProgress = this.writeQueue.remove(0); 38 | } else { 39 | this.messageInProgress = null; 40 | //todo unregister from selector 41 | } 42 | } 43 | } 44 | 45 | public boolean isEmpty() { 46 | return this.writeQueue.isEmpty() && this.messageInProgress == null; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/jenkov/nioserver/QueueIntFlip.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver; 2 | 3 | /** 4 | * Same as QueueFillCount, except that QueueFlip uses a flip flag to keep track of when the internal writePos has 5 | * "overflowed" (meaning it goes back to 0). Other than that, the two implementations are very similar in functionality. 6 | * 7 | * One additional difference is that QueueFlip has an available() method, where this is a public variable in 8 | * QueueFillCount. 9 | * 10 | * Created by jjenkov on 18-09-2015. 11 | */ 12 | public class QueueIntFlip { 13 | 14 | public int[] elements = null; 15 | 16 | public int capacity = 0; 17 | public int writePos = 0; 18 | public int readPos = 0; 19 | public boolean flipped = false; 20 | 21 | public QueueIntFlip(int capacity) { 22 | this.capacity = capacity; 23 | this.elements = new int[capacity]; //todo get from TypeAllocator ? 24 | } 25 | 26 | public void reset() { 27 | this.writePos = 0; 28 | this.readPos = 0; 29 | this.flipped = false; 30 | } 31 | 32 | public int available() { 33 | if(!flipped){ 34 | return writePos - readPos; 35 | } 36 | return capacity - readPos + writePos; 37 | } 38 | 39 | public int remainingCapacity() { 40 | if(!flipped){ 41 | return capacity - writePos; 42 | } 43 | return readPos - writePos; 44 | } 45 | 46 | public boolean put(int element){ 47 | if(!flipped){ 48 | if(writePos == capacity){ 49 | writePos = 0; 50 | flipped = true; 51 | 52 | if(writePos < readPos){ 53 | elements[writePos++] = element; 54 | return true; 55 | } else { 56 | return false; 57 | } 58 | } else { 59 | elements[writePos++] = element; 60 | return true; 61 | } 62 | } else { 63 | if(writePos < readPos ){ 64 | elements[writePos++] = element; 65 | return true; 66 | } else { 67 | return false; 68 | } 69 | } 70 | } 71 | 72 | public int put(int[] newElements, int length){ 73 | int newElementsReadPos = 0; 74 | if(!flipped){ 75 | //readPos lower than writePos - free sections are: 76 | //1) from writePos to capacity 77 | //2) from 0 to readPos 78 | 79 | if(length <= capacity - writePos){ 80 | //new elements fit into top of elements array - copy directly 81 | for(; newElementsReadPos < length; newElementsReadPos++){ 82 | this.elements[this.writePos++] = newElements[newElementsReadPos]; 83 | } 84 | 85 | return newElementsReadPos; 86 | } else { 87 | //new elements must be divided between top and bottom of elements array 88 | 89 | //writing to top 90 | for(;this.writePos < capacity; this.writePos++){ 91 | this.elements[this.writePos] = newElements[newElementsReadPos++]; 92 | } 93 | 94 | //writing to bottom 95 | this.writePos = 0; 96 | this.flipped = true; 97 | int endPos = Math.min(this.readPos, length - newElementsReadPos); 98 | for(; this.writePos < endPos; this.writePos++){ 99 | this.elements[writePos] = newElements[newElementsReadPos++]; 100 | } 101 | 102 | 103 | return newElementsReadPos; 104 | } 105 | 106 | } else { 107 | //readPos higher than writePos - free sections are: 108 | //1) from writePos to readPos 109 | 110 | int endPos = Math.min(this.readPos, this.writePos + length); 111 | 112 | for(; this.writePos < endPos; this.writePos++){ 113 | this.elements[this.writePos] = newElements[newElementsReadPos++]; 114 | } 115 | 116 | return newElementsReadPos; 117 | } 118 | } 119 | 120 | 121 | public int take() { 122 | if(!flipped){ 123 | if(readPos < writePos){ 124 | return elements[readPos++]; 125 | } else { 126 | return -1; 127 | } 128 | } else { 129 | if(readPos == capacity){ 130 | readPos = 0; 131 | flipped = false; 132 | 133 | if(readPos < writePos){ 134 | return elements[readPos++]; 135 | } else { 136 | return -1; 137 | } 138 | } else { 139 | return elements[readPos++]; 140 | } 141 | } 142 | } 143 | 144 | public int take(int[] into, int length){ 145 | int intoWritePos = 0; 146 | if(!flipped){ 147 | //writePos higher than readPos - available section is writePos - readPos 148 | 149 | int endPos = Math.min(this.writePos, this.readPos + length); 150 | for(; this.readPos < endPos; this.readPos++){ 151 | into[intoWritePos++] = this.elements[this.readPos]; 152 | } 153 | return intoWritePos; 154 | } else { 155 | //readPos higher than writePos - available sections are top + bottom of elements array 156 | 157 | if(length <= capacity - readPos){ 158 | //length is lower than the elements available at the top of the elements array - copy directly 159 | for(; intoWritePos < length; intoWritePos++){ 160 | into[intoWritePos] = this.elements[this.readPos++]; 161 | } 162 | 163 | return intoWritePos; 164 | } else { 165 | //length is higher than elements available at the top of the elements array 166 | //split copy into a copy from both top and bottom of elements array. 167 | 168 | //copy from top 169 | for(; this.readPos < capacity; this.readPos++){ 170 | into[intoWritePos++] = this.elements[this.readPos]; 171 | } 172 | 173 | //copy from bottom 174 | this.readPos = 0; 175 | this.flipped = false; 176 | int endPos = Math.min(this.writePos, length - intoWritePos); 177 | for(; this.readPos < endPos; this.readPos++){ 178 | into[intoWritePos++] = this.elements[this.readPos]; 179 | } 180 | 181 | return intoWritePos; 182 | } 183 | } 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /src/main/java/com/jenkov/nioserver/Server.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver; 2 | 3 | import java.io.IOException; 4 | import java.util.Queue; 5 | import java.util.concurrent.ArrayBlockingQueue; 6 | import java.util.concurrent.BlockingQueue; 7 | 8 | /** 9 | * Created by jjenkov on 24-10-2015. 10 | */ 11 | public class Server { 12 | 13 | private SocketAccepter socketAccepter = null; 14 | private SocketProcessor socketProcessor = null; 15 | 16 | private int tcpPort = 0; 17 | private IMessageReaderFactory messageReaderFactory = null; 18 | private IMessageProcessor messageProcessor = null; 19 | 20 | public Server(int tcpPort, IMessageReaderFactory messageReaderFactory, IMessageProcessor messageProcessor) { 21 | this.tcpPort = tcpPort; 22 | this.messageReaderFactory = messageReaderFactory; 23 | this.messageProcessor = messageProcessor; 24 | } 25 | 26 | public void start() throws IOException { 27 | 28 | Queue socketQueue = new ArrayBlockingQueue(1024); //move 1024 to ServerConfig 29 | 30 | this.socketAccepter = new SocketAccepter(tcpPort, socketQueue); 31 | 32 | 33 | MessageBuffer readBuffer = new MessageBuffer(); 34 | MessageBuffer writeBuffer = new MessageBuffer(); 35 | 36 | this.socketProcessor = new SocketProcessor(socketQueue, readBuffer, writeBuffer, this.messageReaderFactory, this.messageProcessor); 37 | 38 | Thread accepterThread = new Thread(this.socketAccepter); 39 | Thread processorThread = new Thread(this.socketProcessor); 40 | 41 | accepterThread.start(); 42 | processorThread.start(); 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/jenkov/nioserver/Socket.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver; 2 | 3 | import java.io.IOException; 4 | import java.nio.ByteBuffer; 5 | import java.nio.channels.SocketChannel; 6 | 7 | /** 8 | * Created by jjenkov on 16-10-2015. 9 | */ 10 | public class Socket { 11 | 12 | public long socketId; 13 | 14 | public SocketChannel socketChannel = null; 15 | public IMessageReader messageReader = null; 16 | public MessageWriter messageWriter = null; 17 | 18 | public boolean endOfStreamReached = false; 19 | 20 | public Socket() { 21 | } 22 | 23 | public Socket(SocketChannel socketChannel) { 24 | this.socketChannel = socketChannel; 25 | } 26 | 27 | public int read(ByteBuffer byteBuffer) throws IOException { 28 | int bytesRead = this.socketChannel.read(byteBuffer); 29 | int totalBytesRead = bytesRead; 30 | 31 | while(bytesRead > 0){ 32 | bytesRead = this.socketChannel.read(byteBuffer); 33 | totalBytesRead += bytesRead; 34 | } 35 | if(bytesRead == -1){ 36 | this.endOfStreamReached = true; 37 | } 38 | 39 | return totalBytesRead; 40 | } 41 | 42 | public int write(ByteBuffer byteBuffer) throws IOException{ 43 | int bytesWritten = this.socketChannel.write(byteBuffer); 44 | int totalBytesWritten = bytesWritten; 45 | 46 | while(bytesWritten > 0 && byteBuffer.hasRemaining()){ 47 | bytesWritten = this.socketChannel.write(byteBuffer); 48 | totalBytesWritten += bytesWritten; 49 | } 50 | 51 | return totalBytesWritten; 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/jenkov/nioserver/SocketAccepter.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver; 2 | 3 | import java.io.IOException; 4 | import java.net.InetSocketAddress; 5 | import java.net.ServerSocket; 6 | import java.nio.channels.ServerSocketChannel; 7 | import java.nio.channels.SocketChannel; 8 | import java.util.Queue; 9 | 10 | /** 11 | * Created by jjenkov on 19-10-2015. 12 | */ 13 | public class SocketAccepter implements Runnable{ 14 | 15 | private int tcpPort = 0; 16 | private ServerSocketChannel serverSocket = null; 17 | 18 | private Queue socketQueue = null; 19 | 20 | public SocketAccepter(int tcpPort, Queue socketQueue) { 21 | this.tcpPort = tcpPort; 22 | this.socketQueue = socketQueue; 23 | } 24 | 25 | 26 | 27 | public void run() { 28 | try{ 29 | this.serverSocket = ServerSocketChannel.open(); 30 | this.serverSocket.bind(new InetSocketAddress(tcpPort)); 31 | } catch(IOException e){ 32 | e.printStackTrace(); 33 | return; 34 | } 35 | 36 | 37 | while(true){ 38 | try{ 39 | SocketChannel socketChannel = this.serverSocket.accept(); 40 | 41 | System.out.println("Socket accepted: " + socketChannel); 42 | 43 | //todo check if the queue can even accept more sockets. 44 | this.socketQueue.add(new Socket(socketChannel)); 45 | 46 | } catch(IOException e){ 47 | e.printStackTrace(); 48 | } 49 | 50 | } 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/jenkov/nioserver/SocketProcessor.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver; 2 | 3 | import java.io.IOException; 4 | import java.nio.ByteBuffer; 5 | import java.nio.channels.ClosedChannelException; 6 | import java.nio.channels.SelectionKey; 7 | import java.nio.channels.Selector; 8 | import java.util.*; 9 | 10 | /** 11 | * Created by jjenkov on 16-10-2015. 12 | */ 13 | public class SocketProcessor implements Runnable { 14 | 15 | private Queue inboundSocketQueue = null; 16 | 17 | private MessageBuffer readMessageBuffer = null; //todo Not used now - but perhaps will be later - to check for space in the buffer before reading from sockets 18 | private MessageBuffer writeMessageBuffer = null; //todo Not used now - but perhaps will be later - to check for space in the buffer before reading from sockets (space for more to write?) 19 | 20 | private IMessageReaderFactory messageReaderFactory = null; 21 | 22 | private Queue outboundMessageQueue = new LinkedList<>(); //todo use a better / faster queue. 23 | 24 | private Map socketMap = new HashMap<>(); 25 | 26 | private ByteBuffer readByteBuffer = ByteBuffer.allocate(1024 * 1024); 27 | private ByteBuffer writeByteBuffer = ByteBuffer.allocate(1024 * 1024); 28 | private Selector readSelector = null; 29 | private Selector writeSelector = null; 30 | 31 | private IMessageProcessor messageProcessor = null; 32 | private WriteProxy writeProxy = null; 33 | 34 | private long nextSocketId = 16 * 1024; //start incoming socket ids from 16K - reserve bottom ids for pre-defined sockets (servers). 35 | 36 | private Set emptyToNonEmptySockets = new HashSet<>(); 37 | private Set nonEmptyToEmptySockets = new HashSet<>(); 38 | 39 | 40 | public SocketProcessor(Queue inboundSocketQueue, MessageBuffer readMessageBuffer, MessageBuffer writeMessageBuffer, IMessageReaderFactory messageReaderFactory, IMessageProcessor messageProcessor) throws IOException { 41 | this.inboundSocketQueue = inboundSocketQueue; 42 | 43 | this.readMessageBuffer = readMessageBuffer; 44 | this.writeMessageBuffer = writeMessageBuffer; 45 | this.writeProxy = new WriteProxy(writeMessageBuffer, this.outboundMessageQueue); 46 | 47 | this.messageReaderFactory = messageReaderFactory; 48 | 49 | this.messageProcessor = messageProcessor; 50 | 51 | this.readSelector = Selector.open(); 52 | this.writeSelector = Selector.open(); 53 | } 54 | 55 | public void run() { 56 | while(true){ 57 | try{ 58 | executeCycle(); 59 | } catch(IOException e){ 60 | e.printStackTrace(); 61 | } 62 | 63 | try { 64 | Thread.sleep(100); 65 | } catch (InterruptedException e) { 66 | e.printStackTrace(); 67 | } 68 | } 69 | } 70 | 71 | 72 | public void executeCycle() throws IOException { 73 | takeNewSockets(); 74 | readFromSockets(); 75 | writeToSockets(); 76 | } 77 | 78 | 79 | public void takeNewSockets() throws IOException { 80 | Socket newSocket = this.inboundSocketQueue.poll(); 81 | 82 | while(newSocket != null){ 83 | newSocket.socketId = this.nextSocketId++; 84 | newSocket.socketChannel.configureBlocking(false); 85 | 86 | newSocket.messageReader = this.messageReaderFactory.createMessageReader(); 87 | newSocket.messageReader.init(this.readMessageBuffer); 88 | 89 | newSocket.messageWriter = new MessageWriter(); 90 | 91 | this.socketMap.put(newSocket.socketId, newSocket); 92 | 93 | SelectionKey key = newSocket.socketChannel.register(this.readSelector, SelectionKey.OP_READ); 94 | key.attach(newSocket); 95 | 96 | newSocket = this.inboundSocketQueue.poll(); 97 | } 98 | } 99 | 100 | 101 | public void readFromSockets() throws IOException { 102 | int readReady = this.readSelector.selectNow(); 103 | 104 | if(readReady > 0){ 105 | Set selectedKeys = this.readSelector.selectedKeys(); 106 | Iterator keyIterator = selectedKeys.iterator(); 107 | 108 | while(keyIterator.hasNext()) { 109 | SelectionKey key = keyIterator.next(); 110 | 111 | readFromSocket(key); 112 | 113 | keyIterator.remove(); 114 | } 115 | selectedKeys.clear(); 116 | } 117 | } 118 | 119 | private void readFromSocket(SelectionKey key) throws IOException { 120 | Socket socket = (Socket) key.attachment(); 121 | socket.messageReader.read(socket, this.readByteBuffer); 122 | 123 | List fullMessages = socket.messageReader.getMessages(); 124 | if(fullMessages.size() > 0){ 125 | for(Message message : fullMessages){ 126 | message.socketId = socket.socketId; 127 | this.messageProcessor.process(message, this.writeProxy); //the message processor will eventually push outgoing messages into an IMessageWriter for this socket. 128 | } 129 | fullMessages.clear(); 130 | } 131 | 132 | if(socket.endOfStreamReached){ 133 | System.out.println("Socket closed: " + socket.socketId); 134 | this.socketMap.remove(socket.socketId); 135 | key.attach(null); 136 | key.cancel(); 137 | key.channel().close(); 138 | } 139 | } 140 | 141 | 142 | public void writeToSockets() throws IOException { 143 | 144 | // Take all new messages from outboundMessageQueue 145 | takeNewOutboundMessages(); 146 | 147 | // Cancel all sockets which have no more data to write. 148 | cancelEmptySockets(); 149 | 150 | // Register all sockets that *have* data and which are not yet registered. 151 | registerNonEmptySockets(); 152 | 153 | // Select from the Selector. 154 | int writeReady = this.writeSelector.selectNow(); 155 | 156 | if(writeReady > 0){ 157 | Set selectionKeys = this.writeSelector.selectedKeys(); 158 | Iterator keyIterator = selectionKeys.iterator(); 159 | 160 | while(keyIterator.hasNext()){ 161 | SelectionKey key = keyIterator.next(); 162 | 163 | Socket socket = (Socket) key.attachment(); 164 | 165 | socket.messageWriter.write(socket, this.writeByteBuffer); 166 | 167 | if(socket.messageWriter.isEmpty()){ 168 | this.nonEmptyToEmptySockets.add(socket); 169 | } 170 | 171 | keyIterator.remove(); 172 | } 173 | 174 | selectionKeys.clear(); 175 | 176 | } 177 | } 178 | 179 | private void registerNonEmptySockets() throws ClosedChannelException { 180 | for(Socket socket : emptyToNonEmptySockets){ 181 | socket.socketChannel.register(this.writeSelector, SelectionKey.OP_WRITE, socket); 182 | } 183 | emptyToNonEmptySockets.clear(); 184 | } 185 | 186 | private void cancelEmptySockets() { 187 | for(Socket socket : nonEmptyToEmptySockets){ 188 | SelectionKey key = socket.socketChannel.keyFor(this.writeSelector); 189 | 190 | key.cancel(); 191 | } 192 | nonEmptyToEmptySockets.clear(); 193 | } 194 | 195 | private void takeNewOutboundMessages() { 196 | Message outMessage = this.outboundMessageQueue.poll(); 197 | while(outMessage != null){ 198 | Socket socket = this.socketMap.get(outMessage.socketId); 199 | 200 | if(socket != null){ 201 | MessageWriter messageWriter = socket.messageWriter; 202 | if(messageWriter.isEmpty()){ 203 | messageWriter.enqueue(outMessage); 204 | nonEmptyToEmptySockets.remove(socket); 205 | emptyToNonEmptySockets.add(socket); //not necessary if removed from nonEmptyToEmptySockets in prev. statement. 206 | } else{ 207 | messageWriter.enqueue(outMessage); 208 | } 209 | } 210 | 211 | outMessage = this.outboundMessageQueue.poll(); 212 | } 213 | } 214 | 215 | } 216 | -------------------------------------------------------------------------------- /src/main/java/com/jenkov/nioserver/WriteProxy.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver; 2 | 3 | import java.util.Queue; 4 | 5 | /** 6 | * Created by jjenkov on 22-10-2015. 7 | */ 8 | public class WriteProxy { 9 | 10 | private MessageBuffer messageBuffer = null; 11 | private Queue writeQueue = null; 12 | 13 | public WriteProxy(MessageBuffer messageBuffer, Queue writeQueue) { 14 | this.messageBuffer = messageBuffer; 15 | this.writeQueue = writeQueue; 16 | } 17 | 18 | public Message getMessage(){ 19 | return this.messageBuffer.getMessage(); 20 | } 21 | 22 | public boolean enqueue(Message message){ 23 | return this.writeQueue.offer(message); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/jenkov/nioserver/example/Main.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver.example; 2 | 3 | import com.jenkov.nioserver.*; 4 | import com.jenkov.nioserver.http.HttpMessageReaderFactory; 5 | 6 | import java.io.IOException; 7 | import java.util.concurrent.ArrayBlockingQueue; 8 | import java.util.concurrent.BlockingQueue; 9 | 10 | /** 11 | * Created by jjenkov on 19-10-2015. 12 | */ 13 | public class Main { 14 | 15 | public static void main(String[] args) throws IOException { 16 | 17 | String httpResponse = "HTTP/1.1 200 OK\r\n" + 18 | "Content-Length: 38\r\n" + 19 | "Content-Type: text/html\r\n" + 20 | "\r\n" + 21 | "Hello World!"; 22 | 23 | byte[] httpResponseBytes = httpResponse.getBytes("UTF-8"); 24 | 25 | IMessageProcessor messageProcessor = (request, writeProxy) -> { 26 | System.out.println("Message Received from socket: " + request.socketId); 27 | 28 | Message response = writeProxy.getMessage(); 29 | response.socketId = request.socketId; 30 | response.writeToMessage(httpResponseBytes); 31 | 32 | writeProxy.enqueue(response); 33 | }; 34 | 35 | Server server = new Server(9999, new HttpMessageReaderFactory(), messageProcessor); 36 | 37 | server.start(); 38 | 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/jenkov/nioserver/http/HttpHeaders.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver.http; 2 | 3 | /** 4 | * Created by jjenkov on 19-10-2015. 5 | */ 6 | public class HttpHeaders { 7 | 8 | public static int HTTP_METHOD_GET = 1; 9 | public static int HTTP_METHOD_POST = 2; 10 | public static int HTTP_METHOD_PUT = 3; 11 | public static int HTTP_METHOD_HEAD = 4; 12 | public static int HTTP_METHOD_DELETE = 5; 13 | 14 | public int httpMethod = 0; 15 | 16 | public int hostStartIndex = 0; 17 | public int hostEndIndex = 0; 18 | 19 | public int contentLength = 0; 20 | 21 | public int bodyStartIndex = 0; 22 | public int bodyEndIndex = 0; 23 | 24 | 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/jenkov/nioserver/http/HttpMessageReader.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver.http; 2 | 3 | import com.jenkov.nioserver.IMessageReader; 4 | import com.jenkov.nioserver.Message; 5 | import com.jenkov.nioserver.MessageBuffer; 6 | import com.jenkov.nioserver.Socket; 7 | 8 | import java.io.IOException; 9 | import java.nio.ByteBuffer; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * Created by jjenkov on 18-10-2015. 15 | */ 16 | public class HttpMessageReader implements IMessageReader { 17 | 18 | private MessageBuffer messageBuffer = null; 19 | 20 | private List completeMessages = new ArrayList(); 21 | private Message nextMessage = null; 22 | 23 | public HttpMessageReader() { 24 | } 25 | 26 | @Override 27 | public void init(MessageBuffer readMessageBuffer) { 28 | this.messageBuffer = readMessageBuffer; 29 | this.nextMessage = messageBuffer.getMessage(); 30 | this.nextMessage.metaData = new HttpHeaders(); 31 | } 32 | 33 | @Override 34 | public void read(Socket socket, ByteBuffer byteBuffer) throws IOException { 35 | int bytesRead = socket.read(byteBuffer); 36 | byteBuffer.flip(); 37 | 38 | if(byteBuffer.remaining() == 0){ 39 | byteBuffer.clear(); 40 | return; 41 | } 42 | 43 | this.nextMessage.writeToMessage(byteBuffer); 44 | 45 | int endIndex = HttpUtil.parseHttpRequest(this.nextMessage.sharedArray, this.nextMessage.offset, this.nextMessage.offset + this.nextMessage.length, (HttpHeaders) this.nextMessage.metaData); 46 | if(endIndex != -1){ 47 | Message message = this.messageBuffer.getMessage(); 48 | message.metaData = new HttpHeaders(); 49 | 50 | message.writePartialMessageToMessage(nextMessage, endIndex); 51 | 52 | completeMessages.add(nextMessage); 53 | nextMessage = message; 54 | } 55 | byteBuffer.clear(); 56 | } 57 | 58 | 59 | @Override 60 | public List getMessages() { 61 | return this.completeMessages; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/jenkov/nioserver/http/HttpMessageReaderFactory.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver.http; 2 | 3 | import com.jenkov.nioserver.IMessageReader; 4 | import com.jenkov.nioserver.IMessageReaderFactory; 5 | import com.jenkov.nioserver.MessageBuffer; 6 | 7 | /** 8 | * Created by jjenkov on 18-10-2015. 9 | */ 10 | public class HttpMessageReaderFactory implements IMessageReaderFactory { 11 | 12 | public HttpMessageReaderFactory() { 13 | } 14 | 15 | @Override 16 | public IMessageReader createMessageReader() { 17 | return new HttpMessageReader(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/jenkov/nioserver/http/HttpUtil.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver.http; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | 5 | /** 6 | * Created by jjenkov on 19-10-2015. 7 | */ 8 | public class HttpUtil { 9 | 10 | private static final byte[] GET = new byte[]{'G','E','T'}; 11 | private static final byte[] POST = new byte[]{'P','O','S','T'}; 12 | private static final byte[] PUT = new byte[]{'P','U','T'}; 13 | private static final byte[] HEAD = new byte[]{'H','E','A','D'}; 14 | private static final byte[] DELETE = new byte[]{'D','E','L','E','T','E'}; 15 | 16 | private static final byte[] HOST = new byte[]{'H','o','s','t'}; 17 | private static final byte[] CONTENT_LENGTH = new byte[]{'C','o','n','t','e','n','t','-','L','e','n','g','t','h'}; 18 | 19 | public static int parseHttpRequest(byte[] src, int startIndex, int endIndex, HttpHeaders httpHeaders){ 20 | 21 | 22 | /* 23 | int endOfHttpMethod = findNext(src, startIndex, endIndex, (byte) ' '); 24 | if(endOfHttpMethod == -1) return false; 25 | resolveHttpMethod(src, startIndex, httpHeaders); 26 | */ 27 | 28 | //parse HTTP request line 29 | int endOfFirstLine = findNextLineBreak(src, startIndex, endIndex); 30 | if(endOfFirstLine == -1) return -1; 31 | 32 | 33 | //parse HTTP headers 34 | int prevEndOfHeader = endOfFirstLine + 1; 35 | int endOfHeader = findNextLineBreak(src, prevEndOfHeader, endIndex); 36 | 37 | while(endOfHeader != -1 && endOfHeader != prevEndOfHeader + 1){ //prevEndOfHeader + 1 = end of previous header + 2 (+2 = CR + LF) 38 | 39 | if(matches(src, prevEndOfHeader, CONTENT_LENGTH)){ 40 | try { 41 | findContentLength(src, prevEndOfHeader, endIndex, httpHeaders); 42 | } catch (UnsupportedEncodingException e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | 47 | prevEndOfHeader = endOfHeader + 1; 48 | endOfHeader = findNextLineBreak(src, prevEndOfHeader, endIndex); 49 | } 50 | 51 | if(endOfHeader == -1){ 52 | return -1; 53 | } 54 | 55 | //check that byte array contains full HTTP message. 56 | int bodyStartIndex = endOfHeader + 1; 57 | int bodyEndIndex = bodyStartIndex + httpHeaders.contentLength; 58 | 59 | if(bodyEndIndex <= endIndex){ 60 | //byte array contains a full HTTP request 61 | httpHeaders.bodyStartIndex = bodyStartIndex; 62 | httpHeaders.bodyEndIndex = bodyEndIndex; 63 | return bodyEndIndex; 64 | } 65 | 66 | 67 | return -1; 68 | } 69 | 70 | private static void findContentLength(byte[] src, int startIndex, int endIndex, HttpHeaders httpHeaders) throws UnsupportedEncodingException { 71 | int indexOfColon = findNext(src, startIndex, endIndex, (byte) ':'); 72 | 73 | //skip spaces after colon 74 | int index = indexOfColon +1; 75 | while(src[index] == ' '){ 76 | index++; 77 | } 78 | 79 | int valueStartIndex = index; 80 | int valueEndIndex = index; 81 | boolean endOfValueFound = false; 82 | 83 | while(index < endIndex && !endOfValueFound){ 84 | switch(src[index]){ 85 | case '0' : ; 86 | case '1' : ; 87 | case '2' : ; 88 | case '3' : ; 89 | case '4' : ; 90 | case '5' : ; 91 | case '6' : ; 92 | case '7' : ; 93 | case '8' : ; 94 | case '9' : { index++; break; } 95 | 96 | default: { 97 | endOfValueFound = true; 98 | valueEndIndex = index; 99 | } 100 | } 101 | } 102 | 103 | httpHeaders.contentLength = Integer.parseInt(new String(src, valueStartIndex, valueEndIndex - valueStartIndex, "UTF-8")); 104 | 105 | } 106 | 107 | 108 | public static int findNext(byte[] src, int startIndex, int endIndex, byte value){ 109 | for(int index = startIndex; index < endIndex; index++){ 110 | if(src[index] == value) return index; 111 | } 112 | return -1; 113 | } 114 | 115 | public static int findNextLineBreak(byte[] src, int startIndex, int endIndex) { 116 | for(int index = startIndex; index < endIndex; index++){ 117 | if(src[index] == '\n'){ 118 | if(src[index - 1] == '\r'){ 119 | return index; 120 | } 121 | }; 122 | } 123 | return -1; 124 | } 125 | 126 | public static void resolveHttpMethod(byte[] src, int startIndex, HttpHeaders httpHeaders){ 127 | if(matches(src, startIndex, GET)) { 128 | httpHeaders.httpMethod = HttpHeaders.HTTP_METHOD_GET; 129 | return; 130 | } 131 | if(matches(src, startIndex, POST)){ 132 | httpHeaders.httpMethod = HttpHeaders.HTTP_METHOD_POST; 133 | return; 134 | } 135 | if(matches(src, startIndex, PUT)){ 136 | httpHeaders.httpMethod = HttpHeaders.HTTP_METHOD_PUT; 137 | return; 138 | } 139 | if(matches(src, startIndex, HEAD)){ 140 | httpHeaders.httpMethod = HttpHeaders.HTTP_METHOD_HEAD; 141 | return; 142 | } 143 | if(matches(src, startIndex, DELETE)){ 144 | httpHeaders.httpMethod = HttpHeaders.HTTP_METHOD_DELETE; 145 | return; 146 | } 147 | } 148 | 149 | public static boolean matches(byte[] src, int offset, byte[] value){ 150 | for(int i=offset, n=0; n < value.length; i++, n++){ 151 | if(src[i] != value[n]) return false; 152 | } 153 | return true; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/test/java/com.jenkov.nioserver/MessageBufferTest.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | import static org.junit.Assert.assertFalse; 7 | import static org.junit.Assert.assertSame; 8 | import static org.junit.Assert.assertNotNull; 9 | import static org.junit.Assert.assertNotSame; 10 | 11 | /** 12 | * Created by jjenkov on 18-10-2015. 13 | */ 14 | public class MessageBufferTest { 15 | 16 | @Test 17 | public void testGetMessage() { 18 | 19 | MessageBuffer messageBuffer = new MessageBuffer(); 20 | 21 | Message message = messageBuffer.getMessage(); 22 | 23 | assertNotNull(message); 24 | assertEquals(0 , message.offset); 25 | assertEquals(0 , message.length); 26 | assertEquals(4 * 1024, message.capacity); 27 | 28 | Message message2 = messageBuffer.getMessage(); 29 | 30 | assertNotNull(message2); 31 | assertEquals(4096 , message2.offset); 32 | assertEquals(0 , message2.length); 33 | assertEquals(4 * 1024, message2.capacity); 34 | 35 | //todo test what happens if the small buffer space is depleted of messages. 36 | 37 | } 38 | 39 | 40 | @Test 41 | public void testExpandMessage(){ 42 | MessageBuffer messageBuffer = new MessageBuffer(); 43 | 44 | Message message = messageBuffer.getMessage(); 45 | 46 | byte[] smallSharedArray = message.sharedArray; 47 | 48 | assertNotNull(message); 49 | assertEquals(0 , message.offset); 50 | assertEquals(0 , message.length); 51 | assertEquals(4 * 1024, message.capacity); 52 | 53 | messageBuffer.expandMessage(message); 54 | assertEquals(0 , message.offset); 55 | assertEquals(0 , message.length); 56 | assertEquals(128 * 1024, message.capacity); 57 | 58 | byte[] mediumSharedArray = message.sharedArray; 59 | assertNotSame(smallSharedArray, mediumSharedArray); 60 | 61 | messageBuffer.expandMessage(message); 62 | assertEquals(0 , message.offset); 63 | assertEquals(0 , message.length); 64 | assertEquals(1024 * 1024, message.capacity); 65 | 66 | byte[] largeSharedArray = message.sharedArray; 67 | assertNotSame(smallSharedArray, largeSharedArray); 68 | assertNotSame(mediumSharedArray, largeSharedArray); 69 | 70 | //next expansion should not be possible. 71 | assertFalse(messageBuffer.expandMessage(message)); 72 | assertEquals(0 , message.offset); 73 | assertEquals(0 , message.length); 74 | assertEquals(1024 * 1024, message.capacity); 75 | assertSame(message.sharedArray, largeSharedArray); 76 | 77 | 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/com.jenkov.nioserver/MessageTest.java: -------------------------------------------------------------------------------- 1 | package com.jenkov.nioserver; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | import static org.junit.Assert.assertFalse; 7 | import static org.junit.Assert.assertSame; 8 | import static org.junit.Assert.assertNotNull; 9 | import static org.junit.Assert.assertNotSame; 10 | 11 | import java.nio.ByteBuffer; 12 | 13 | 14 | /** 15 | * Created by jjenkov on 18-10-2015. 16 | */ 17 | public class MessageTest { 18 | 19 | 20 | @Test 21 | public void testWriteToMessage() { 22 | MessageBuffer messageBuffer = new MessageBuffer(); 23 | 24 | Message message = messageBuffer.getMessage(); 25 | ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024); 26 | 27 | fill(byteBuffer, 4096); 28 | 29 | int written = message.writeToMessage(byteBuffer); 30 | assertEquals(4096, written); 31 | assertEquals(4096, message.length); 32 | assertSame(messageBuffer.smallMessageBuffer, message.sharedArray); 33 | 34 | fill(byteBuffer, 124 * 1024); 35 | written = message.writeToMessage(byteBuffer); 36 | assertEquals(124 * 1024, written); 37 | assertEquals(128 * 1024, message.length); 38 | assertSame(messageBuffer.mediumMessageBuffer, message.sharedArray); 39 | 40 | fill(byteBuffer, (1024-128) * 1024); 41 | written = message.writeToMessage(byteBuffer); 42 | assertEquals(896 * 1024, written); 43 | assertEquals(1024 * 1024, message.length); 44 | assertSame(messageBuffer.largeMessageBuffer, message.sharedArray); 45 | 46 | fill(byteBuffer, 1); 47 | written = message.writeToMessage(byteBuffer); 48 | assertEquals(-1, written); 49 | 50 | } 51 | 52 | private void fill(ByteBuffer byteBuffer, int length){ 53 | byteBuffer.clear(); 54 | for(int i=0; i