HTTP response data mapping in Gloop
Once request data has been mapped into your service and the service has been invoked, Martini will try to map the output of your service to the HTTP response.
Exceptions in services
If your service throws an uncaught exception, Martini will wrap it with an
APIException
. These exceptions are annotated to allow
Martini to respond with error messages in a common format such as:
-
JSON
1 2 3 4 5
{ "result" : "ERROR", "apiErrorCode" : -1, "message" : "Unhandled Error: null" }
-
XML
1 2 3 4 5
<APIException> <result>ERROR</result> <apiErrorCode>-1</apiErrorCode> <message>Unhandled Error: null</message> </APIException>
There are Gloop model-compatible versions of these Java classes at io.toro.martini.api
,
in the core/models
directory (seen in the Navigator view).
Rendering responses with JSP
If you wish to render the output of your service with a JSP page, simply add an output property called $gloopView
.
If this property exists, a ModelAndView
object will be returned to Spring Web
MVC. This object contains the name of the view and the output from the service as the model. The service output
will be wrapped in a custom class that implements the Map
interface, allowing the named
JSP to use JSTL to access the properties from the output of the service. Since the response handling
from this point on is delegated back to Spring, the use of the redirect:
prefix is also
supported.
JSPs for Martini must be in the web
directory of the corresponding Martini package
where the top-level service also resides in. $gloopView
's value doesn't need the web
directory included in it,
nor does it need the file extension. Therefore, if you wanted your service to use the JSP web/myOutput.jsp
,
then $gloopView
's value should be myOutput
. Similarly, if your service was to use the JSP
web/my/views/here/view1.jsp
then the corresponding value for $gloopView
should be my/views/here/view1
.
Rendering binary output
If the output of your service is binary data (such as having a service that renders an image or PDF, or prompts
the user to download a file), then the output of the service should only contain a single model property that
references the io.toro.martini.gloop.http.MultipartFile
model. The name of the model
isn't important. If the contentType
of the model is compatible with application/octet-stream
, then Martini
will set the Content-Disposition
response headers for you, which normally trigger web
browsers to prompt the user for downloading a file (and also includes the filename in the header). Otherwise, the
contentType
from the model will be set as the response content type. If the size
property is set, it too will be
passed to the response. Once all the headers are
taken care of, the inputStream
of the service output io.toro.martini.gloop.http.MultipartFile
model is copied to
the response output stream.
Other response types
If the output of the service doesn't tell Martini to use binary data or a JSP page, then the output of the
service itself is sent to the response. Martini uses the detected response content type to determine whether to
write the response out as XML, JSON, or YAML. For output properties that are models, Martini will
use another customised
ContentNegotiationManager
for
responses to determine the format (content-type) of the output. The ContentNegotiationManager
in Martini
doesn't use file extensions to detect the content type. Instead, it uses a parameter called dataFormat
to detect
whether to use formats such as XML or JSON. If the dataFormat
parameter doesn't exist in the request,
then the Accept
header of the request is used. Finally, if no Accept
header exists (or is too broad), it will fall
back to the format specified in the api.rest.default-content-type
instance property.
Using different Content-Type
and Accept
headers for your requests and responses
Since Martini uses both the Content-Type
and Accept
headers to work out the request and response data
types, it's possible to construct a request with a Content-Type
header of application/json
, an Accept
header
of application/xml
to mix-and-match your request and response formats!
Serialization of output data
Martini behaves slightly differently if there's only one output parameter in your service. If your Gloop service has one output parameter, then only that will get serialised.
Example response per type
In this section, we'll show you how Martini behaves with certain output types.
Single string parameter
Below is the output of a service called TestOutput
that returns a single String
property called name
, whose
value is John Smith
.
-
JSON
1
"John Smith"
-
XML
1 2
<?xml version="1.0"?> <name>John Smith</name>
-
YAML
1
name: John Smith
More than one parameter
Below is the output of a service called TestOutput
that returns two properties called name
and age
, with the
values of John Smith
and 25
, respectively.
-
JSON
1 2 3 4
{ "name": "John Smith", "age": 25 }
-
XML
1 2 3 4 5
<?xml version="1.0"?> <TestOutputOutput> <name>John Smith</name> <age>25</age> </TestOutputOutput>
-
YAML
1 2 3
TestOutputOutput: name: John Smith age: 25
Notice how the output has been wrapped in all three formats. For XML and YAML, the parent node is called the name of
the service, suffixed with Output
.
Single Gloop model parameter
Below is the output for a service called TestOutput
that returns one model property called person
, which
has two properties called name
and age
, with a value of John Smith
and 25
.
-
JSON
1 2 3 4
{ "name": "John Smith", "age": 25 }
-
XML
1 2 3 4 5
<?xml version="1.0"?> <person> <name>John Smith</name> <age>25</age> </person>
-
YAML
1 2 3
person: name: John Smith age: 25
Notice how the output has been wrapped in all three formats again. Also, the XML and YAML outputs are now wrapped
with a nicer name (person
instead of TestOutputOutput
).
Complex output
Below is the output for a service called TestOutput
that returns one model property called person
, which
has two properties called name
and age
, with a value of John Smith
and 25
. It also has two other output
parameters called reponseMessage
with a value of OK
and responseTime
with a value of 50
.
-
JSON
1 2 3 4 5 6 7 8
{ "person": { "name": "John Smith", "age": 25 }, "responseMessage": "OK", "responseTime": 50 }
-
XML
1 2 3 4 5 6 7 8 9
<?xml version="1.0"?> <TestOutputOutput> <person> <name>John Smith</name> <age>25</age> </person> <responseMessage>OK</responseMessage> <responseTime>50</responseTime> </TestOutputOutput>
-
YAML
1 2 3 4 5 6
TestOutputOutput: person: name: John Smith age: 25 responseMessage: OK responseTime: 50
Notice how the output has been wrapped in all three formats again. But since the service has more than one output
parameter, the XML and YAML responses have gone back to using TestOutputOutput
as the name or the root node.
Writing data directly to the response output stream
It's also possible for a service to write data directly to the response output stream. To do this,
the service should have a $response
input property of type object1, and a $requestHandled
boolean output property with a value of true
. The $response
object has a writer
field
(of type PrintWriter
) which can be used by the service to write the data.
Meanwhile, $requestHandled = true
tells Martini that it should not handle the request and response anymore
because it's already been fully-handled by the service. Here's a usage example:
-
Martini will automatically assume this object references the
HttpServletResponse
type. ↩