1//
2//  ========================================================================
3//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4//  ------------------------------------------------------------------------
5//  All rights reserved. This program and the accompanying materials
6//  are made available under the terms of the Eclipse Public License v1.0
7//  and Apache License v2.0 which accompanies this distribution.
8//
9//      The Eclipse Public License is available at
10//      http://www.eclipse.org/legal/epl-v10.html
11//
12//      The Apache License v2.0 is available at
13//      http://www.opensource.org/licenses/apache2.0.php
14//
15//  You may elect to redistribute this code under either of these licenses.
16//  ========================================================================
17//
18
19package org.eclipse.jetty.server.bio;
20
21import java.io.IOException;
22import java.net.InetAddress;
23import java.net.ServerSocket;
24import java.net.Socket;
25import java.net.SocketException;
26import java.util.HashSet;
27import java.util.Set;
28
29import org.eclipse.jetty.http.HttpException;
30import org.eclipse.jetty.io.Buffer;
31import org.eclipse.jetty.io.ConnectedEndPoint;
32import org.eclipse.jetty.io.Connection;
33import org.eclipse.jetty.io.EndPoint;
34import org.eclipse.jetty.io.EofException;
35import org.eclipse.jetty.io.bio.SocketEndPoint;
36import org.eclipse.jetty.server.AbstractConnector;
37import org.eclipse.jetty.server.AbstractHttpConnection;
38import org.eclipse.jetty.server.BlockingHttpConnection;
39import org.eclipse.jetty.server.Request;
40import org.eclipse.jetty.util.component.AggregateLifeCycle;
41import org.eclipse.jetty.util.log.Log;
42import org.eclipse.jetty.util.log.Logger;
43
44
45/* ------------------------------------------------------------------------------- */
46/**  Socket Connector.
47 * This connector implements a traditional blocking IO and threading model.
48 * Normal JRE sockets are used and a thread is allocated per connection.
49 * Buffers are managed so that large buffers are only allocated to active connections.
50 *
51 * This Connector should only be used if NIO is not available.
52 *
53 * @org.apache.xbean.XBean element="bioConnector" description="Creates a BIO based socket connector"
54 *
55 *
56 */
57public class SocketConnector extends AbstractConnector
58{
59    private static final Logger LOG = Log.getLogger(SocketConnector.class);
60
61    protected ServerSocket _serverSocket;
62    protected final Set<EndPoint> _connections;
63    protected volatile int _localPort=-1;
64
65    /* ------------------------------------------------------------ */
66    /** Constructor.
67     *
68     */
69    public SocketConnector()
70    {
71        _connections=new HashSet<EndPoint>();
72    }
73
74    /* ------------------------------------------------------------ */
75    public Object getConnection()
76    {
77        return _serverSocket;
78    }
79
80    /* ------------------------------------------------------------ */
81    public void open() throws IOException
82    {
83        // Create a new server socket and set to non blocking mode
84        if (_serverSocket==null || _serverSocket.isClosed())
85        _serverSocket= newServerSocket(getHost(),getPort(),getAcceptQueueSize());
86        _serverSocket.setReuseAddress(getReuseAddress());
87        _localPort=_serverSocket.getLocalPort();
88        if (_localPort<=0)
89            throw new IllegalStateException("port not allocated for "+this);
90
91    }
92
93    /* ------------------------------------------------------------ */
94    protected ServerSocket newServerSocket(String host, int port,int backlog) throws IOException
95    {
96        ServerSocket ss= host==null?
97            new ServerSocket(port,backlog):
98            new ServerSocket(port,backlog,InetAddress.getByName(host));
99
100        return ss;
101    }
102
103    /* ------------------------------------------------------------ */
104    public void close() throws IOException
105    {
106        if (_serverSocket!=null)
107            _serverSocket.close();
108        _serverSocket=null;
109        _localPort=-2;
110    }
111
112    /* ------------------------------------------------------------ */
113    @Override
114    public void accept(int acceptorID)
115    	throws IOException, InterruptedException
116    {
117        Socket socket = _serverSocket.accept();
118        configure(socket);
119
120        ConnectorEndPoint connection=new ConnectorEndPoint(socket);
121        connection.dispatch();
122    }
123
124    /* ------------------------------------------------------------------------------- */
125    /**
126     * Allows subclass to override Conection if required.
127     */
128    protected Connection newConnection(EndPoint endpoint)
129    {
130        return new BlockingHttpConnection(this, endpoint, getServer());
131    }
132
133    /* ------------------------------------------------------------------------------- */
134    @Override
135    public void customize(EndPoint endpoint, Request request)
136        throws IOException
137    {
138        ConnectorEndPoint connection = (ConnectorEndPoint)endpoint;
139        int lrmit = isLowResources()?_lowResourceMaxIdleTime:_maxIdleTime;
140        connection.setMaxIdleTime(lrmit);
141
142        super.customize(endpoint, request);
143    }
144
145    /* ------------------------------------------------------------------------------- */
146    public int getLocalPort()
147    {
148        return _localPort;
149    }
150
151    /* ------------------------------------------------------------------------------- */
152    @Override
153    protected void doStart() throws Exception
154    {
155        _connections.clear();
156        super.doStart();
157    }
158
159    /* ------------------------------------------------------------------------------- */
160    @Override
161    protected void doStop() throws Exception
162    {
163        super.doStop();
164        Set<EndPoint> set = new HashSet<EndPoint>();
165        synchronized(_connections)
166        {
167            set.addAll(_connections);
168        }
169        for (EndPoint endPoint : set)
170        {
171            ConnectorEndPoint connection = (ConnectorEndPoint)endPoint;
172            connection.close();
173        }
174    }
175
176    @Override
177    public void dump(Appendable out, String indent) throws IOException
178    {
179        super.dump(out, indent);
180        Set<EndPoint> connections = new HashSet<EndPoint>();
181        synchronized (_connections)
182        {
183            connections.addAll(_connections);
184        }
185        AggregateLifeCycle.dump(out, indent, connections);
186    }
187
188    /* ------------------------------------------------------------------------------- */
189    /* ------------------------------------------------------------------------------- */
190    /* ------------------------------------------------------------------------------- */
191    protected class ConnectorEndPoint extends SocketEndPoint implements Runnable, ConnectedEndPoint
192    {
193        volatile Connection _connection;
194        protected final Socket _socket;
195
196        public ConnectorEndPoint(Socket socket) throws IOException
197        {
198            super(socket,_maxIdleTime);
199            _connection = newConnection(this);
200            _socket=socket;
201        }
202
203        public Connection getConnection()
204        {
205            return _connection;
206        }
207
208        public void setConnection(Connection connection)
209        {
210            if (_connection!=connection && _connection!=null)
211                connectionUpgraded(_connection,connection);
212            _connection=connection;
213        }
214
215        public void dispatch() throws IOException
216        {
217            if (getThreadPool()==null || !getThreadPool().dispatch(this))
218            {
219                LOG.warn("dispatch failed for {}",_connection);
220                close();
221            }
222        }
223
224        @Override
225        public int fill(Buffer buffer) throws IOException
226        {
227            int l = super.fill(buffer);
228            if (l<0)
229            {
230                if (!isInputShutdown())
231                    shutdownInput();
232                if (isOutputShutdown())
233                    close();
234            }
235            return l;
236        }
237
238        @Override
239        public void close() throws IOException
240        {
241            if (_connection instanceof AbstractHttpConnection)
242                ((AbstractHttpConnection)_connection).getRequest().getAsyncContinuation().cancel();
243            super.close();
244        }
245
246        public void run()
247        {
248            try
249            {
250                connectionOpened(_connection);
251                synchronized(_connections)
252                {
253                    _connections.add(this);
254                }
255
256                while (isStarted() && !isClosed())
257                {
258                    if (_connection.isIdle())
259                    {
260                        if (isLowResources())
261                            setMaxIdleTime(getLowResourcesMaxIdleTime());
262                    }
263
264                    _connection=_connection.handle();
265                }
266            }
267            catch (EofException e)
268            {
269                LOG.debug("EOF", e);
270                try{close();}
271                catch(IOException e2){LOG.ignore(e2);}
272            }
273            catch (SocketException e)
274            {
275                LOG.debug("EOF", e);
276                try{close();}
277                catch(IOException e2){LOG.ignore(e2);}
278            }
279            catch (HttpException e)
280            {
281                LOG.debug("BAD", e);
282                try{close();}
283                catch(IOException e2){LOG.ignore(e2);}
284            }
285            catch(Exception e)
286            {
287                LOG.warn("handle failed?",e);
288                try{close();}
289                catch(IOException e2){LOG.ignore(e2);}
290            }
291            finally
292            {
293                connectionClosed(_connection);
294                synchronized(_connections)
295                {
296                    _connections.remove(this);
297                }
298
299                // wait for client to close, but if not, close ourselves.
300                try
301                {
302                    if (!_socket.isClosed())
303                    {
304                        long timestamp=System.currentTimeMillis();
305                        int max_idle=getMaxIdleTime();
306
307                        _socket.setSoTimeout(getMaxIdleTime());
308                        int c=0;
309                        do
310                        {
311                            c = _socket.getInputStream().read();
312                        }
313                        while (c>=0 && (System.currentTimeMillis()-timestamp)<max_idle);
314                        if (!_socket.isClosed())
315                            _socket.close();
316                    }
317                }
318                catch(IOException e)
319                {
320                    LOG.ignore(e);
321                }
322            }
323        }
324    }
325}
326