public class RESTHandler extends BaseServlet
RestDataSource
provided with SmartClient is such a client, 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 using the RestDataSource's postMessage protocol. 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".
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> </servlet> <servlet-mapping> <servlet-name>RESTHandler</servlet-name> <url-pattern>/restapi/</url-pattern> </servlet-mapping>
With no additional configuration, the SmartClient server will also 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. Refer to the client-side OpenAPI documentation topic for further discussion. Even if you do not plan to expose the generated documentation, there may be value in referring to it for fuller understanding of the remaining discussion. As it turns out, the RestDataSource postMessage protocol, or "Advanced REST", is not the only option.
Simplified REST
RESTHandler also offers a "Simplified REST" mode in which the DataSource name and (optionally) primary key values appear in the request URI, with simplified request and response formats. This simplified mode may be appropriate for automated systems that need to query or update DataSource data in a simplified manner. Using SimplifiedREST 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.
Two styles are supported. The first behaves much like the AdvancedREST protocol, in that it responds to POST requests only, makes use of request body content in all cases, and is able to support AdvancedCriteria and some batching capabilities. The second makes use of HTTP verbs and status codes more traditionally, and does none of those things. The remainder of this documentation will refer to both styles as Simplified REST, but refer to the first, more robust style as SimplifiedPOST wherever a distinction needs to be made for clarity.
All SimplifiedREST HTTP requests consist 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
. The default for SimplifiedPOST is XML, as it is for the AdvancedREST postMessage protocol. The default for SimplifiedREST is JSON, however, amd HTTP response status codes will more precisely reflect the success (2XX) or failure (4XX/5XX) of the request. Failures will contain one or more error messages. For more details on status codes, see HTTP methods.
URI Interpretation
The basic SimplifiedREST 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 and in fact is not valid when using SimplifiedPOST. SimplifiedPOST requests always begin with the
RESTDataSource
prefix, but otherwise take a similar form: isomorphic/RESTHandler/RESTDataSource/<DataSource name>Additional field/value pairs may be provided as query parameters or posted data, including the primary key whenever the longer syntax is not used.
Again, SimplifiedPOST responds to the single POST verb only, and returns a uniform response format, regardless of request body content. Requests and responses are handled by other SimplifiedREST requests 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 URI Syntax
In addition to the URI syntax discussed above, SimplifiedREST also accepts HTTP requests with the syntax:
isomorphic/RESTHandler/RESTDataSource/<DataSource name>/<operation>[/<operation ID>]or
isomorphic/RESTHandler/<DataSource name>/<operation>[/<operation ID>]where the operation is one of "add", "remove", "update", "fetch", or "custom". SimplifiedPOST requests also recognize a "batch" pseudo-operation on the path, where it accepts a batch of transactions, each having its operationType & operationId in the request body. Operation IDs on the path are optional, except of course when trying to reach a particular operationId, and for "custom" operations where 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". Eliminating the 'foobar' path segment would execute the default fetch operation for the countryDS DataSource. Note that support for this syntax means that you must not use these operation names as primary key values for your DataSource. Hybrid SimplifiedREST
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.