Skip to main content
summaryrefslogtreecommitdiffstats
blob: 0eb8ca0efdc912980689172a1605fd22bee1c59d (plain) (blame)
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
//
//  ========================================================================
//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.continuation;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;

/* ------------------------------------------------------------ */
/**
 * Continuation.
 * 
 * A continuation is a mechanism by which a HTTP Request can be suspended and
 * restarted after a timeout or an asynchronous event has occurred.
 * <p>
 * The continuation mechanism is a portable mechanism that will work 
 * asynchronously without additional configuration of all jetty-7, 
 * jetty-8 and Servlet 3.0 containers.   With the addition of 
 * the {@link ContinuationFilter}, the mechanism will also work
 * asynchronously on jetty-6 and non-asynchronously on any 
 * servlet 2.5 container.
 * <p>
 * The Continuation API is a simplification of the richer async API
 * provided by the servlet-3.0 and an enhancement of the continuation
 * API that was introduced with jetty-6. 
 * </p>
 * <h1>Continuation Usage</h1>
 * <p>
 * A continuation object is obtained for a request by calling the 
 * factory method {@link ContinuationSupport#getContinuation(ServletRequest)}.
 * The continuation type returned will depend on the servlet container
 * being used.
 * </p> 
 * <p>
 * There are two distinct style of operation of the continuation API.
 * </p>
 * <h3>Suspend/Resume Usage</h3> 
 * <p>The suspend/resume style is used when a servlet and/or
 * filter is used to generate the response after a asynchronous wait that is
 * terminated by an asynchronous handler.
 * </p>
 * <pre>
 * <b>Filter/Servlet:</b>
 *   // if we need to get asynchronous results
 *   Object results = request.getAttribute("results);
 *   if (results==null)
 *   {
 *     Continuation continuation = ContinuationSupport.getContinuation(request);
 *     continuation.suspend();
 *     myAsyncHandler.register(continuation);
 *     return; // or continuation.undispatch();
 *   }
 * 
 * async wait ...
 * 
 * <b>Async Handler:</b>
 *   // when the waited for event happens
 *   continuation.setAttribute("results",event);
 *   continuation.resume();
 *   
 * <b>Filter/Servlet:</b>
 *   // when the request is redispatched 
 *   if (results==null)
 *   {
 *     ... // see above
 *   }
 *   else
 *   {
 *     response.getOutputStream().write(process(results));
 *   }
 * </pre> 
 * <h3>Suspend/Complete Usage</h3> 
 * <p>
 * The suspend/complete style is used when an asynchronous handler is used to 
 * generate the response:
 * </p>
 * <pre>
 * <b>Filter/Servlet:</b>
 *   // when we want to enter asynchronous mode
 *   Continuation continuation = ContinuationSupport.getContinuation(request);
 *   continuation.suspend(response); // response may be wrapped
 *   myAsyncHandler.register(continuation);
 *   return; // or continuation.undispatch();
 *
 * <b>Wrapping Filter:</b>
 *   // any filter that had wrapped the response should be implemented like:
 *   try
 *   {
 *     chain.doFilter(request,wrappedResponse);
 *   }
 *   finally
 *   {
 *     if (!continuation.isResponseWrapped())
 *       wrappedResponse.finish()
 *     else
 *       continuation.addContinuationListener(myCompleteListener)
 *   }
 *
 * async wait ...
 *
 * <b>Async Handler:</b>
 *   // when the async event happens
 *   continuation.getServletResponse().getOutputStream().write(process(event));
 *   continuation.complete()
 * </pre>
 * 
 * <h1>Continuation Timeout</h1>
 * <p>
 * If a continuation is suspended, but neither {@link #complete()} or {@link #resume()} is
 * called during the period set by {@link #setTimeout(long)}, then the continuation will
 * expire and {@link #isExpired()} will return true. 
 * </p>
 * <p>
 * When a continuation expires, the {@link ContinuationListener#onTimeout(Continuation)}
 * method is called on any {@link ContinuationListener} that has been registered via the
 * {@link #addContinuationListener(ContinuationListener)} method. The onTimeout handlers 
 * may write a response and call {@link #complete()}. If {@link #complete()} is not called, 
 * then the container will redispatch the request as if {@link #resume()} had been called,
 * except that {@link #isExpired()} will be true and {@link #isResumed()} will be false.
 * </p>
 * 
 * @see ContinuationSupport
 * @see ContinuationListener
 * 
 */
public interface Continuation
{
    public final static String ATTRIBUTE = "org.eclipse.jetty.continuation";

    /* ------------------------------------------------------------ */
    /**
     * Set the continuation timeout.
     * 
     * @param timeoutMs
     *            The time in milliseconds to wait before expiring this
     *            continuation after a call to {@link #suspend()} or {@link #suspend(ServletResponse)}.
     *            A timeout of <=0 means the continuation will never expire.
     */
    void setTimeout(long timeoutMs);

    /* ------------------------------------------------------------ */
    /**
     * Suspend the processing of the request and associated
     * {@link ServletResponse}.
     * 
     * <p>
     * After this method has been called, the lifecycle of the request will be
     * extended beyond the return to the container from the
     * {@link Servlet#service(ServletRequest, ServletResponse)} method and
     * {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)}
     * calls. When a suspended request is returned to the container after
     * a dispatch, then the container will not commit the associated response
     * (unless an exception other than {@link ContinuationThrowable} is thrown).
     * </p>
     * 
     * <p>
     * When the thread calling the filter chain and/or servlet has returned to
     * the container with a suspended request, the thread is freed for other
     * tasks and the request is held until either:
     * <ul>
     * <li>a call to {@link #resume()}.</li>
     * <li>a call to {@link #complete()}.</li>
     * <li>the timeout expires.</li>
     * </ul>
     * <p>
     * Typically suspend with no arguments is uses when a call to {@link #resume()}
     * is expected. If a call to {@link #complete()} is expected, then the 
     * {@link #suspend(ServletResponse)} method should be used instead of this method.
     * </p>
     * 
     * @exception IllegalStateException
     *                If the request cannot be suspended
     */
    void suspend();
    
    
    /* ------------------------------------------------------------ */
    /**
     * Suspend the processing of the request and associated
     * {@link ServletResponse}.
     * 
     * <p>
     * After this method has been called, the lifecycle of the request will be
     * extended beyond the return to the container from the
     * {@link Servlet#service(ServletRequest, ServletResponse)} method and
     * {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)}
     * calls. When a suspended request is returned to the container after
     * a dispatch, then the container will not commit the associated response
     * (unless an exception other than {@link ContinuationThrowable} is thrown).
     * </p>
     * <p>
     * When the thread calling the filter chain and/or servlet has returned to
     * the container with a suspended request, the thread is freed for other
     * tasks and the request is held until either:
     * <ul>
     * <li>a call to {@link #resume()}.</li>
     * <li>a call to {@link #complete()}.</li>
     * <li>the timeout expires.</li>
     * </ul>
     * <p>
     * Typically suspend with a response argument is uses when a call to {@link #complete()}
     * is expected. If a call to {@link #resume()} is expected, then the 
     * {@link #suspend()} method should be used instead of this method.
     * </p>
     * <p>
     * Filters that may wrap the response object should check {@link #isResponseWrapped()}
     * to decide if they should destroy/finish the wrapper. If {@link #isResponseWrapped()}
     * returns true, then the wrapped request has been passed to the asynchronous
     * handler and the wrapper should not be destroyed/finished until after a call to 
     * {@link #complete()} (potentially using a {@link ContinuationListener#onComplete(Continuation)}
     * listener).
     * 
     * @param response The response to return via a call to {@link #getServletResponse()}
     * @exception IllegalStateException
     *                If the request cannot be suspended
     */
    void suspend(ServletResponse response);

    /* ------------------------------------------------------------ */
    /**
     * Resume a suspended request.
     * 
     * <p>
     * This method can be called by any thread that has been passed a reference
     * to a continuation. When called the request is redispatched to the
     * normal filter chain and servlet processing with {@link #isInitial()} false.
     * </p>
     * <p>
     * If resume is called before a suspended request is returned to the
     * container (ie the thread that called {@link #suspend()} is still
     * within the filter chain and/or servlet service method), then the resume
     * does not take effect until the call to the filter chain and/or servlet
     * returns to the container. In this case both {@link #isSuspended()} and
     * {@link #isResumed()} return true. Multiple calls to resume are ignored.
     * </p>
     * <p>
     * Typically resume() is used after a call to {@link #suspend()} with
     * no arguments. The dispatch after a resume call will use the original
     * request and response objects, even if {@link #suspend(ServletResponse)} 
     * had been passed a wrapped response.
     * </p>
     * 
     * @see #suspend()
     * @exception IllegalStateException if the request is not suspended.
     * 
     */
    void resume();

    /* ------------------------------------------------------------ */
    /**
     * Complete a suspended request.
     * 
     * <p>
     * This method can be called by any thread that has been passed a reference
     * to a suspended request. When a request is completed, the associated
     * response object committed and flushed. The request is not redispatched.
     * </p>
     * 
     * <p>
     * If complete is called before a suspended request is returned to the
     * container (ie the thread that called {@link #suspend()} is still
     * within the filter chain and/or servlet service method), then the complete
     * does not take effect until the call to the filter chain and/or servlet
     * returns to the container. In this case both {@link #isSuspended()} and
     * {@link #isResumed()} return true.
     * </p>
     * 
     * <p>
     * Typically resume() is used after a call to {@link #suspend(ServletResponse)} with
     * a possibly wrapped response. The async handler should use the response
     * provided by {@link #getServletResponse()} to write the response before
     * calling {@link #complete()}. If the request was suspended with a 
     * call to {@link #suspend()} then no response object will be available via
     * {@link #getServletResponse()}.
     * </p>
     * 
     * <p>
     * Once complete has been called and any thread calling the filter chain
     * and/or servlet chain has returned to the container, the request lifecycle
     * is complete. The container is able to recycle request objects, so it is
     * not valid hold a request or continuation reference after the end of the 
     * life cycle.
     * 
     * @see #suspend()
     * @exception IllegalStateException
     *                if the request is not suspended.
     * 
     */
    void complete();

    /* ------------------------------------------------------------ */
    /**
     * @return true after {@link #suspend()} has been called and before the
     *         request has been redispatched due to being resumed, completed or
     *         timed out.
     */
    boolean isSuspended();

    /* ------------------------------------------------------------ */
    /**
     * @return true if the request has been redispatched by a call to
     *         {@link #resume()}. Returns false after any subsequent call to
     *         suspend
     */
    boolean isResumed();

    /* ------------------------------------------------------------ */
    /**
     * @return true after a request has been redispatched as the result of a
     *         timeout. Returns false after any subsequent call to suspend.
     */
    boolean isExpired();

    /* ------------------------------------------------------------ */
    /**
     * @return true while the request is within the initial dispatch to the
     *         filter chain and/or servlet. Will return false once the calling
     *         thread has returned to the container after suspend has been
     *         called and during any subsequent redispatch.
     */
    boolean isInitial();

    /* ------------------------------------------------------------ */
    /**
     * Is the suspended response wrapped.
     * <p>
     * Filters that wrap the response object should check this method to 
     * determine if they should destroy/finish the wrapped response. If 
     * the request was suspended with a call to {@link #suspend(ServletResponse)}
     * that passed the wrapped response, then the filter should register
     * a {@link ContinuationListener} to destroy/finish the wrapped response
     * during a call to {@link ContinuationListener#onComplete(Continuation)}.
     * @return True if {@link #suspend(ServletResponse)} has been passed a
     * {@link ServletResponseWrapper} instance.
     */
    boolean isResponseWrapped();


    /* ------------------------------------------------------------ */
    /**
     * Get the suspended response.
     * @return the {@link ServletResponse} passed to {@link #suspend(ServletResponse)}.
     */
    ServletResponse getServletResponse();
    
    /* ------------------------------------------------------------ */
    /** 
     * Add a ContinuationListener.
     * 
     * @param listener
     */
    void addContinuationListener(ContinuationListener listener);
    
    /* ------------------------------------------------------------ */
    /** Set a request attribute.
     * This method is a convenience method to call the {@link ServletRequest#setAttribute(String, Object)}
     * method on the associated request object.
     * This is a thread safe call and may be called by any thread.
     * @param name the attribute name
     * @param attribute the attribute value
     */
    public void setAttribute(String name, Object attribute);
    
    /* ------------------------------------------------------------ */
    /** Get a request attribute.
     * This method is a convenience method to call the {@link ServletRequest#getAttribute(String)}
     * method on the associated request object.
     * This is a thread safe call and may be called by any thread.
     * @param name the attribute name
     * @return the attribute value
     */
    public Object getAttribute(String name);
    
    /* ------------------------------------------------------------ */
    /** Remove a request attribute.
     * This method is a convenience method to call the {@link ServletRequest#removeAttribute(String)}
     * method on the associated request object.
     * This is a thread safe call and may be called by any thread.
     * @param name the attribute name
     */
    public void removeAttribute(String name);
    
    /* ------------------------------------------------------------ */
    /**
     * Undispatch the request.
     * <p>
     * This method can be called on a suspended continuation in order
     * to exit the dispatch to the filter/servlet by throwing a {@link ContinuationThrowable}
     * which is caught either by the container or the {@link ContinuationFilter}.
     * This is an alternative to simply returning from the dispatch in the case
     * where filters in the filter chain may not be prepared to handle a suspended
     * request.
     * </p>
     * This method should only be used as a last resort and a normal return is a prefereable
     * solution if filters can be updated to handle that case.
     * 
     * @throws ContinuationThrowable thrown if the request is suspended. The instance of the 
     * exception may be reused on subsequent calls, so the stack frame may not be accurate.
     */
    public void undispatch() throws ContinuationThrowable;
}

Back to the top