1
18
19 package io.undertow.server.protocol.http;
20
21 import io.undertow.UndertowLogger;
22 import io.undertow.UndertowOptions;
23 import io.undertow.conduits.ChunkedStreamSinkConduit;
24 import io.undertow.conduits.ChunkedStreamSourceConduit;
25 import io.undertow.conduits.ConduitListener;
26 import io.undertow.conduits.FinishableStreamSinkConduit;
27 import io.undertow.conduits.FixedLengthStreamSourceConduit;
28 import io.undertow.conduits.HeadStreamSinkConduit;
29 import io.undertow.conduits.PreChunkedStreamSinkConduit;
30 import io.undertow.server.Connectors;
31 import io.undertow.server.HttpServerExchange;
32 import io.undertow.util.DateUtils;
33 import io.undertow.util.HeaderMap;
34 import io.undertow.util.Headers;
35 import io.undertow.util.HttpString;
36 import io.undertow.util.Methods;
37 import io.undertow.util.StatusCodes;
38
39 import org.jboss.logging.Logger;
40 import org.xnio.conduits.ConduitStreamSourceChannel;
41 import org.xnio.conduits.StreamSinkConduit;
42 import org.xnio.conduits.StreamSourceConduit;
43
44
53 class HttpTransferEncoding {
54
55 private static final Logger log = Logger.getLogger("io.undertow.server.handler.transfer-encoding");
56
57
58
61 private HttpTransferEncoding() {
62 }
63
64 public static void setupRequest(final HttpServerExchange exchange) {
65 final HeaderMap requestHeaders = exchange.getRequestHeaders();
66 final String connectionHeader = requestHeaders.getFirst(Headers.CONNECTION);
67 final String transferEncodingHeader = requestHeaders.getLast(Headers.TRANSFER_ENCODING);
68 final String contentLengthHeader = requestHeaders.getFirst(Headers.CONTENT_LENGTH);
69
70 final HttpServerConnection connection = (HttpServerConnection) exchange.getConnection();
71
72 PipeliningBufferingStreamSinkConduit pipeliningBuffer = connection.getPipelineBuffer();
73 if (pipeliningBuffer != null) {
74 pipeliningBuffer.setupPipelineBuffer(exchange);
75 }
76 ConduitStreamSourceChannel sourceChannel = connection.getChannel().getSourceChannel();
77 sourceChannel.setConduit(connection.getReadDataStreamSourceConduit());
78
79 boolean persistentConnection = persistentConnection(exchange, connectionHeader);
80
81 if (transferEncodingHeader == null && contentLengthHeader == null) {
82 if (persistentConnection
83 && connection.getExtraBytes() != null
84 && pipeliningBuffer == null
85 && connection.getUndertowOptions().get(UndertowOptions.BUFFER_PIPELINED_DATA, false)) {
86 pipeliningBuffer = new PipeliningBufferingStreamSinkConduit(connection.getOriginalSinkConduit(), connection.getByteBufferPool());
87 connection.setPipelineBuffer(pipeliningBuffer);
88 pipeliningBuffer.setupPipelineBuffer(exchange);
89 }
90
91 Connectors.terminateRequest(exchange);
92 } else {
93 persistentConnection = handleRequestEncoding(exchange, transferEncodingHeader, contentLengthHeader, connection, pipeliningBuffer, persistentConnection);
94 }
95
96 exchange.setPersistent(persistentConnection);
97
98 if (!exchange.isRequestComplete() || connection.getExtraBytes() != null) {
99
100 sourceChannel.setReadListener(null);
101 sourceChannel.suspendReads();
102 }
103
104 }
105
106 private static boolean handleRequestEncoding(final HttpServerExchange exchange, String transferEncodingHeader, String contentLengthHeader, HttpServerConnection connection, PipeliningBufferingStreamSinkConduit pipeliningBuffer, boolean persistentConnection) {
107
108 HttpString transferEncoding = Headers.IDENTITY;
109 if (transferEncodingHeader != null) {
110 transferEncoding = new HttpString(transferEncodingHeader);
111 }
112 if (transferEncodingHeader != null && !transferEncoding.equals(Headers.IDENTITY)) {
113 ConduitStreamSourceChannel sourceChannel = ((HttpServerConnection) exchange.getConnection()).getChannel().getSourceChannel();
114 sourceChannel.setConduit(new ChunkedStreamSourceConduit(sourceChannel.getConduit(), exchange, chunkedDrainListener(exchange)));
115 } else if (contentLengthHeader != null) {
116 final long contentLength;
117 contentLength = parsePositiveLong(contentLengthHeader);
118 if (contentLength == 0L) {
119 log.trace("No content, starting next request");
120
121 Connectors.terminateRequest(exchange);
122 } else {
123
124 ConduitStreamSourceChannel sourceChannel = ((HttpServerConnection) exchange.getConnection()).getChannel().getSourceChannel();
125 sourceChannel.setConduit(fixedLengthStreamSourceConduitWrapper(contentLength, sourceChannel.getConduit(), exchange));
126 }
127 } else if (transferEncodingHeader != null) {
128
129 log.trace("Connection not persistent (no content length and identity transfer encoding)");
130
131 persistentConnection = false;
132 } else if (persistentConnection) {
133
134
135 if (connection.getExtraBytes() != null
136 && pipeliningBuffer == null
137 && connection.getUndertowOptions().get(UndertowOptions.BUFFER_PIPELINED_DATA, false)) {
138 pipeliningBuffer = new PipeliningBufferingStreamSinkConduit(connection.getOriginalSinkConduit(), connection.getByteBufferPool());
139 connection.setPipelineBuffer(pipeliningBuffer);
140 pipeliningBuffer.setupPipelineBuffer(exchange);
141 }
142
143
144 Connectors.terminateRequest(exchange);
145 } else {
146
147
148 Connectors.terminateRequest(exchange);
149 }
150 return persistentConnection;
151 }
152
153 private static boolean persistentConnection(HttpServerExchange exchange, String connectionHeader) {
154 if (exchange.isHttp11()) {
155 return !(connectionHeader != null && Headers.CLOSE.equalToString(connectionHeader));
156 } else if (exchange.isHttp10()) {
157 if (connectionHeader != null) {
158 if (Headers.KEEP_ALIVE.equals(new HttpString(connectionHeader))) {
159 return true;
160 }
161 }
162 }
163 log.trace("Connection not persistent");
164 return false;
165 }
166
167 private static StreamSourceConduit fixedLengthStreamSourceConduitWrapper(final long contentLength, final StreamSourceConduit conduit, final HttpServerExchange exchange) {
168 return new FixedLengthStreamSourceConduit(conduit, contentLength, fixedLengthDrainListener(exchange), exchange);
169 }
170
171 private static ConduitListener<FixedLengthStreamSourceConduit> fixedLengthDrainListener(final HttpServerExchange exchange) {
172 return new ConduitListener<FixedLengthStreamSourceConduit>() {
173 public void handleEvent(final FixedLengthStreamSourceConduit fixedLengthConduit) {
174 long remaining = fixedLengthConduit.getRemaining();
175 if (remaining > 0L) {
176 UndertowLogger.REQUEST_LOGGER.requestWasNotFullyConsumed();
177 exchange.setPersistent(false);
178 }
179 Connectors.terminateRequest(exchange);
180 }
181 };
182 }
183
184 private static ConduitListener<ChunkedStreamSourceConduit> chunkedDrainListener(final HttpServerExchange exchange) {
185 return new ConduitListener<ChunkedStreamSourceConduit>() {
186 public void handleEvent(final ChunkedStreamSourceConduit chunkedStreamSourceConduit) {
187 if (!chunkedStreamSourceConduit.isFinished()) {
188 UndertowLogger.REQUEST_LOGGER.requestWasNotFullyConsumed();
189 exchange.setPersistent(false);
190 }
191 Connectors.terminateRequest(exchange);
192 }
193 };
194 }
195
196 private static ConduitListener<StreamSinkConduit> terminateResponseListener(final HttpServerExchange exchange) {
197 return new ConduitListener<StreamSinkConduit>() {
198 public void handleEvent(final StreamSinkConduit channel) {
199 Connectors.terminateResponse(exchange);
200 }
201 };
202 }
203
204 static StreamSinkConduit createSinkConduit(final HttpServerExchange exchange) {
205 DateUtils.addDateHeaderIfRequired(exchange);
206
207 boolean headRequest = exchange.getRequestMethod().equals(Methods.HEAD);
208 HttpServerConnection serverConnection = (HttpServerConnection) exchange.getConnection();
209
210 HttpResponseConduit responseConduit = serverConnection.getResponseConduit();
211 responseConduit.reset(exchange);
212 StreamSinkConduit channel = responseConduit;
213 if (headRequest) {
214
215
216
217 channel = new HeadStreamSinkConduit(channel, terminateResponseListener(exchange));
218 } else if(!Connectors.isEntityBodyAllowed(exchange)) {
219
220 exchange.getResponseHeaders().remove(Headers.CONTENT_LENGTH);
221 exchange.getResponseHeaders().remove(Headers.TRANSFER_ENCODING);
222 channel = new HeadStreamSinkConduit(channel, terminateResponseListener(exchange));
223 return channel;
224 }
225
226 final HeaderMap responseHeaders = exchange.getResponseHeaders();
227
228 String connection = responseHeaders.getFirst(Headers.CONNECTION);
229 if(exchange.getStatusCode() == StatusCodes.EXPECTATION_FAILED) {
230
231
232 exchange.setPersistent(false);
233 }
234 if (!exchange.isPersistent()) {
235 responseHeaders.put(Headers.CONNECTION, Headers.CLOSE.toString());
236 } else if (exchange.isPersistent() && connection != null) {
237 if (HttpString.tryFromString(connection).equals(Headers.CLOSE)) {
238 exchange.setPersistent(false);
239 }
240 } else if (exchange.getConnection().getUndertowOptions().get(UndertowOptions.ALWAYS_SET_KEEP_ALIVE, true)) {
241 responseHeaders.put(Headers.CONNECTION, Headers.KEEP_ALIVE.toString());
242 }
243
244 final String transferEncodingHeader = responseHeaders.getLast(Headers.TRANSFER_ENCODING);
245 if(transferEncodingHeader == null) {
246 final String contentLengthHeader = responseHeaders.getFirst(Headers.CONTENT_LENGTH);
247 if (contentLengthHeader != null) {
248 StreamSinkConduit res = handleFixedLength(exchange, headRequest, channel, responseHeaders, contentLengthHeader, serverConnection);
249 if (res != null) {
250 return res;
251 }
252 }
253 } else {
254 responseHeaders.remove(Headers.CONTENT_LENGTH);
255 }
256 return handleResponseConduit(exchange, headRequest, channel, responseHeaders, terminateResponseListener(exchange), transferEncodingHeader);
257 }
258
259 private static StreamSinkConduit handleFixedLength(HttpServerExchange exchange, boolean headRequest, StreamSinkConduit channel, HeaderMap responseHeaders, String contentLengthHeader, HttpServerConnection connection) {
260 try {
261 final long contentLength = parsePositiveLong(contentLengthHeader);
262 if (headRequest) {
263 return channel;
264 }
265
266 ServerFixedLengthStreamSinkConduit fixed = connection.getFixedLengthStreamSinkConduit();
267 fixed.reset(contentLength, exchange);
268 return fixed;
269 } catch (NumberFormatException e) {
270
271 responseHeaders.remove(Headers.CONTENT_LENGTH);
272 }
273 return null;
274 }
275
276 private static StreamSinkConduit handleResponseConduit(HttpServerExchange exchange, boolean headRequest, StreamSinkConduit channel, HeaderMap responseHeaders, ConduitListener<StreamSinkConduit> finishListener, String transferEncodingHeader) {
277
278 if (transferEncodingHeader == null) {
279 if (exchange.isHttp11()) {
280 if (exchange.isPersistent()) {
281 responseHeaders.put(Headers.TRANSFER_ENCODING, Headers.CHUNKED.toString());
282
283 if (headRequest) {
284 return channel;
285 }
286 return new ChunkedStreamSinkConduit(channel, exchange.getConnection().getByteBufferPool(), true, !exchange.isPersistent(), responseHeaders, finishListener, exchange);
287 } else {
288 if (headRequest) {
289 return channel;
290 }
291 return new FinishableStreamSinkConduit(channel, finishListener);
292 }
293 } else {
294 exchange.setPersistent(false);
295 responseHeaders.put(Headers.CONNECTION, Headers.CLOSE.toString());
296 if (headRequest) {
297 return channel;
298 }
299 return new FinishableStreamSinkConduit(channel, finishListener);
300 }
301 } else {
302
303
304 return handleExplicitTransferEncoding(exchange, channel, finishListener, responseHeaders, transferEncodingHeader, headRequest);
305 }
306 }
307
308 private static StreamSinkConduit handleExplicitTransferEncoding(HttpServerExchange exchange, StreamSinkConduit channel, ConduitListener<StreamSinkConduit> finishListener, HeaderMap responseHeaders, String transferEncodingHeader, boolean headRequest) {
309 HttpString transferEncoding = new HttpString(transferEncodingHeader);
310 if (transferEncoding.equals(Headers.CHUNKED)) {
311 if (headRequest) {
312 return channel;
313 }
314 Boolean preChunked = exchange.getAttachment(HttpAttachments.PRE_CHUNKED_RESPONSE);
315 if(preChunked != null && preChunked) {
316 return new PreChunkedStreamSinkConduit(channel, finishListener, exchange);
317 } else {
318 return new ChunkedStreamSinkConduit(channel, exchange.getConnection().getByteBufferPool(), true, !exchange.isPersistent(), responseHeaders, finishListener, exchange);
319 }
320 } else {
321
322 if (headRequest) {
323 return channel;
324 }
325 log.trace("Cancelling persistence because response is identity with no content length");
326
327 exchange.setPersistent(false);
328 responseHeaders.put(Headers.CONNECTION, Headers.CLOSE.toString());
329 return new FinishableStreamSinkConduit(channel, terminateResponseListener(exchange));
330 }
331 }
332
333
339 public static long parsePositiveLong(String str) {
340 long value = 0;
341 final int length = str.length();
342
343 if (length == 0) {
344 throw new NumberFormatException(str);
345 }
346
347 long multiplier = 1;
348 for (int i = length - 1; i >= 0; --i) {
349 char c = str.charAt(i);
350
351 if (c < '0' || c > '9') {
352 throw new NumberFormatException(str);
353 }
354 long digit = c - '0';
355 value += digit * multiplier;
356 multiplier *= 10;
357 }
358 return value;
359 }
360
361 }
362