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.handlers;
20
21 import java.io.IOException;
22 import java.nio.ByteBuffer;
23 import java.nio.channels.FileChannel;
24 import java.util.concurrent.TimeUnit;
25
26 import org.xnio.channels.StreamSinkChannel;
27 import org.xnio.conduits.AbstractStreamSourceConduit;
28 import org.xnio.conduits.StreamSourceConduit;
29
30 import io.undertow.server.ConduitWrapper;
31 import io.undertow.server.Connectors;
32 import io.undertow.server.HttpHandler;
33 import io.undertow.server.HttpServerExchange;
34 import io.undertow.server.ResponseCommitListener;
35 import io.undertow.server.protocol.http.HttpContinue;
36 import io.undertow.util.ConduitFactory;
37 import io.undertow.util.StatusCodes;
38
39 /**
40  * Handler for requests that require 100-continue responses. If an attempt is made to read from the source
41  * channel then a 100 continue response is sent.
42  *
43  * @author Stuart Douglas
44  */

45 public class HttpContinueReadHandler implements HttpHandler {
46
47     private static final ConduitWrapper<StreamSourceConduit> WRAPPER = new ConduitWrapper<StreamSourceConduit>() {
48         @Override
49         public StreamSourceConduit wrap(final ConduitFactory<StreamSourceConduit> factory, final HttpServerExchange exchange) {
50             if (exchange.isRequestChannelAvailable() && !exchange.isResponseStarted()) {
51                 return new ContinueConduit(factory.create(), exchange);
52             }
53             return factory.create();
54         }
55     };
56
57     private final HttpHandler handler;
58
59     public HttpContinueReadHandler(final HttpHandler handler) {
60         this.handler = handler;
61     }
62
63     @Override
64     public void handleRequest(final HttpServerExchange exchange) throws Exception {
65         if (HttpContinue.requiresContinueResponse(exchange)) {
66             exchange.addRequestWrapper(WRAPPER);
67             exchange.addResponseCommitListener(new ResponseCommitListener() {
68                 @Override
69                 public void beforeCommit(HttpServerExchange exchange) {
70                     //we are writing the response, and have not read the request then we mark this as non-persistent
71                     if (!HttpContinue.isContinueResponseSent(exchange)) {
72                         exchange.setPersistent(false);
73                         //we also kill the request channel, because it is unusable now
74                         exchange.getConnection().terminateRequestChannel(exchange);
75                     }
76                 }
77             });
78         }
79         handler.handleRequest(exchange);
80     }
81
82     private static final class ContinueConduit extends AbstractStreamSourceConduit<StreamSourceConduit> implements StreamSourceConduit {
83
84         private boolean sent = false;
85         private HttpContinue.ContinueResponseSender response = null;
86         private final HttpServerExchange exchange;
87
88
89         protected ContinueConduit(final StreamSourceConduit next, final HttpServerExchange exchange) {
90             super(next);
91             this.exchange = exchange;
92         }
93
94         @Override
95         public long transferTo(final long position, final long count, final FileChannel target) throws IOException {
96             if (exchange.getStatusCode() == StatusCodes.EXPECTATION_FAILED) {
97                 //rejected
98                 Connectors.terminateRequest(exchange);
99                 return -1;
100             }
101             if (!sent) {
102                 sent = true;
103                 response = HttpContinue.createResponseSender(exchange);
104             }
105             if (response != null) {
106                 if (!response.send()) {
107                     return 0;
108                 }
109                 response = null;
110             }
111             return super.transferTo(position, count, target);
112         }
113
114         @Override
115         public long transferTo(final long count, final ByteBuffer throughBuffer, final StreamSinkChannel target) throws IOException {
116             if (exchange.getStatusCode() == StatusCodes.EXPECTATION_FAILED) {
117                 //rejected
118                 Connectors.terminateRequest(exchange);
119                 return -1;
120             }
121             if (!sent) {
122                 sent = true;
123                 response = HttpContinue.createResponseSender(exchange);
124             }
125             if (response != null) {
126                 if (!response.send()) {
127                     return 0;
128                 }
129                 response = null;
130             }
131             return super.transferTo(count, throughBuffer, target);
132         }
133
134         @Override
135         public int read(final ByteBuffer dst) throws IOException {
136             if (exchange.getStatusCode() == StatusCodes.EXPECTATION_FAILED) {
137                 //rejected
138                 Connectors.terminateRequest(exchange);
139                 return -1;
140             }
141             if (!sent) {
142                 sent = true;
143                 response = HttpContinue.createResponseSender(exchange);
144             }
145             if (response != null) {
146                 if (!response.send()) {
147                     return 0;
148                 }
149                 response = null;
150             }
151             return super.read(dst);
152         }
153
154         @Override
155         public long read(final ByteBuffer[] dsts, final int offs, final int len) throws IOException {
156             if (exchange.getStatusCode() == StatusCodes.EXPECTATION_FAILED) {
157                 //rejected
158                 Connectors.terminateRequest(exchange);
159                 return -1;
160             }
161             if (!sent) {
162                 sent = true;
163                 response = HttpContinue.createResponseSender(exchange);
164             }
165             if (response != null) {
166                 if (!response.send()) {
167                     return 0;
168                 }
169                 response = null;
170             }
171             return super.read(dsts, offs, len);
172         }
173
174         @Override
175         public void awaitReadable(final long time, final TimeUnit timeUnit) throws IOException {
176             if (exchange.getStatusCode() == StatusCodes.EXPECTATION_FAILED) {
177                 //rejected
178                 return;
179             }
180             if (!sent) {
181                 sent = true;
182                 response = HttpContinue.createResponseSender(exchange);
183             }
184             long exitTime = System.currentTimeMillis() + timeUnit.toMillis(time);
185             if (response != null) {
186                 while (!response.send()) {
187                     long currentTime = System.currentTimeMillis();
188                     if (currentTime > exitTime) {
189                         return;
190                     }
191                     response.awaitWritable(exitTime - currentTime, TimeUnit.MILLISECONDS);
192                 }
193                 response = null;
194             }
195
196             long currentTime = System.currentTimeMillis();
197             super.awaitReadable(exitTime - currentTime, TimeUnit.MILLISECONDS);
198         }
199
200         @Override
201         public void awaitReadable() throws IOException {
202             if (exchange.getStatusCode() == StatusCodes.EXPECTATION_FAILED) {
203                 //rejected
204                 return;
205             }
206             if (!sent) {
207                 sent = true;
208                 response = HttpContinue.createResponseSender(exchange);
209             }
210             if (response != null) {
211                 while (!response.send()) {
212                     response.awaitWritable();
213                 }
214                 response = null;
215             }
216             super.awaitReadable();
217         }
218     }
219 }
220