1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.searchers;
import com.yahoo.component.annotation.Inject;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
import com.yahoo.search.Searcher;
import com.yahoo.search.searchchain.Execution;
import java.util.concurrent.TimeUnit;
import java.util.function.LongSupplier;
/**
* Searcher which can enforce HTTP connection close based on query properties.
*
* <p>
* This searcher informs the client to close a persistent HTTP connection if the
* connection is older than the configured max lifetime. This is done by adding
* the "Connection" HTTP header with the value "Close" to the result.
* </p>
*
* <p>
* The searcher reads the query property "connectioncontrol.maxlifetime", which
* is an integer number of seconds, to get the value for maximum connection
* lifetime. Setting it to zero will enforce connection close independently of
* the age of the connection. Typical usage would be as follows:
* </p>
*
* <ol>
* <li>Add the ConnectionControlSearcher to the default search chain of your
* application. (It has no special ordering considerations.)</li>
*
* <li>For the default query profile of your application, set a reasonable value
* for "connectioncontrol.maxlifetime". The definition of reasonable will be
* highly application dependent, but it should always be less than the grace
* period when taking the container out of production traffic.</li>
*
* <li>Deploy application. The container will now inform clients to close
* connections/reconnect within the configured time limit.
* </ol>
*
* @author frodelu
* @author Steinar Knutsen
*/
public class ConnectionControlSearcher extends Searcher {
private final String simpleName = this.getClass().getSimpleName();
private final LongSupplier clock;
private static final CompoundName KEEPALIVE_MAXLIFETIMESECONDS = CompoundName.from("connectioncontrol.maxlifetime");
private static final String HTTP_CONNECTION_HEADER_NAME = "Connection";
private static final String HTTP_CONNECTION_CLOSE_ARGUMENT = "Close";
@Inject
public ConnectionControlSearcher() {
this(() -> System.currentTimeMillis());
}
private ConnectionControlSearcher(LongSupplier clock) {
this.clock = clock;
}
/**
* Create a searcher instance suitable for unit tests.
*
* @param clock a simulated or real clock behaving similarly to System.currentTimeMillis()
* @return a fully initialised instance
*/
public static ConnectionControlSearcher createTestInstance(LongSupplier clock) {
return new ConnectionControlSearcher(clock);
}
@Override
public Result search(Query query, Execution execution) {
Result result = execution.search(query);
query.trace(false, 3, simpleName, " updating headers.");
keepAliveProcessing(query, result);
return result;
}
/**
* If the HTTP connection has been alive for too long, set the header
* "Connection: Close" to tell the client to close the connection after this
* request.
*/
private void keepAliveProcessing(Query query, Result result) {
int maxLifetimeSeconds = query.properties().getInteger(KEEPALIVE_MAXLIFETIMESECONDS, -1);
if (maxLifetimeSeconds < 0) {
return;
} else if (maxLifetimeSeconds == 0) {
result.getHeaders(true).put(HTTP_CONNECTION_HEADER_NAME, HTTP_CONNECTION_CLOSE_ARGUMENT);
query.trace(false, 5, simpleName, ": Max HTTP connection lifetime set to 0; adding \"", HTTP_CONNECTION_HEADER_NAME,
": ", HTTP_CONNECTION_CLOSE_ARGUMENT, "\" header");
} else {
setCloseIfLifetimeExceeded(query, result, maxLifetimeSeconds);
}
}
private void setCloseIfLifetimeExceeded(Query query, Result result, int maxLifetimeSeconds) {
if (query.getHttpRequest() == null) {
query.trace(false, 5, simpleName, " got max lifetime = ", maxLifetimeSeconds,
", but got no JDisc request. Setting no header.");
return;
}
final long connectedAtMillis = query.getHttpRequest().getConnectedAt(TimeUnit.MILLISECONDS);
final long maxLifeTimeMillis = maxLifetimeSeconds * 1000L;
if (connectedAtMillis + maxLifeTimeMillis < clock.getAsLong()) {
result.getHeaders(true).put(HTTP_CONNECTION_HEADER_NAME, HTTP_CONNECTION_CLOSE_ARGUMENT);
query.trace(false, 5, simpleName, ": Max HTTP connection lifetime (", maxLifetimeSeconds, ") exceeded; adding \"",
HTTP_CONNECTION_HEADER_NAME, ": ", HTTP_CONNECTION_CLOSE_ARGUMENT, "\" header");
}
}
}
|