1 /*
2  * JBoss, Home of Professional Open Source.
3  * Copyright 2014 Red Hat, Inc., and individual contributors
4  * as indicated by the @author tags.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */

18
19 package io.undertow.server.protocol.http;
20
21 import java.io.IOException;
22 import java.nio.ByteBuffer;
23 import java.util.Collections;
24 import java.util.Set;
25 import java.util.concurrent.ConcurrentHashMap;
26
27 import org.xnio.ChannelListener;
28 import org.xnio.IoUtils;
29 import org.xnio.OptionMap;
30 import org.xnio.Options;
31 import org.xnio.Pool;
32 import org.xnio.StreamConnection;
33
34 import io.undertow.UndertowLogger;
35 import io.undertow.UndertowMessages;
36 import io.undertow.UndertowOptions;
37 import io.undertow.conduits.BytesReceivedStreamSourceConduit;
38 import io.undertow.conduits.BytesSentStreamSinkConduit;
39 import io.undertow.conduits.IdleTimeoutConduit;
40 import io.undertow.conduits.ReadTimeoutStreamSourceConduit;
41 import io.undertow.conduits.WriteTimeoutStreamSinkConduit;
42 import io.undertow.connector.ByteBufferPool;
43 import io.undertow.connector.PooledByteBuffer;
44 import io.undertow.server.ConnectorStatistics;
45 import io.undertow.server.ConnectorStatisticsImpl;
46 import io.undertow.server.DelegateOpenListener;
47 import io.undertow.server.HttpHandler;
48 import io.undertow.server.ServerConnection;
49 import io.undertow.server.XnioByteBufferPool;
50
51 /**
52  * Open listener for HTTP server.  XNIO should be set up to chain the accept handler to post-accept open
53  * listeners to this listener which actually initiates HTTP parsing.
54  *
55  * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
56  */

57 public final class HttpOpenListener implements ChannelListener<StreamConnection>, DelegateOpenListener {
58
59     private final Set<HttpServerConnection> connections = Collections.newSetFromMap(new ConcurrentHashMap<>());
60
61     private final ByteBufferPool bufferPool;
62     private final int bufferSize;
63
64     private volatile HttpHandler rootHandler;
65
66     private volatile OptionMap undertowOptions;
67
68     private volatile HttpRequestParser parser;
69
70     private volatile boolean statisticsEnabled;
71     private final ConnectorStatisticsImpl connectorStatistics;
72
73     @Deprecated
74     public HttpOpenListener(final Pool<ByteBuffer> pool) {
75         this(pool, OptionMap.EMPTY);
76     }
77
78     @Deprecated
79     public HttpOpenListener(final Pool<ByteBuffer> pool, final OptionMap undertowOptions) {
80         this(new XnioByteBufferPool(pool), undertowOptions);
81     }
82
83     public HttpOpenListener(final ByteBufferPool pool) {
84         this(pool, OptionMap.EMPTY);
85     }
86
87     public HttpOpenListener(final ByteBufferPool pool, final OptionMap undertowOptions) {
88         this.undertowOptions = undertowOptions;
89         this.bufferPool = pool;
90         PooledByteBuffer buf = pool.allocate();
91         this.bufferSize = buf.getBuffer().remaining();
92         buf.close();
93         parser = HttpRequestParser.instance(undertowOptions);
94         connectorStatistics = new ConnectorStatisticsImpl();
95         statisticsEnabled = undertowOptions.get(UndertowOptions.ENABLE_CONNECTOR_STATISTICS, false);
96     }
97
98     @Override
99     public void handleEvent(StreamConnection channel) {
100         handleEvent(channel, null);
101     }
102
103     @Override
104     public void handleEvent(final StreamConnection channel, PooledByteBuffer buffer) {
105         if (UndertowLogger.REQUEST_LOGGER.isTraceEnabled()) {
106             UndertowLogger.REQUEST_LOGGER.tracef("Opened connection with %s", channel.getPeerAddress());
107         }
108
109         //set read and write timeouts
110         try {
111             Integer readTimeout = channel.getOption(Options.READ_TIMEOUT);
112             Integer idle = undertowOptions.get(UndertowOptions.IDLE_TIMEOUT);
113             if (idle != null) {
114                 IdleTimeoutConduit conduit = new IdleTimeoutConduit(channel);
115                 channel.getSourceChannel().setConduit(conduit);
116                 channel.getSinkChannel().setConduit(conduit);
117             }
118             if (readTimeout != null && readTimeout > 0) {
119                 channel.getSourceChannel().setConduit(new ReadTimeoutStreamSourceConduit(channel.getSourceChannel().getConduit(), channel, this));
120             }
121             Integer writeTimeout = channel.getOption(Options.WRITE_TIMEOUT);
122             if (writeTimeout != null && writeTimeout > 0) {
123                 channel.getSinkChannel().setConduit(new WriteTimeoutStreamSinkConduit(channel.getSinkChannel().getConduit(), channel, this));
124             }
125         } catch (IOException e) {
126             IoUtils.safeClose(channel);
127             UndertowLogger.REQUEST_IO_LOGGER.ioException(e);
128         } catch (Throwable t) {
129             IoUtils.safeClose(channel);
130             UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t);
131         }
132         if (statisticsEnabled) {
133             channel.getSinkChannel().setConduit(new BytesSentStreamSinkConduit(channel.getSinkChannel().getConduit(), connectorStatistics.sentAccumulator()));
134             channel.getSourceChannel().setConduit(new BytesReceivedStreamSourceConduit(channel.getSourceChannel().getConduit(), connectorStatistics.receivedAccumulator()));
135         }
136
137         HttpServerConnection connection = new HttpServerConnection(channel, bufferPool, rootHandler, undertowOptions, bufferSize, statisticsEnabled ? connectorStatistics : null);
138         HttpReadListener readListener = new HttpReadListener(connection, parser, statisticsEnabled ? connectorStatistics : null);
139
140
141         if (buffer != null) {
142             if (buffer.getBuffer().hasRemaining()) {
143                 connection.setExtraBytes(buffer);
144             } else {
145                 buffer.close();
146             }
147         }
148         if (connectorStatistics != null && statisticsEnabled) {
149             connectorStatistics.incrementConnectionCount();
150         }
151
152         connections.add(connection);
153         connection.addCloseListener(new ServerConnection.CloseListener() {
154             @Override
155             public void closed(ServerConnection c) {
156                 connections.remove(connection);
157             }
158         });
159         connection.setReadListener(readListener);
160         readListener.newRequest();
161         channel.getSourceChannel().setReadListener(readListener);
162         readListener.handleEvent(channel.getSourceChannel());
163     }
164
165     @Override
166     public HttpHandler getRootHandler() {
167         return rootHandler;
168     }
169
170     @Override
171     public void setRootHandler(final HttpHandler rootHandler) {
172         this.rootHandler = rootHandler;
173     }
174
175     @Override
176     public OptionMap getUndertowOptions() {
177         return undertowOptions;
178     }
179
180     @Override
181     public void setUndertowOptions(final OptionMap undertowOptions) {
182         if (undertowOptions == null) {
183             throw UndertowMessages.MESSAGES.argumentCannotBeNull("undertowOptions");
184         }
185         this.undertowOptions = undertowOptions;
186         this.parser = HttpRequestParser.instance(undertowOptions);
187         statisticsEnabled = undertowOptions.get(UndertowOptions.ENABLE_CONNECTOR_STATISTICS, false);
188     }
189
190     @Override
191     public ByteBufferPool getBufferPool() {
192         return bufferPool;
193     }
194
195     @Override
196     public ConnectorStatistics getConnectorStatistics() {
197         if (statisticsEnabled) {
198             return connectorStatistics;
199         }
200         return null;
201     }
202
203     @Override
204     public void closeConnections() {
205         for(HttpServerConnection i : connections) {
206             i.getIoThread().execute(new Runnable() {
207                 @Override
208                 public void run() {
209                     IoUtils.safeClose(i);
210                 }
211             });
212         }
213     }
214
215 }
216