Java-Gaming.org    
Featured games (81)
games approved by the League of Dukes
Games in Showcase (498)
Games in Android Showcase (117)
games submitted by our members
Games in WIP (563)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  NIO SSL Server  (Read 3474 times)
0 Members and 1 Guest are viewing this topic.
Offline Riven
« League of Dukes »

JGO Overlord


Medals: 800
Projects: 4
Exp: 16 years


Hand over your head.


« Posted 2010-02-14 20:49:26 »

SSL with plain old IO is downright simple, yet with NIO it's a whole different story.

This class uses SSLEngine to allow running multiple SSL connections on a single I/O thread.
The SSL handshake itself is performed by N worker threads to prevent them blocking all I/O.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
102  
103  
104  
105  
106  
107  
108  
109  
110  
111  
112  
113  
114  
115  
116  
117  
118  
119  
120  
121  
122  
123  
124  
125  
126  
127  
128  
129  
130  
131  
132  
133  
134  
135  
136  
137  
138  
139  
140  
141  
142  
143  
144  
145  
146  
147  
148  
149  
150  
151  
152  
153  
154  
155  
156  
157  
158  
159  
160  
161  
162  
163  
164  
165  
166  
167  
168  
169  
170  
171  
172  
173  
174  
175  
176  
177  
178  
179  
180  
181  
182  
183  
184  
185  
186  
187  
188  
189  
190  
191  
192  
193  
194  
195  
196  
197  
198  
199  
200  
201  
202  
203  
204  
205  
206  
207  
208  
209  
210  
211  
212  
213  
214  
215  
216  
217  
218  
219  
220  
221  
222  
223  
import java.nio.ByteBuffer;
import java.util.concurrent.Executor;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;

public abstract class NonBlockingSSL implements Runnable
{
   final ByteBuffer wrapSrc, unwrapSrc;
   final ByteBuffer wrapDst, unwrapDst;

   final SSLEngine  engine;
   final Executor   ioWorker, taskWorkers;

   public NonBlockingSSL(SSLEngine engine, int bufferSize, Executor ioWorker, Executor taskWorkers)
   {
      this.wrapSrc = ByteBuffer.allocateDirect(bufferSize);
      this.wrapDst = ByteBuffer.allocateDirect(bufferSize);

      this.unwrapSrc = ByteBuffer.allocateDirect(bufferSize);
      this.unwrapDst = ByteBuffer.allocateDirect(bufferSize);

      this.unwrapSrc.limit(0);

      this.engine = engine;
      this.ioWorker = ioWorker;
      this.taskWorkers = taskWorkers;

      this.ioWorker.execute(this);
   }

   public abstract void onInboundData(ByteBuffer decrypted);

   public abstract void onOutboundData(ByteBuffer encrypted);

   public abstract void onHandshakeFailure(Exception cause);

   public abstract void onHandshakeSuccess();

   public abstract void onClosed();

   public void sendLater(final ByteBuffer data)
   {
      this.ioWorker.execute(new Runnable()
      {
         @Override
         public void run()
         {
            wrapSrc.put(data);

            NonBlockingSSL.this.run();
         }
      });
   }

   public void notifyReceived(final ByteBuffer data)
   {
      this.ioWorker.execute(new Runnable()
      {
         @Override
         public void run()
         {
            unwrapSrc.put(data);

            NonBlockingSSL.this.run();
         }
      });
   }

   public void run()
   {
      // executes non-blocking tasks on the IO-Worker

      while (this.step())
      {
         continue;
      }

      // apparently we hit a blocking-task...
  }

   private boolean step()
   {
      switch (engine.getHandshakeStatus())
      {
         case NOT_HANDSHAKING:
            boolean anything = false;
            {
               if (wrapSrc.position() > 0)
                  anything |= this.wrap();
               if (unwrapSrc.position() > 0)
                  anything |= this.unwrap();
            }
            return anything;

         case NEED_WRAP:
            if (!this.wrap())
               return false;
            break;

         case NEED_UNWRAP:
            if (!this.unwrap())
               return false;
            break;

         case NEED_TASK:
            final Runnable sslTask = engine.getDelegatedTask();
            Runnable wrappedTask = new Runnable()
            {
               @Override
               public void run()
               {
                  System.out.println("async SSL task: " + sslTask);
                  long t0 = System.nanoTime();
                  sslTask.run();
                  long t1 = System.nanoTime();
                  System.out.println("async SSL task took: " + (t1 - t0) / 1000000 + "ms");

                  // continue handling I/O
                 ioWorker.execute(NonBlockingSSL.this);
               }
            };
            taskWorkers.execute(wrappedTask);
            return false;

         case FINISHED:
            throw new IllegalStateException("FINISHED");
      }

      return true;
   }

   private boolean wrap()
   {
      SSLEngineResult wrapResult;

      try
      {
         wrapSrc.flip();
         wrapResult = engine.wrap(wrapSrc, wrapDst);
         wrapSrc.compact();
      }
      catch (SSLException exc)
      {
         this.onHandshakeFailure(exc);
         return false;
      }

      switch (wrapResult.getStatus())
      {
         case OK:
            if (wrapDst.position() > 0)
            {
               wrapDst.flip();
               this.onOutboundData(wrapDst);
               wrapDst.compact();
            }
            break;

         case BUFFER_UNDERFLOW:
            // try again later
           break;

         case BUFFER_OVERFLOW:
            throw new IllegalStateException("failed to wrap");

         case CLOSED:
            this.onClosed();
            return false;
      }

      return true;
   }

   private boolean unwrap()
   {
      SSLEngineResult unwrapResult;

      try
      {
         unwrapSrc.flip();
         unwrapResult = engine.unwrap(unwrapSrc, unwrapDst);
         unwrapSrc.compact();
      }
      catch (SSLException exc)
      {
         this.onHandshakeFailure(exc);
         return false;
      }

      switch (unwrapResult.getStatus())
      {
         case OK:
            if (unwrapDst.position() > 0)
            {
               unwrapDst.flip();
               this.onInboundData(unwrapDst);
               unwrapDst.compact();
            }
            break;

         case CLOSED:
            this.onClosed();
            return false;

         case BUFFER_OVERFLOW:
            throw new IllegalStateException("failed to unwrap");

         case BUFFER_UNDERFLOW:
            return false;
      }

      switch (unwrapResult.getHandshakeStatus())
      {
         case FINISHED:
            this.onHandshakeSuccess();
            return false;
      }

      return true;
   }
}



The SSL code is actually independant of NIO, but I added the following code to support NIO.
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.WritableByteChannel;
import java.util.concurrent.Executor;

import javax.net.ssl.SSLEngine;

public abstract class NioNonBlockingSSL extends NonBlockingSSL
{
   private final SelectionKey key;

   public NioNonBlockingSSL(SelectionKey key, SSLEngine engine, int bufferSize, Executor ioWorker, Executor taskWorkers)
   {
      super(engine, bufferSize, ioWorker, taskWorkers);

      this.key = key;
   }

   private final ByteBuffer big = ByteBuffer.allocateDirect(64 * 1024);

   public boolean processIncomingData()
   {
      big.clear();
      int bytes;
      try
      {
         bytes = ((ReadableByteChannel) this.key.channel()).read(big);
      }
      catch (IOException exc)
      {
         bytes = -1;
      }
      if (bytes == -1)
         return false;
      big.flip();

      ByteBuffer copy = ByteBuffer.allocateDirect(bytes);
      copy.put(big);
      copy.flip();

      this.notifyReceived(copy);
      return true;
   }

   @Override
   public void onOutboundData(ByteBuffer encrypted)
   {
      try
      {
         ((WritableByteChannel) this.key.channel()).write(encrypted);

         if (encrypted.hasRemaining())
         {
            throw new IllegalStateException("failed to bulk-write");
         }
      }
      catch (IOException exc)
      {
         throw new IllegalStateException(exc);
      }
   }
}

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline Riven
« League of Dukes »

JGO Overlord


Medals: 800
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #1 - Posted 2010-02-14 20:50:13 »

Demo, which connects to https://www.paypal.com/ and displays the decrypted HTTP response.

Anybody mentioning URLConnection to do the same thing, will be gently explained that you can similarly run a server with this code.

I realize very few people here actually need a non-blocking SSL server, but heck, give it a whirl!
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
102  
103  
104  
105  
106  
107  
108  
109  
110  
111  
112  
113  
114  
115  
116  
117  
118  
119  
120  
121  
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;

public class NonBlockingSSLDemo
{
   public static void main(String[] args) throws Exception
   {
      // connect to the webservice
     SelectionKey key;
      {
         InetSocketAddress connectTo = new InetSocketAddress("www.paypal.com", 443);
         Selector selector = Selector.open();
         SocketChannel channel = SocketChannel.open();
         channel.connect(connectTo);
         System.out.println("connected to: " + channel);

         channel.configureBlocking(false);
         channel.socket().setTcpNoDelay(true);

         int ops = SelectionKey.OP_CONNECT | SelectionKey.OP_READ;
         key = channel.register(selector, ops);
      }

      // setup the io/worker threads
     final Executor ioWorker = Executors.newSingleThreadExecutor();
      final Executor taskWorkers = Executors.newFixedThreadPool(4);

      // setup the SSLEngine
     final SSLEngine engine = SSLContext.getDefault().createSSLEngine();
      engine.setUseClientMode(true);
      engine.beginHandshake();
      final int ioBufferSize = 64 * 1024;

      final NioNonBlockingSSL ssl;
      ssl = new NioNonBlockingSSL(key, engine, ioBufferSize, ioWorker, taskWorkers)
      {
         @Override
         public void onHandshakeFailure(Exception cause)
         {
            System.out.println("handshake failure");

            cause.printStackTrace();
         }

         @Override
         public void onHandshakeSuccess()
         {
            System.out.println("handshake success");

            SSLSession session = engine.getSession();

            try
            {
               System.out.println("- local principal: " + session.getLocalPrincipal());
               System.out.println("- remote principal: " + session.getPeerPrincipal());
               System.out.println("- using cipher: " + session.getCipherSuite());
            }
            catch (Exception exc)
            {
               exc.printStackTrace();
            }

            // simple HTTP request to www.paypal.com
           StringBuilder http = new StringBuilder();
            http.append("GET / HTTP/1.0\r\n");
            http.append("Connection: close\r\n");
            http.append("\r\n");

            byte[] data = http.toString().getBytes();
            ByteBuffer send = ByteBuffer.wrap(data);
            this.sendLater(send);
         }

         @Override
         public void onInboundData(ByteBuffer decrypted)
         {
            // this is where the HTTP response ends up

            byte[] dst = new byte[decrypted.remaining()];
            decrypted.get(dst);
            String response = new String(dst);

            System.out.print(response);
            System.out.flush();
         }

         @Override
         public void onClosed()
         {
            System.out.println("<ssl session closed>");
         }
      };

      // simplistic NIO stuff

      while (true)
      {
         key.selector().select();

         Iterator<SelectionKey> keys = key.selector().selectedKeys().iterator();

         while (keys.hasNext())
         {
            keys.next(); // there is only one key, don't bother
           keys.remove();

            ssl.processIncomingData();
         }
      }
   }
}

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline i30817

Junior Member





« Reply #2 - Posted 2010-02-15 15:36:37 »

A thing i've been meaning to ask you pro networking guys.
A new URL connection opens a socket to each request to a server right?

Anyway to use the http protocol without having to open always open a new connection? I'm already using Executors and some application heuristics, but there can be 6 connections (threads) at a time for responsiveness.
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Riven
« League of Dukes »

JGO Overlord


Medals: 800
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #3 - Posted 2010-02-15 16:23:40 »

A thing i've been meaning to ask you pro networking guys.
A new URL connection opens a socket for each request to a server right?
Browsing through the bytecode (sourcecode seems missing) it seems that sun.net.www.http.HttpClient.New() is able to reuse existing TCP connections.


Any way to use the http protocol without having to always open a new connection?
Yes, HTTP/1.1 supports multiple (sequential) requests/responses.


I'm already using Executors and some application heuristics, but there can be 6 connections (threads) at a time for responsiveness.
It's would be a shame if you have 6 connections to a slow/overloaded server, while you were interested in many files from many servers. I suggest you change it, to (max) 6 connections to each server. For proper performance of small request (like dozens of thumbnails), it's very important  to keep the same TCP connection, to sustain/increase bandwidth, as each TCP connection starts out relatively slow.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline i30817

Junior Member





« Reply #4 - Posted 2010-02-15 16:33:04 »

Yeah the server i am using (openlibrary) doesn't seem to have a way to do multiple request of covers or even a way to combine requests for author queries (for the id) and author books

So for each book i do:
1) get the author id (search in server)
2) get the author book (search in server)
3) while not found a cover in the book list go to 3)

And i found out that i was saving my thumbnails in tmp instead of the user home. (in linux tmp is deleted in startup)

In fact i'm sure i put it down sometimes when testing.
It's better now.
 Tongue
Offline Riven
« League of Dukes »

JGO Overlord


Medals: 800
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #5 - Posted 2010-02-15 16:52:29 »

Yeah the server i am using (openlibrary) doesn't seem to have a way to do multiple request of covers or even a way to combine requests for author queries (for the id) and author books

Well, no. In your case, you can't have the result of multiple queries in one response (as long as the service doesn't support that) you can query multiple times over the same tcp connection (as long as the httpserver supports it).

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

radar3301 (12 views)
2014-09-21 23:33:17

BurntPizza (31 views)
2014-09-21 02:42:18

BurntPizza (22 views)
2014-09-21 01:30:30

moogie (20 views)
2014-09-21 00:26:15

UprightPath (29 views)
2014-09-20 20:14:06

BurntPizza (33 views)
2014-09-19 03:14:18

Dwinin (48 views)
2014-09-12 09:08:26

Norakomi (74 views)
2014-09-10 13:57:51

TehJavaDev (103 views)
2014-09-10 06:39:09

Tekkerue (51 views)
2014-09-09 02:24:56
List of Learning Resources
by Longor1996
2014-08-16 10:40:00

List of Learning Resources
by SilverTiger
2014-08-05 19:33:27

Resources for WIP games
by CogWheelz
2014-08-01 16:20:17

Resources for WIP games
by CogWheelz
2014-08-01 16:19:50

List of Learning Resources
by SilverTiger
2014-07-31 16:29:50

List of Learning Resources
by SilverTiger
2014-07-31 16:26:06

List of Learning Resources
by SilverTiger
2014-07-31 11:54:12

HotSpot Options
by dleskov
2014-07-08 01:59:08
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!