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;
20
21 import io.undertow.UndertowLogger;
22 import io.undertow.server.ServerConnection;
23 import io.undertow.util.WorkerUtils;
24 import org.xnio.IoUtils;
25 import org.xnio.XnioExecutor;
26 import org.xnio.channels.ConnectedChannel;
27
28 import java.io.Closeable;
29 import java.util.concurrent.RejectedExecutionException;
30 import java.util.concurrent.TimeUnit;
31
32 /**
33  * Wrapper for parse timeout.
34  *
35  * @author Sebastian Laskawiec
36  * @see io.undertow.UndertowOptions#REQUEST_PARSE_TIMEOUT
37  */

38 public final class ParseTimeoutUpdater implements Runnable, ServerConnection.CloseListener, Closeable {
39
40     private final ConnectedChannel connection;
41     private final long requestParseTimeout;
42     private final long requestIdleTimeout;
43     private volatile XnioExecutor.Key handle;
44     private volatile long expireTime = -1;
45     private volatile boolean parsing = false;
46
47     //we add 50ms to the timeout to make sure the underlying channel has actually timed out
48     private static final int FUZZ_FACTOR = 50;
49
50     private final Runnable closeTask;
51
52
53     /**
54      * Creates new instance of ParseTimeoutSourceConduit.
55      *  @param channel             Channel which will be closed in case of timeout.
56      * @param requestParseTimeout Timeout value. Negative value will indicate that this updated is disabled.
57      * @param requestIdleTimeout
58      */

59     public ParseTimeoutUpdater(ConnectedChannel channel, long requestParseTimeout, long requestIdleTimeout) {
60         this(channel, requestParseTimeout, requestIdleTimeout, new Runnable() {
61             @Override
62             public void run() {
63                 IoUtils.safeClose(channel);
64             }
65         });
66     }
67
68     /**
69      * Creates new instance of ParseTimeoutSourceConduit.
70      *  @param channel             Channel which will be closed in case of timeout.
71      * @param requestParseTimeout Timeout value. Negative value will indicate that this updated is disabled.
72      * @param requestIdleTimeout
73      */

74     public ParseTimeoutUpdater(ConnectedChannel channel, long requestParseTimeout, long requestIdleTimeout, Runnable closeTask) {
75         this.connection = channel;
76         this.requestParseTimeout = requestParseTimeout;
77         this.requestIdleTimeout = requestIdleTimeout;
78         this.closeTask = closeTask;
79     }
80     /**
81      * Called when the connection goes idle
82      */

83     public void connectionIdle() {
84         parsing = false;
85         handleSchedule(requestIdleTimeout);
86     }
87
88     private void handleSchedule(long timeout) {
89         //no current timeout, clear the expire time
90         if(timeout == -1) {
91             this.expireTime = -1;
92             return;
93         }
94         //calculate the new expire time
95         long newExpireTime = System.currentTimeMillis() + timeout;
96         long oldExpireTime = this.expireTime;
97         this.expireTime = newExpireTime;
98         //if the new one is less than the current one we need to schedule a new timer, so cancel the old one
99         if(newExpireTime < oldExpireTime) {
100             if(handle != null) {
101                 handle.remove();
102                 handle = null;
103             }
104         }
105         if(handle == null) {
106             try {
107                 handle = WorkerUtils.executeAfter(connection.getIoThread(), this, timeout + FUZZ_FACTOR, TimeUnit.MILLISECONDS);
108             } catch (RejectedExecutionException e) {
109                 UndertowLogger.REQUEST_LOGGER.debug("Failed to schedule parse timeout, server is probably shutting down", e);
110             }
111         }
112     }
113
114     /**
115      * Called when a request is received, however it is not parsed in a single read() call. This starts a timer,
116      * and if the request is not parsed within this time then the connection is closed.
117      *
118      */

119     public void failedParse() {
120         if(!parsing) {
121             parsing = true;
122             handleSchedule(requestParseTimeout);
123         }
124     }
125
126     /**
127      * Cancels timeout countdown.
128      * <p>
129      * Should be called after parsing is complete (to avoid closing connection during other activities).
130      * </p>
131      */

132     public void requestStarted() {
133         expireTime = -1;
134         parsing = false;
135     }
136
137     @Override
138     public void run() {
139         if(!connection.isOpen()) {
140             return;
141         }
142         handle = null;
143         if (expireTime > 0) { //timeout is not active
144             long now = System.currentTimeMillis();
145             if(expireTime > now) {
146                 handle = WorkerUtils.executeAfter(connection.getIoThread(), this, (expireTime - now) + FUZZ_FACTOR, TimeUnit.MILLISECONDS);
147             } else {
148                 if(parsing) {
149                     UndertowLogger.REQUEST_LOGGER.parseRequestTimedOut(connection.getPeerAddress());
150                 } else {
151                     UndertowLogger.REQUEST_LOGGER.debugf("Timing out idle connection from %s", connection.getPeerAddress());
152                 }
153                 closeTask.run();
154             }
155         }
156     }
157
158     @Override
159     public void closed(ServerConnection connection) {
160         close();
161     }
162
163     public void close() {
164         if(handle != null) {
165             handle.remove();
166             handle = null;
167         }
168     }
169 }
170