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 io.undertow.UndertowMessages;
22 import io.undertow.conduits.ReadDataStreamSourceConduit;
23 import io.undertow.server.AbstractServerConnection;
24 import io.undertow.server.ConduitWrapper;
25 import io.undertow.server.ConnectionSSLSessionInfo;
26 import io.undertow.server.ConnectorStatisticsImpl;
27 import io.undertow.server.Connectors;
28 import io.undertow.server.ExchangeCompletionListener;
29 import io.undertow.server.HttpHandler;
30 import io.undertow.server.HttpServerExchange;
31 import io.undertow.server.HttpUpgradeListener;
32 import io.undertow.server.SSLSessionInfo;
33 import io.undertow.server.ServerConnection;
34 import io.undertow.util.ConduitFactory;
35 import io.undertow.util.Headers;
36 import io.undertow.util.HttpString;
37 import io.undertow.util.ImmediatePooledByteBuffer;
38 import io.undertow.util.Methods;
39
40 import org.xnio.IoUtils;
41 import org.xnio.OptionMap;
42 import io.undertow.connector.ByteBufferPool;
43 import io.undertow.connector.PooledByteBuffer;
44 import org.xnio.StreamConnection;
45 import org.xnio.channels.SslChannel;
46 import org.xnio.conduits.StreamSinkConduit;
47
48 import javax.net.ssl.SSLSession;
49 import java.nio.ByteBuffer;
50
51 /**
52  * A server-side HTTP connection.
53  * <p>
54  * Note that the lifecycle of the server connection is tied to the underlying TCP connection. Even if the channel
55  * is upgraded the connection is not considered closed until the upgraded channel is closed.
56  *
57  * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
58  */

59 public final class HttpServerConnection extends AbstractServerConnection {
60
61     private SSLSessionInfo sslSessionInfo;
62     private HttpReadListener readListener;
63     private PipeliningBufferingStreamSinkConduit pipelineBuffer;
64     private HttpResponseConduit responseConduit;
65     private ServerFixedLengthStreamSinkConduit fixedLengthStreamSinkConduit;
66     private ReadDataStreamSourceConduit readDataStreamSourceConduit;
67
68     private HttpUpgradeListener upgradeListener;
69     private boolean connectHandled;
70
71     public HttpServerConnection(StreamConnection channel, final ByteBufferPool bufferPool, final HttpHandler rootHandler, final OptionMap undertowOptions, final int bufferSize, final ConnectorStatisticsImpl connectorStatistics) {
72         super(channel, bufferPool, rootHandler, undertowOptions, bufferSize);
73         if (channel instanceof SslChannel) {
74             sslSessionInfo = new ConnectionSSLSessionInfo(((SslChannel) channel), this);
75         }
76         this.responseConduit = new HttpResponseConduit(channel.getSinkChannel().getConduit(), bufferPool, this);
77
78         fixedLengthStreamSinkConduit = new ServerFixedLengthStreamSinkConduit(responseConduit, falsefalse);
79         readDataStreamSourceConduit = new ReadDataStreamSourceConduit(channel.getSourceChannel().getConduit(), this);
80         //todo: do this without an allocation
81         addCloseListener(new CloseListener() {
82             @Override
83             public void closed(ServerConnection connection) {
84                 if(connectorStatistics != null) {
85                     connectorStatistics.decrementConnectionCount();
86                 }
87                 responseConduit.freeBuffers();
88             }
89         });
90     }
91
92     @Override
93     public HttpServerExchange sendOutOfBandResponse(HttpServerExchange exchange) {
94         if (exchange == null || !HttpContinue.requiresContinueResponse(exchange)) {
95             throw UndertowMessages.MESSAGES.outOfBandResponseOnlyAllowedFor100Continue();
96         }
97         final ConduitState state = resetChannel();
98         HttpServerExchange newExchange = new HttpServerExchange(this);
99         for (HttpString header : exchange.getRequestHeaders().getHeaderNames()) {
100             newExchange.getRequestHeaders().putAll(header, exchange.getRequestHeaders().get(header));
101         }
102         newExchange.setProtocol(exchange.getProtocol());
103         newExchange.setRequestMethod(exchange.getRequestMethod());
104         exchange.setRequestURI(exchange.getRequestURI(), exchange.isHostIncludedInRequestURI());
105         exchange.setRequestPath(exchange.getRequestPath());
106         exchange.setRelativePath(exchange.getRelativePath());
107         newExchange.getRequestHeaders().put(Headers.CONNECTION, Headers.KEEP_ALIVE.toString());
108         newExchange.getRequestHeaders().put(Headers.CONTENT_LENGTH, 0);
109         newExchange.setPersistent(true);
110
111         Connectors.terminateRequest(newExchange);
112         newExchange.addResponseWrapper(new ConduitWrapper<StreamSinkConduit>() {
113             @Override
114             public StreamSinkConduit wrap(ConduitFactory<StreamSinkConduit> factory, HttpServerExchange exchange) {
115
116                 HttpResponseConduit httpResponseConduit = new HttpResponseConduit(getSinkChannel().getConduit(), getByteBufferPool(), HttpServerConnection.this, exchange);
117                 exchange.addExchangeCompleteListener(new ExchangeCompletionListener() {
118                     @Override
119                     public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) {
120                         httpResponseConduit.freeContinueResponse();
121                         nextListener.proceed();
122                     }
123                 });
124                 ServerFixedLengthStreamSinkConduit fixed = new ServerFixedLengthStreamSinkConduit(httpResponseConduit, falsefalse);
125                 fixed.reset(0, exchange);
126                 return fixed;
127             }
128         });
129
130         //we restore the read channel immediately, as this out of band response has no read side
131         channel.getSourceChannel().setConduit(source(state));
132         newExchange.addExchangeCompleteListener(new ExchangeCompletionListener() {
133             @Override
134             public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) {
135                 restoreChannel(state);
136             }
137         });
138         return newExchange;
139     }
140
141     @Override
142     public boolean isContinueResponseSupported() {
143         return true;
144     }
145
146     @Override
147     public void terminateRequestChannel(HttpServerExchange exchange) {
148         if (!exchange.isPersistent()) {
149             IoUtils.safeClose(getChannel().getSourceChannel());
150         }
151     }
152
153     /**
154      * Pushes back the given data. This should only be used by transfer coding handlers that have read past
155      * the end of the request when handling pipelined requests
156      *
157      * @param unget The buffer to push back
158      */

159     public void ungetRequestBytes(final PooledByteBuffer unget) {
160         if (getExtraBytes() == null) {
161             setExtraBytes(unget);
162         } else {
163             PooledByteBuffer eb = getExtraBytes();
164             ByteBuffer buf = eb.getBuffer();
165             final ByteBuffer ugBuffer = unget.getBuffer();
166
167             if (ugBuffer.limit() - ugBuffer.remaining() > buf.remaining()) {
168                 //stuff the existing data after the data we are ungetting
169                 ugBuffer.compact();
170                 ugBuffer.put(buf);
171                 ugBuffer.flip();
172                 eb.close();
173                 setExtraBytes(unget);
174             } else {
175                 //TODO: this is horrible, but should not happen often
176                 final byte[] data = new byte[ugBuffer.remaining() + buf.remaining()];
177                 int first = ugBuffer.remaining();
178                 ugBuffer.get(data, 0, ugBuffer.remaining());
179                 buf.get(data, first, buf.remaining());
180                 eb.close();
181                 unget.close();
182                 final ByteBuffer newBuffer = ByteBuffer.wrap(data);
183                 setExtraBytes(new ImmediatePooledByteBuffer(newBuffer));
184             }
185         }
186     }
187
188     @Override
189     public SSLSessionInfo getSslSessionInfo() {
190         return sslSessionInfo;
191     }
192
193     @Override
194     public void setSslSessionInfo(SSLSessionInfo sessionInfo) {
195         this.sslSessionInfo = sessionInfo;
196     }
197
198     public SSLSession getSslSession() {
199         if (channel instanceof SslChannel) {
200             return ((SslChannel) channel).getSslSession();
201         }
202         return null;
203     }
204
205     @Override
206     protected StreamConnection upgradeChannel() {
207         clearChannel();
208         if (extraBytes != null) {
209             channel.getSourceChannel().setConduit(new ReadDataStreamSourceConduit(channel.getSourceChannel().getConduit(), this));
210         }
211         return channel;
212     }
213
214     @Override
215     protected StreamSinkConduit getSinkConduit(HttpServerExchange exchange, StreamSinkConduit conduit) {
216         if(exchange.getRequestMethod().equals(Methods.CONNECT) && !connectHandled) {
217             //make sure that any unhandled CONNECT requests result in a connection close
218             exchange.setPersistent(false);
219             exchange.getResponseHeaders().put(Headers.CONNECTION, "close");
220         }
221         return HttpTransferEncoding.createSinkConduit(exchange);
222     }
223
224     @Override
225     protected boolean isUpgradeSupported() {
226         return true;
227     }
228
229     @Override
230     protected boolean isConnectSupported() {
231         return true;
232     }
233
234     void setReadListener(HttpReadListener readListener) {
235         this.readListener = readListener;
236     }
237
238     @Override
239     protected void exchangeComplete(HttpServerExchange exchange) {
240         if(fixedLengthStreamSinkConduit != null) {
241             fixedLengthStreamSinkConduit.clearExchange();
242         }
243         if (pipelineBuffer == null) {
244             readListener.exchangeComplete(exchange);
245         } else {
246             pipelineBuffer.exchangeComplete(exchange);
247         }
248     }
249
250     HttpReadListener getReadListener() {
251         return readListener;
252     }
253
254     ReadDataStreamSourceConduit getReadDataStreamSourceConduit() {
255         return readDataStreamSourceConduit;
256     }
257
258     public PipeliningBufferingStreamSinkConduit getPipelineBuffer() {
259         return pipelineBuffer;
260     }
261
262     public HttpResponseConduit getResponseConduit() {
263         return responseConduit;
264     }
265
266     ServerFixedLengthStreamSinkConduit getFixedLengthStreamSinkConduit() {
267         return fixedLengthStreamSinkConduit;
268     }
269
270     protected HttpUpgradeListener getUpgradeListener() {
271         return upgradeListener;
272     }
273
274     @Override
275     protected void setUpgradeListener(HttpUpgradeListener upgradeListener) {
276         this.upgradeListener = upgradeListener;
277     }
278
279     @Override
280     protected void setConnectListener(HttpUpgradeListener connectListener) {
281         this.upgradeListener = connectListener;
282         connectHandled = true;
283     }
284
285     void setCurrentExchange(HttpServerExchange exchange) {
286         this.current = exchange;
287     }
288
289     public void setPipelineBuffer(PipeliningBufferingStreamSinkConduit pipelineBuffer) {
290         this.pipelineBuffer = pipelineBuffer;
291         this.responseConduit = new HttpResponseConduit(pipelineBuffer, bufferPool, this);
292         this.fixedLengthStreamSinkConduit = new ServerFixedLengthStreamSinkConduit(responseConduit, falsefalse);
293     }
294
295     @Override
296     public String getTransportProtocol() {
297         return "http/1.1";
298     }
299
300     @Override
301     public boolean isRequestTrailerFieldsSupported() {
302         if(current == null) {
303             return false;
304         }
305
306         String te = current.getRequestHeaders().getFirst(Headers.TRANSFER_ENCODING);
307         if(te == null) {
308             return false;
309         }
310         return te.equalsIgnoreCase(Headers.CHUNKED.toString());
311     }
312
313     boolean isConnectHandled() {
314         return connectHandled;
315     }
316 }
317