All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ratpack.handling.Context Maven / Gradle / Ivy

There is a newer version: 2.0.0-rc-1
Show newest version
/*
 * Copyright 2013 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ratpack.handling;

import com.google.common.base.Predicate;
import com.google.common.reflect.TypeToken;
import ratpack.api.NonBlocking;
import ratpack.api.Nullable;
import ratpack.exec.*;
import ratpack.func.Action;
import ratpack.handling.direct.DirectChannelAccess;
import ratpack.http.Request;
import ratpack.http.Response;
import ratpack.launch.LaunchConfig;
import ratpack.parse.NoSuchParserException;
import ratpack.parse.Parse;
import ratpack.parse.ParserException;
import ratpack.path.PathTokens;
import ratpack.registry.NotInRegistryException;
import ratpack.registry.Registry;
import ratpack.render.NoSuchRendererException;
import ratpack.server.BindAddress;

import java.nio.file.Path;
import java.util.Date;
import java.util.concurrent.Callable;

/**
 * The context of an individual {@link Handler} invocation.
 * 

* It provides: *

    *
  • Access the HTTP {@link #getRequest() request} and {@link #getResponse() response}
  • *
  • Delegation (via the {@link #next} and {@link #insert} family of methods)
  • *
  • Access to contextual objects (see below)
  • *
  • Convenience for common handler operations
  • *
*

*

Contextual objects

*

* A context is also a {@link Registry} of objects. * Arbitrary objects can be "pushed" into the context for use by downstream handlers. *

* There are some significant contextual objects that drive key infrastructure. * For example, error handling is based on informing the contextual {@link ratpack.error.ServerErrorHandler} of exceptions. * The error handling strategy for an application can be changed by pushing a new implementation of this interface into the context that is used downstream. *

* See {@link #insert(Handler...)} for more on how to do this. *

Default contextual objects
*

There is also a set of default objects that are made available via the Ratpack infrastructure: *

    *
  • The effective {@link ratpack.launch.LaunchConfig}
  • *
  • A {@link ratpack.file.FileSystemBinding} that is the application {@link ratpack.launch.LaunchConfig#getBaseDir()}
  • *
  • A {@link ratpack.file.MimeTypes} implementation
  • *
  • A {@link ratpack.error.ServerErrorHandler}
  • *
  • A {@link ratpack.error.ClientErrorHandler}
  • *
  • A {@link ratpack.file.FileRenderer}
  • *
  • A {@link ratpack.server.BindAddress}
  • *
  • A {@link ratpack.server.PublicAddress}
  • *
  • A {@link Redirector}
  • *
*/ public interface Context extends ExecControl, Registry { /** * Returns this. * * @return this. */ Context getContext(); /** * The execution of handling this request. * * @return the execution of handling this request */ Execution getExecution(); LaunchConfig getLaunchConfig(); /** * The HTTP request. * * @return The HTTP request. */ Request getRequest(); /** * The HTTP response. * * @return The HTTP response. */ Response getResponse(); /** * Delegate handling to the next handler in line. *

* The request and response of this object should not be accessed after this method is called. */ @NonBlocking void next(); /** * Invokes the next handler, after adding the given registry. *

* The given registry is appended to the existing. * This means that it can shadow objects previously available. *

   * import ratpack.handling.Handler;
   * import ratpack.handling.Handlers;
   * import ratpack.handling.Chain;
   * import ratpack.handling.ChainAction;
   * import ratpack.handling.Context;
   * import ratpack.launch.HandlerFactory;
   * import ratpack.launch.LaunchConfig;
   * import ratpack.launch.LaunchConfigBuilder;
   * import ratpack.func.Factory;
   *
   * import static ratpack.registry.Registries.just;
   *
   * public interface SomeThing {}
   * public class SomeThingImpl implements SomeThing {}
   *
   * public class UpstreamHandler implements Handler {
   *   public void handle(Context context) {
   *     context.next(just(SomeThing.class, new SomeThingImpl()));
   *   }
   * }
   *
   * public class DownstreamHandler implements Handler {
   *   public void handle(Context context) {
   *     SomeThing someThing = context.get(SomeThing.class); // instance provided upstream
   *     assert someThing instanceof SomeThingImpl;
   *     // …
   *   }
   * }
   *
   * LaunchConfigBuilder.baseDir(new File("base")).build(new HandlerFactory() {
   *   public Handler create(LaunchConfig launchConfig) {
   *     return Handlers.chain(launchConfig, new ChainAction() {
   *       protected void execute() {
   *         handler(new UpstreamHandler());
   *         handler(new DownstreamHandler());
   *       }
   *     });
   *   }
   * });
   * 
* * @param registry The registry to make available for subsequent handlers. */ @NonBlocking void next(Registry registry); /** * Inserts some handlers into the pipeline, then delegates to the first. *

* The request and response of this object should not be accessed after this method is called. * * @param handlers The handlers to insert. */ @NonBlocking void insert(Handler... handlers); /** * Inserts some handlers into the pipeline to execute with the given registry, then delegates to the first. *

* The given registry is only applicable to the inserted handlers. *

* Almost always, the registry should be a super set of the current registry. * * @param handlers The handlers to insert * @param registry The registry for the inserted handlers */ @NonBlocking void insert(Registry registry, Handler... handlers); /** * Convenience method for delegating to a single handler. *

* Designed to be used in conjunction with the {@link #getByMethod()} and {@link #getByContent()} methods. * * @param handler The handler to invoke * @see ByContentHandler * @see ByMethodHandler */ @NonBlocking void respond(Handler handler); /** * A buildable handler for conditional processing based on the HTTP request method. * * @return A buildable handler for conditional processing based on the HTTP request method. */ ByMethodHandler getByMethod(); /** * A buildable handler useful for performing content negotiation. * * @return A buildable handler useful for performing content negotiation. */ ByContentHandler getByContent(); // Shorthands for common service lookups /** * Forwards the exception to the {@link ratpack.error.ServerErrorHandler} in this service. *

* The default configuration of Ratpack includes a {@link ratpack.error.ServerErrorHandler} in all contexts. * A {@link NotInRegistryException} will only be thrown if a very custom service setup is being used. * * @param exception The exception that occurred * @throws NotInRegistryException if no {@link ratpack.error.ServerErrorHandler} can be found in the service */ @NonBlocking void error(Exception exception); /** * Executes a blocking operation, returning a promise for its result. *

* This method executes asynchronously, in that it does not invoke the {@code operation} before returning the promise. * When the returned promise is subscribed to (i.e. its {@link ratpack.exec.SuccessPromise#then(Action)} method is called), * the given {@code operation} will be submitted to a thread pool that is different to the request handling thread pool. * Therefore, if the returned promise is never subscribed to, the {@code operation} will never be initiated. *

* The promise returned by this method, has the same default error handling strategy as those returned by {@link ratpack.exec.ExecControl#promise(ratpack.func.Action)}. *

*

   * import ratpack.handling.*;
   * import ratpack.func.Action;

   * import java.util.concurrent.Callable;
   *
   * public class BlockingJavaHandler implements Handler {
   *   void handle(final Context context) {
   *     context.blocking(new Callable<String>() {
   *        public String call() {
   *          // perform some kind of blocking IO in here, such as accessing a database
   *          return "hello world!";
   *        }
   *     }).then(new Action<String>() {
   *       public void execute(String result) {
   *         context.render(result);
   *       }
   *     });
   *   }
   * }
   *
   * public class BlockingGroovyHandler implements Handler {
   *   void handle(final Context context) {
   *     context.blocking {
   *       "hello world!"
   *     } then { String result ->
   *       context.render(result)
   *     }
   *   }
   * }
   *
   * // Test (Groovy) …
   *
   * import static ratpack.groovy.test.TestHttpClients.testHttpClient
   * import static ratpack.groovy.test.embed.EmbeddedApplications.embeddedApp
   *
   * def app = embeddedApp {
   *   handlers {
   *     get("java", new BlockingJavaHandler())
   *     get("groovy", new BlockingGroovyHandler())
   *   }
   * }
   *
   * def client = testHttpClient(app)
   *
   * assert client.getText("java") == "hello world!"
   * assert client.getText("groovy") == "hello world!"
   *
   * app.close()
   * 
* * @param blockingOperation The operation to perform * @param The type of result object that the operation produces * @return a promise for the return value of the callable. */ @Override Promise blocking(Callable blockingOperation); /** * Creates a promise of a value that will made available asynchronously. *

* The {@code action} given to this method receives a {@link Fulfiller}, which can be used to fulfill the promise at any time in the future. * The {@code action} is not required to fulfill the promise during the execution of the {@code execute()} method (i.e. it can be asynchronous). * The {@code action} MUST call one of the fulfillment methods. * Otherwise, the promise will go unfulfilled. * There is no time limit or timeout on fulfillment. *

* The promise returned has a default error handling strategy of forwarding exceptions to {@link #error(Exception)} of this context. * To use a different error strategy, supply it to the {@link ratpack.exec.Promise#onError(Action)} method. *

* The promise will always be fulfilled on a thread managed by Ratpack. *

   * import ratpack.handling.*;
   * import ratpack.exec.Fulfiller;
   * import ratpack.func.Action;
   *
   * public class PromiseUsingJavaHandler implements Handler {
   *   public void handle(final Context context) {
   *     context.promise(new Action<Fulfiller<String>>() {
   *       public void execute(final Fulfiller<String> fulfiller) {
   *         new Thread(new Runnable() {
   *           public void run() {
   *             fulfiller.success("hello world!");
   *           }
   *         }).start();
   *       }
   *     }).then(new Action<String>() {
   *       public void execute(String string) {
   *         context.render(string);
   *       }
   *     });
   *   }
   * }
   *
   * class PromiseUsingGroovyHandler implements Handler {
   *   void handle(Context context) {
   *     context.promise { Fulfiller<String> fulfiller ->
   *       Thread.start {
   *         fulfiller.success("hello world!")
   *       }
   *     } then { String string ->
   *       context.render(string)
   *     }
   *   }
   * }
   *
   * // Test (Groovy) …
   *
   * import static ratpack.groovy.test.TestHttpClients.testHttpClient
   * import static ratpack.groovy.test.embed.EmbeddedApplications.embeddedApp
   *
   * def app = embeddedApp {
   *   handlers {
   *     get("java", new PromiseUsingJavaHandler())
   *     get("groovy", new PromiseUsingGroovyHandler())
   *   }
   * }
   *
   * def client = testHttpClient(app)
   *
   * assert client.getText("java") == "hello world!"
   * assert client.getText("groovy") == "hello world!"
   *
   * app.close()
   * 
* @param the type of value promised */ @Override Promise promise(Action> action); /** * Forks a new execution, detached from the current. *

* This can be used to launch a one time background job during request processing. * The forked execution does not inherit anything from the current execution. *

* Forked executions MUST NOT write to the response or participate in the handler pipeline at all. * That is, they should not call methods like {@link #next()} or {@link #insert(Handler...)}. *

* When using forking to process work in parallel, use {@link #promise(ratpack.func.Action)} to continue request handling when the parallel work is done. * *

*

   * import ratpack.handling.Handler;
   * import ratpack.handling.Context;
   * import ratpack.func.Action;
   * import ratpack.func.Actions;
   * import ratpack.exec.Execution;
   * import ratpack.exec.Fulfiller;
   *
   * import java.util.Collections;
   * import java.util.concurrent.atomic.AtomicInteger;
   * import java.util.concurrent.atomic.AtomicReference;
   *
   * import ratpack.test.UnitTest;
   * import ratpack.test.handling.HandlingResult;
   * import ratpack.test.handling.RequestFixture;
   *
   * public class Example {
   *   public static class ForkingHandler implements Handler {
   *     public void handle(final Context context) {
   *       final int numJobs = 3;
   *       final Integer failOnIteration = context.getPathTokens().asInt("failOn");
   *
   *       context.promise(new Action<Fulfiller<Integer>>() {
   *         private final AtomicInteger counter = new AtomicInteger();
   *         private final AtomicReference<Throwable> error = new AtomicReference<>();
   *
   *         public void execute(final Fulfiller<Integer> fulfiller) {
   *           for (int i = 0; i < numJobs; ++i) {
   *             final int iteration = i;
   *
   *             context.fork(new Action<Execution>() {
   *               public void execute(Execution execution) throws Exception {
   *
   *                 // Important to set a error handler that somehow signals completion so the request processing can continue
   *                 // (the error handler is invoked if an exception is uncaught during the execution)
   *                 execution.setErrorHandler(new Action<Throwable>() {
   *                   public void execute(Throwable throwable) {
   *                     error.compareAndSet(null, throwable); // just take the first error
   *                     completeJob();
   *                   }
   *                 });
   *
   *                 if (failOnIteration != null && failOnIteration.intValue() == iteration) {
   *                   throw new Exception("bang!");
   *                 } else {
   *                   completeJob();
   *                 }
   *               }
   *
   *               private void completeJob() {
   *                 if (counter.incrementAndGet() == numJobs) {
   *                   Throwable throwable = error.get();
   *                   if (throwable == null) {
   *                     fulfiller.success(numJobs);
   *                   } else {
   *                     fulfiller.error(throwable);
   *                   }
   *                 }
   *               }
   *             });
   *           }
   *         }
   *       }).then(new Action<Integer>() {
   *         public void execute(Integer integer) {
   *           context.render(integer.toString());
   *         }
   *       });
   *     }
   *   }
   *
   *   public static void main(String[] args) {
   *     HandlingResult result = UnitTest.handle(new ForkingHandler(), Actions.noop());
   *     assert result.rendered(String.class).equals("3");
   *
   *     result = UnitTest.handle(new ForkingHandler(), new Action<RequestFixture>() {
   *       public void execute(RequestFixture fixture) {
   *         fixture.pathBinding(Collections.singletonMap("failOn", "2"));
   *       }
   *     });
   *
   *     assert result.getException().getMessage().equals("bang!");
   *   }
   * }
   * 
* * @param action the initial execution segment */ @Override void fork(Action action); /** * Forwards the error to the {@link ratpack.error.ClientErrorHandler} in this service. * * The default configuration of Ratpack includes a {@link ratpack.error.ClientErrorHandler} in all contexts. * A {@link ratpack.registry.NotInRegistryException} will only be thrown if a very custom service setup is being used. * * @param statusCode The 4xx range status code that indicates the error type * @throws NotInRegistryException if no {@link ratpack.error.ClientErrorHandler} can be found in the service */ @NonBlocking void clientError(int statusCode) throws NotInRegistryException; /** * Render the given object, using the rendering framework. *

* The first {@link ratpack.render.Renderer}, that is able to render the given object will be delegated to. * If the given argument is {@code null}, this method will have the same effect as {@link #clientError(int) clientError(404)}. *

* If no renderer can be found for the given type, a {@link NoSuchRendererException} will be given to {@link #error(Exception)}. *

* If a renderer throws an exception during its execution it will be wrapped in a {@link ratpack.render.RendererException} and given to {@link #error(Exception)}. *

* Ratpack has built in support for rendering the following types: *

    *
  • {@link java.nio.file.Path} (see {@link ratpack.file.FileRenderer})
  • *
  • {@link java.lang.CharSequence} (see {@link ratpack.render.CharSequenceRenderer})
  • *
*

* See {@link ratpack.render.Renderer} for more on how to contribute to the rendering framework. * * @param object The object to render * @throws NoSuchRendererException if no suitable renderer can be found */ @NonBlocking void render(Object object) throws NoSuchRendererException; /** * Sends a temporary redirect response (i.e. statusCode 302) to the client using the specified redirect location URL. * * @param location the redirect location URL * @throws NotInRegistryException if there is no {@link Redirector} in the current service but one is provided by default */ void redirect(String location) throws NotInRegistryException; /** * Sends a redirect response location URL and status code (which should be in the 3xx range). * * @param code The status code of the redirect * @param location the redirect location URL * @throws NotInRegistryException if there is no {@link Redirector} in the current service but one is provided by default */ void redirect(int code, String location) throws NotInRegistryException; /** * Convenience method for handling last-modified based HTTP caching. *

* The given date is the "last modified" value of the response. * If the client sent an "If-Modified-Since" header that is of equal or greater value than date, a 304 * will be returned to the client. Otherwise, the given runnable will be executed (it should send a response) * and the "Last-Modified" header will be set by this method. * * @param date The effective last modified date of the response * @param runnable The response sending action if the response needs to be sent */ @NonBlocking void lastModified(Date date, Runnable runnable); /** * Parse the request into the given type, using no options (or more specifically an instance of {@link ratpack.parse.NullParseOpts} as the options). *

* The code sample is functionally identical to the sample given for the {@link #parse(Parse)} variant… *

   * import ratpack.handling.Handler;
   * import ratpack.handling.Context;
   * import ratpack.form.Form;
   *
   * public class FormHandler implements Handler {
   *   public void handle(Context context) {
   *     Form form = context.parse(Form.class);
   *     context.render(form.get("someFormParam"));
   *   }
   * }
   * 
*

* That is, it is a convenient form of {@code parse(Parse.of(T))}. * * @param type the type to parse to * @param the type to parse to * @return The parsed object * @throws NoSuchParserException if no suitable parser could be found in the registry * @throws ParserException if a suitable parser was found, but it threw an exception while parsing */ T parse(Class type) throws NoSuchParserException, ParserException; /** * Parse the request into the given type, using no options (or more specifically an instance of {@link ratpack.parse.NullParseOpts} as the options). *

* The code sample is functionally identical to the sample given for the {@link #parse(Parse)} variant… *

   * import ratpack.handling.Handler;
   * import ratpack.handling.Context;
   * import ratpack.form.Form;
   * import com.google.common.reflect.TypeToken;
   *
   * public class FormHandler implements Handler {
   *   public void handle(Context context) {
   *     Form form = context.parse(new TypeToken<Form>() {});
   *     context.render(form.get("someFormParam"));
   *   }
   * }
   * 
*

* That is, it is a convenient form of {@code parse(Parse.of(T))}. * * @param type the type to parse to * @param the type to parse to * @return The parsed object * @throws NoSuchParserException if no suitable parser could be found in the registry * @throws ParserException if a suitable parser was found, but it threw an exception while parsing */ T parse(TypeToken type) throws NoSuchParserException, ParserException; /** * Constructs a {@link Parse} from the given args and delegates to {@link #parse(Parse)}. * * @param type The type to parse to * @param options The parse options * @param The type to parse to * @param The type of the parse opts * @return The parsed object * @throws NoSuchParserException if no suitable parser could be found in the registry * @throws ParserException if a suitable parser was found, but it threw an exception while parsing */ T parse(Class type, O options) throws NoSuchParserException, ParserException; /** * Constructs a {@link Parse} from the given args and delegates to {@link #parse(Parse)}. * * @param type The type to parse to * @param options The parse options * @param The type to parse to * @param The type of the parse opts * @return The parsed object * @throws NoSuchParserException if no suitable parser could be found in the registry * @throws ParserException if a suitable parser was found, but it threw an exception while parsing */ T parse(TypeToken type, O options) throws NoSuchParserException, ParserException; /** * Parses the request body into an object. *

* How to parse the request is determined by the given {@link Parse} object. *

Parser Resolution
*

* Parser resolution happens as follows: *

    *
  1. All {@link ratpack.parse.Parser parsers} are retrieved from the context registry (i.e. {@link #getAll(Class) getAll(Parser.class)});
  2. *
  3. Found parsers are checked (in order returned by {@code getAll()}) for compatibility with the current request content type and options type;
  4. *
  5. If a parser is found that is compatible, its {@link ratpack.parse.Parser#parse(Context, ratpack.http.TypedData, Parse)} method is called;
  6. *
  7. If the parser returns {@code null} the next parser will be tried, if it returns a value it will be returned by this method;
  8. *
  9. If no compatible parser could be found, a {@link NoSuchParserException} will be thrown.
  10. *
*
Parser Compatibility
*

* A parser is compatible if all of the following hold true: *

    *
  • Its {@link ratpack.parse.Parser#getContentType()} is exactly equal to {@link ratpack.http.MediaType#getType() getRequest().getBody().getContentType().getType()}
  • *
  • The opts of the given {@code parse} object is an {@code instanceof} its {@link ratpack.parse.Parser#getOptsType()} ()}
  • *
  • The {@link ratpack.parse.Parser#parse(Context, ratpack.http.TypedData, Parse)} method returns a non null value.
  • *
*

* If the request has no declared content type, {@code text/plain} will be assumed. *

Core Parsers
*

* Ratpack core provides implicit {@link ratpack.parse.NoOptParserSupport no opt parsers} for the following types and content types: *

    *
  • {@link ratpack.form.Form}
  • *
      *
    • multipart/form-data
    • *
    • application/x-www-form-urlencoded
    • *
    *
*
Example Usage
*
   * import ratpack.handling.Handler;
   * import ratpack.handling.Context;
   * import ratpack.form.Form;
   * import ratpack.parse.Parse;
   * import ratpack.parse.NullParseOpts;
   *
   * public class FormHandler implements Handler {
   *   public void handle(Context context) {
   *     Form form = context.parse(Parse.of(Form.class));
   *     context.render(form.get("someFormParam"));
   *   }
   * }
   * 
* @param parse The specification of how to parse the request * @param The type of object the request is parsed into * @param the type of the parse options object * @return The parsed object * @throws NoSuchParserException if no suitable parser could be found in the registry * @throws ParserException if a suitable parser was found, but it threw an exception while parsing * @see #parse(Class) * @see #parse(Class, Object) * @see ratpack.parse.Parser */ T parse(Parse parse) throws NoSuchParserException, ParserException; /** * Provides direct access to the backing Netty channel. *

* General only useful for low level extensions. Avoid if possible. * * @return Direct access to the underlying channel. */ DirectChannelAccess getDirectChannelAccess(); /** * The address that this request was received on. * * @return The address that this request was received on. */ BindAddress getBindAddress(); /** * The contextual path tokens of the current {@link ratpack.path.PathBinding}. *

* Shorthand for {@code get(PathBinding.class).getPathTokens()}. * * @return The contextual path tokens of the current {@link ratpack.path.PathBinding}. * @throws NotInRegistryException if there is no {@link ratpack.path.PathBinding} in the current service */ PathTokens getPathTokens() throws NotInRegistryException; /** * The contextual path tokens of the current {@link ratpack.path.PathBinding}. *

* Shorthand for {@code get(PathBinding.class).getAllPathTokens()}. * * @return The contextual path tokens of the current {@link ratpack.path.PathBinding}. * @throws NotInRegistryException if there is no {@link ratpack.path.PathBinding} in the current service */ PathTokens getAllPathTokens() throws NotInRegistryException; /** * Registers a callback to be notified when the request for this context is “closed” (i.e. responded to). * * @param onClose A notification callback */ void onClose(Action onClose); /** * Gets the file relative to the contextual {@link ratpack.file.FileSystemBinding}. *

* Shorthand for {@code get(FileSystemBinding.class).file(path)}. *

* The default configuration of Ratpack includes a {@link ratpack.file.FileSystemBinding} in all contexts. * A {@link NotInRegistryException} will only be thrown if a very custom service setup is being used. * * @param path The path to pass to the {@link ratpack.file.FileSystemBinding#file(String)} method. * @return The file relative to the contextual {@link ratpack.file.FileSystemBinding} * @throws NotInRegistryException if there is no {@link ratpack.file.FileSystemBinding} in the current service */ Path file(String path) throws NotInRegistryException; /** * {@inheritDoc} */ @Override O get(Class type) throws NotInRegistryException; /** * {@inheritDoc} */ @Nullable @Override O maybeGet(Class type); /** * {@inheritDoc} */ @Override Iterable getAll(Class type); /** * {@inheritDoc} */ @Override O get(TypeToken type) throws NotInRegistryException; /** * {@inheritDoc} */ @Nullable @Override O maybeGet(TypeToken type); /** * {@inheritDoc} */ @Override Iterable getAll(TypeToken type); void addExecInterceptor(ExecInterceptor execInterceptor, Action action) throws Exception; /** * {@inheritDoc} */ @Nullable @Override T first(TypeToken type, Predicate predicate); /** * {@inheritDoc} */ @Override Iterable all(TypeToken type, Predicate predicate); /** * {@inheritDoc} */ @Override boolean each(TypeToken type, Predicate predicate, Action action) throws Exception; }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy