001    /**
002     * Copyright (C) 2010-2011, FuseSource Corp.  All rights reserved.
003     *
004     *     http://fusesource.com
005     *
006     * The software in this package is published under the terms of the
007     * CDDL license a copy of which has been included with this distribution
008     * in the license.txt file.
009     */
010    package org.fusesource.hawtdispatch.transport;
011    
012    import org.fusesource.hawtdispatch.Dispatch;
013    import org.fusesource.hawtdispatch.DispatchQueue;
014    import org.fusesource.hawtdispatch.DispatchSource;
015    
016    import java.io.IOException;
017    import java.net.*;
018    import java.nio.channels.SelectionKey;
019    import java.nio.channels.ServerSocketChannel;
020    import java.nio.channels.SocketChannel;
021    import java.util.Map;
022    
023    /**
024     * A TCP based implementation of {@link TransportServer}
025     *
026     * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
027     */
028    
029    public class TcpTransportServer implements TransportServer {
030    
031        private final String bindScheme;
032        private final InetSocketAddress bindAddress;
033    
034        private int backlog = 100;
035    
036        private ServerSocketChannel channel;
037        private TransportServerListener listener;
038        private DispatchQueue dispatchQueue;
039        private DispatchSource acceptSource;
040        private int receive_buffer_size = 64*1024;
041    
042        public TcpTransportServer(URI location) throws UnknownHostException {
043            bindScheme = location.getScheme();
044            String host = location.getHost();
045            host = (host == null || host.length() == 0) ? "::" : host;
046            bindAddress = new InetSocketAddress(InetAddress.getByName(host), location.getPort());
047        }
048    
049        public void setTransportServerListener(TransportServerListener listener) {
050            this.listener = listener;
051        }
052    
053        public InetSocketAddress getSocketAddress() {
054            return (InetSocketAddress) channel.socket().getLocalSocketAddress();
055        }
056    
057        public DispatchQueue getDispatchQueue() {
058            return dispatchQueue;
059        }
060    
061        public void setDispatchQueue(DispatchQueue dispatchQueue) {
062            this.dispatchQueue = dispatchQueue;
063        }
064    
065        public void suspend() {
066            acceptSource.suspend();
067        }
068    
069        public void resume() {
070            acceptSource.resume();
071        }
072    
073        public void start() throws Exception {
074            start(null);
075        }
076        public void start(Runnable onCompleted) throws Exception {
077    
078            try {
079                channel = ServerSocketChannel.open();
080                channel.configureBlocking(false);
081                try {
082                    channel.socket().setReceiveBufferSize(receive_buffer_size);
083                } catch (SocketException ignore) {
084                }
085                channel.socket().bind(bindAddress, backlog);
086            } catch (IOException e) {
087                throw new IOException("Failed to bind to server socket: " + bindAddress + " due to: " + e);
088            }
089    
090            acceptSource = Dispatch.createSource(channel, SelectionKey.OP_ACCEPT, dispatchQueue);
091            acceptSource.setEventHandler(new Runnable() {
092                public void run() {
093                    try {
094                        SocketChannel client = channel.accept();
095                        while( client!=null ) {
096                            handleSocket(client);
097                            client = channel.accept();
098                        }
099                    } catch (Exception e) {
100                        listener.onAcceptError(e);
101                    }
102                }
103            });
104            acceptSource.setCancelHandler(new Runnable() {
105                public void run() {
106                    try {
107                        channel.close();
108                    } catch (IOException e) {
109                    }
110                }
111            });
112            acceptSource.resume();
113            if( onCompleted!=null ) {
114                dispatchQueue.execute(onCompleted);
115            }
116        }
117    
118        public String getBoundAddress() {
119            try {
120                return new URI(bindScheme, null, bindAddress.getAddress().getHostAddress(), channel.socket().getLocalPort(), null, null, null).toString();
121            } catch (URISyntaxException e) {
122                throw new RuntimeException(e);
123            }
124        }
125    
126        public String getConnectAddress() {
127            try {
128                return new URI(bindScheme, null, resolveHostName(), channel.socket().getLocalPort(), null, null, null).toString();
129            } catch (URISyntaxException e) {
130                throw new RuntimeException(e);
131            }
132        }
133    
134    
135        protected String resolveHostName() {
136            String result;
137            if (bindAddress.getAddress().isAnyLocalAddress()) {
138                // make it more human readable and useful, an alternative to 0.0.0.0
139                try {
140                    result = InetAddress.getLocalHost().getCanonicalHostName();
141                } catch (UnknownHostException e) {
142                    result = "localhost";
143                }
144            } else {
145                result = bindAddress.getAddress().getCanonicalHostName();
146            }
147            return result;
148        }
149    
150        public void stop() throws Exception {
151            stop(null);
152        }
153        public void stop(final Runnable onCompleted) throws Exception {
154            if( acceptSource.isCanceled() ) {
155                onCompleted.run();
156            } else {
157                acceptSource.setCancelHandler(new Runnable() {
158                    public void run() {
159                        try {
160                            channel.close();
161                        } catch (IOException e) {
162                        }
163                        onCompleted.run();
164                    }
165                });
166                acceptSource.cancel();
167            }
168        }
169    
170        public int getBacklog() {
171            return backlog;
172        }
173    
174        public void setBacklog(int backlog) {
175            this.backlog = backlog;
176        }
177    
178        protected final void handleSocket(SocketChannel socket) throws Exception {
179            TcpTransport transport = createTransport();
180            transport.connected(socket);
181            listener.onAccept(transport);
182        }
183    
184        protected TcpTransport createTransport() {
185            return new TcpTransport();
186        }
187    
188        /**
189         * @return pretty print of this
190         */
191        public String toString() {
192            return getBoundAddress();
193        }
194    
195    
196        public int getReceive_buffer_size() {
197            return receive_buffer_size;
198        }
199    
200        public void setReceive_buffer_size(int receive_buffer_size) {
201            this.receive_buffer_size = receive_buffer_size;
202        }
203    
204    }