public class RESTHandler extends BaseServlet
RestDataSource
provided with SmartClient is such a client, as is SmartGWT.mobile, but this handler will work with any REST client that encodes its data as JSON or XML, sends its requests as POST messages and conforms to the REST transfer protocol described in the client-side documentation for RestDataSource
. Note that you must read these client-side documents in order to understand how to properly format a REST request for processing by this servlet. By default JSON responses will be wrapped into special markers so that code is not directly executable outside of your application. This is a preventive measure against javascript hijacking.
The servlet accepts parameter "wrapJSONResponses":true
- wrap JSON responses; false
- send plain JSON reponses. The parameter can be set in your web.xml file either as a context parameter (for all servlets) or as a servlet initialization parameter. If JSON wrapping is on, you can also set the prefix and suffix strings that we use to wrap responses. Again, you do this by setting the parameters "jsonPrefix" and "jsonSuffix" in your web.xml file, either as a context parameter (for all servlets) or as a servlet initialization parameter. If these parameters are not set in web.xml, they default as follows:
jsonPrefix: "<SCRIPT>//'\"]]>>isc_JSONResponseStart>>"
jsonSuffix: "//isc_JSONResponseEnd"
Note that these parameters can be overridden at the DataSource level, by providing a .ds.xml
file for the DataSource, and specifying jsonPrefix
and jsonSuffix
in there.
NOTE: The default settings shown above are also the defaults used by the client-side RestDataSource. If you choose to change the default prefix and/or suffix strings returned by the server, you must obviously also change the strings that the client expects to see. If you are using RestDataSource, you do this by overriding the jsonPrefix
and jsonSuffix
properties. See the client-side documentation for details.
The servlet also accepts parameter "defaultDataFormat". This governs whether we expect requests to be encoded as XML or JSON, if no explicit dataFormat is provided with the request. Note that the dataFormat is explicitly sent with every request if you are using the Isomorphic RestDataSource
(see below), so this parameter has no effect in that case; it is only used when no dataFormat is provided with the request, as would be the case if you are integrating with a third-party REST client.
If you do not specify a defaultDataFormat, "xml" is assumed.
The servlet also accepts parameter "dynamicDataFormatParamName". This governs the name of the dataFormat parameter we look for in incoming requests (as described above in the paragraph about "defaultDataFormat"). If you wish to send the dataFormat to use with each client request, you send an HTTP parameter with this name in the request, with a value of "xml" or "json". By default, the "dynamicDataFormatParamName" is the value used by the SmartClient RestDataSource: "isc_dataFormat".
With no additional configuration, the SmartClient server will further generate an OpenAPI specification describing your REST api, available at ${urlPattern}/openapi.yaml. If, for example, you've configured your servlet to respond to requests at /restapi
, then a GET request to /restapi/openapi.yaml
will yield the generated documentation.
The Freemarker templates used to provide the document content can be overridden by placing a copy in a WEB-INF/openapi
directory, or in an alrenative location defined in the openapiTemplateDirectory
parameter. Refer to the client-side OpenAPI documentation topic for a brief discussion on what to override and when. NOTE: This servlet is configured to automatically set character encoding on requests and responses to UTF-8. If you wish to force a different encoding, you can do so by specifying the init-param
"encoding" in your web.xml file, as shown in the example below. If you wish to switch off explicit encoding altogether, use the init-param
to set a value of "none".
Please see the client-side documentation on Internationalization for a discussion of why this procedure is necessary.
This snippet shows how you might change your web.xml
file to configure the RESTHandler
servlet:
<servlet> <servlet-name>RESTHandler</servlet-name> <servlet-class>com.isomorphic.servlet.RESTHandler</servlet-class> <init-param> <param-name>defaultDataFormat</param-name> <param-value>json</param-value> </init-param> <init-param> <param-name>dynamicDataFormatParamName</param-name> <param-value>theDataFormat</param-value> </init-param> <init-param> <param-name>wrapJSONResponses</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>encoding</param-name> <param-value>some-other-encoding</param-value> </init-param> <init-param> <param-name>openapiTemplateDirectory</param-name> <param-value>WEB-INF/oas</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>RESTHandler</servlet-name> <url-pattern>/restapi/</url-pattern> </servlet-mapping>
Raw REST Mode
RESTHandler also offers a "raw" REST mode in which the DataSource name and primary key values appear in the request URI, with simplified responses. This "raw mode" may be appropriate for automated systems that need to query or update DataSource data in a simplified manner. Using "raw mode" for a SmartClient or SmartGWT browser-based UI is always, always wrong. It will create additional work while crippling your UI's capabilities and also creating performance problems.
A raw REST mode HTTP request consists of the URI that you provide (including any params), the HTTP verb, and any params (key/value pairs) specified as posted data. The HTTP response format - either XML or JSON - is determined as discussed above by the servlet parameter defaultDataFormat
or the request parameter named by the servlet parameter dynamicDataFormatParamName
, but the default for raw REST mode is JSON, unlike standard REST mode. The HTTP response status will reflect the success (2XX) or failure (4XX/5XX) of the request, and a failure will contain one or more error messages. For more details on status codes, see HTTP methods.
URI Interpretation
The basic raw REST mode URI syntax is:
isomorphic/RESTHandler/<DataSource name>[/<primary key value>]where the DataSource name must always be provided, but a primary key value is optional. Additional field/value pairs may be provided as query parameters or posted data, including the primary key if the longer syntax is not used.
HTTP verbs are handled as follows:
providesMissingKeys
). A PUT with no primary key value performs an add, exactly as if it were a POST request. allowMultiUpdate
settings for updates on the target DataSource / OperationBinding. Note that direct invocation of multi-row updates is normally discouraged - see the allowMultiUpdate topic included with client documentation for further discussion, including best-practice recommendations. For example, to fetch the record with primary key value 5
from countryDS
DataSource, you would send an HTTP GET with URI isomorphic/RESTHandler/countryDS/5
, and to add a new country record to countryDS
DataSource, which has an autogenerated sequence primary key, you would send an HTTP POST with URI isomorphic/RESTHandler/countryDS
and all the record field content as the POSTED data. (Various POSTED data formats, such as application/x-www-form-urlencoded "form data" are supported by the servlet.)
Singular vs. Array Response Format
For both XML and JSON reply formats, whether you receive a singular or array reply is determined by the HTTP request type:
So for example singular responses look like:
{ fieldName : fieldValue, fieldName2: fieldValue2}for JSON, and
<record> <fieldName>fieldValue</fieldName> <fieldName2>fieldValue2</fieldName2> </record>for XML, whereas array responses look like:
[ { fieldName : fieldValue, fieldName2: fieldValue2}, ... ]for JSON, and
<data> <record> <fieldName>fieldValue</fieldName> <fieldName2>fieldValue2</fieldName2> </record> <record> <fieldName>fieldValue</fieldName> <fieldName2>fieldValue2</fieldName2> </record> </data>for XML. Note that, regardless of the above, if a request returns no results, it will be show up as an empty HTTP response with status 204 - "no content".
Error Response Format
When an error HTTP status (4XX or 5XX) is returned from the server, there will be an error object included. It will contain an error code and an array of error messages (even if there's just one message). For JSON, it might look like:
{ code: -1, messages: ["fatal error: cannot find DataSource 'fred'"]}and for XML is might look like:
<error> <code>-1</code> <messages> <message>"fatal error: cannot find DataSource 'fred'"]}</message> </messages> </error>
Expanded Raw REST mode URI Syntax
In addition to the URI syntax discussed above, raw REST mode also accepts HTTP requests with the syntax:
isomorphic/RESTHandler/<DataSource name>/<operation>[/<operation ID>]where the operation is one of "add", "remove", "update", "fetch", or "custom. The operation ID (specifying the
operationId
is optional, except for "custom" operations wher it is required. For example, the URI isomorphic/RESTHandler/countryDS/fetch/foobar
specifies that a "fetch" is to be performed with the operationId
of "foobar". Note that support for this syntax means that you must not use those five operation names as primary key values for your DataSource. Hybrid Raw REST mode
Hybrid mode allows you to send the HTTP requests as you otherwise would as described above, but receive the responses as normal RestDataSource DSResponses. To use hybrid mode, specify hybridMode
as a servlet initialization param or query param in the HTTP request. Note that in hybrid mode, the following properties are supported as query params and/or posted data:
criteria
- must be valid JSON for simple or AdvancedCriteria, in the request body only startRow
and endRow
- as numbers, in either query params or request body textMatchStyle
- as a string, in either query params or request body sortBy
- as either a string or an array of simple sortBy
string specifiers, in query params or request body. In case of the former, something like the following is sufficient sortBy=COUNTRY&sortBy=-CUSTOMER_NAME
where a valid JSON array is required in case of the latter.
Modifier and Type | Method and Description |
---|---|
DSResponse | handleDSRequest(DSRequest dsRequest, RPCManager rpc, RequestContext context) This method is called by RESTHandler.processRestTransaction(RPCManager, RequestContext) to handle a DSRequest sent from the client. |
void | processRequest(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) Servlet entry point to process the request. |
void | processRestTransaction(RPCManager rpcManager, RequestContext context) Process a REST transaction. |
handleError, handleError
public void processRequest(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException
RESTHandler.processRestTransaction(RPCManager, RequestContext)
request
- The HttpServletRequestresponse
- The HttpServletResponsejavax.servlet.ServletException
- As per HttpServlet.service()java.io.IOException
- As per HttpServlet.service()public void processRestTransaction(RPCManager rpcManager, RequestContext context) throws java.lang.Exception
RESTHandler.handleDSRequest(DSRequest, RPCManager, RequestContext)
. If you wish to provide customized REST transaction handling, this is the appropriate method to overriderpcManager
- The RPCManager for this REST requestcontext
- RequestContext class provides accessors to Servlet basics like HttpServletRequest, HttpServletResponsejava.lang.Exception
- For backwards compatibility this method is still declared as throwing an exception, but the default implementation traps and handles all Exceptions.public DSResponse handleDSRequest(DSRequest dsRequest, RPCManager rpc, RequestContext context) throws java.lang.Exception
RESTHandler.processRestTransaction(RPCManager, RequestContext)
to handle a DSRequest sent from the client. This method may be called multiple times while processing a single HTTP request (if the client sent multiple requests in a batch via RPCManager.startQueue()). The default implementation of this method simply calls DSRequest.execute()
, catching and handling any Exception by calling RESTHandler.generateFailureResponse(DSRequest, Exception, int)
dsRequest
- The DSRequest to processrpc
- The RPCManager that was used to demultiplex the DSRequestcontext
- RequestContext class provides accessors to Servlet basics like HttpServletRequest, HttpServletResponsejava.lang.Exception
- For backwards compatibility this method is still declared as throwing an exception, but the default implementation logic (as shown above) traps and handles all Exceptions.