Developing Web Service Applications
This document explains how to develop (REST) web service type applications on the container - design options, accessing the request path, returning a status code etc. There are two types of web service APIs:
- Fine-grained APIs with closed semantics – for example return the number of stars of an article
- Coarse-grained APIs with open semantics – for example return a page containing the most relevant mixture of stuff for this user and action
In addition, the container features a generic mechanism which allows a Handler component to be bound to a URL and invoked to handle all requests matching that URL. This is useful where there is neither a large fine-grained API nor any need to handle complexity and/or federation of various kinds of data in the response. Both the approaches above are implemented as provided Handlers.
A custom Handler may be written to parse the url path/method and dispatch to an appropriate chain of processing components. A "main" processing chain may be written to do the same by dispatching to other chains.
The simplest way to invoke a specific chain of processors
is to forward a query to the ProcessingHandler
with the request property chain
set to the name of the chain to invoke:
import com.google.inject.Inject; public class DemoHandler extends com.yahoo.container.jdisc.ThreadedHttpRequestHandler { ... @Inject public DemoHandler(Executor executor, ProcessingHandler processingHandler) { super(executor); this.processingHandler = processingHandler; } ... @Override public HttpResponse handle(HttpRequest request) { HttpRequest processingRequest = new HttpRequest.Builder(request) .put(com.yahoo.processing.Request.CHAIN, "theProcessingChainIWant") .createDirectRequest(); HttpResponse r = processingHandler.handle(processingRequest); return r; } ... }
Accessing the HTTP request
In a general JDisc request handler
the com.yahoo.jdisc.Request
can be cast to an instance
of com.yahoo.jdisc.http.HttpRequest
when the request is over HTTP.
Web service handlers should usually extend
com.yahoo.container.jdisc.ThreadedHttpRequestHandler
(or the com.yahoo.container.jdisc.LoggingRequestHandler
subclass
which also does access logging),
which provides to subclasses a com.yahoo.container.jdisc.HttpRequest
which contains both the core HttpRequest and the associated properties and request data.
In Processing,
one is given a com.yahoo.processing.Request
which contains the HTTP URL parameters
as well as the entire container request as a member variables.
So to access the HttpRequest data in Processing, use:
// url parameters are added to properties String urlParameter = request.properties().get("urlParameterName"); // jdisc request context is added with prefix context Object contextValue = request.properties().get("context.contextKey"); // or, to access path, HTTP method or data, get the whole HTTP request: com.yahoo.container.jdisc.HttpRequest httpRequest = (com.yahoo.container.jdisc.HttpRequest) request.properties().get("jdisc.request"); // Then POST data inputstream would be: InputStream in = httpRequest.getData(); // And the HTTP method: Method method = httpRequest.getMethod();
Setting the HTTP status and HTTP headers
In Processing, the return status can be set by adding a special Data item to the Response:
response.data().add(new com.yahoo.processing.handler.ResponseStatus(404, request));If no such data element is present, the status will be determined by the container. If it contains renderable data it will be 200, otherwise it will be determined by any ErrorMessage present in the response.
Setting response headers from Processors
Response headers may be added to any Response by adding instances of
com.yahoo.processing.handler.ResponseHeaders
to the Response
(ResponseHeaders is a kind of response Data).
Multiple instances of this may be added to the Response,
and the complete set of headers returned is the superset of all such objects.
Example Processor:
processingResponse.data().add(new com.yahoo.processing.handler.ResponseHeaders(myHeaders, request));Request handlers may in general set their return status, and manipulate headers directly on the HttpRequest.
Queries
Sometimes all that is needed is letting the standard query framework
reply for more paths than standard.
This is possible by adding extra bindings
inside the <search>
element in services.xml
.
Writing a custom request handler
is recommended if the application is a standalone HTTP API,
and especially if there are properties used with the same name as those in the
Query API.
A request handler may query the search components running in the same container
without any appreciable overhead:
Invoking Vespa queries from a component
To invoke Vespa queries from a component, have an instance of com.yahoo.search.searchchain.ExecutionFactory injected
in your constructor and use its API to construct and issue the query. The container this runs in must include the
<search>
tag for the ExecutionFactory to be available. Example:
import com.google.inject.Inject; import com.yahoo.component.ComponentId; import com.yahoo.search.Query; import com.yahoo.search.Result; import com.yahoo.component.Chain; import com.yahoo.search.searchchain.Execution; import com.yahoo.search.searchchain.ExecutionFactory; public class MyComponent { private final ExecutionFactory executionFactory; @Inject public MyComponent(ExecutionFactory executionFactory) { this.executionFactory = executionFactory; } Result executeQuery(Query query, String chainId) { Chain<Search> searchChain = executionFactory.searchChainRegistry().getChain(new ComponentId(chainId)); Execution execution = executionFactory.newExecution(searchChain); query.getModel().setExecution(execution); return execution.search(query); } }