Skip to content

Overview

Concepts and Principles

Development

Overview

IDEs

API Explorer

Releases

Release Notes

TORO Integrate

Coder Studio

Coder Cloud

Bug Reports

Search

Creating HTTP Endpoints via Spring Controllers

Aside from creating ad hoc REST endpoints and Gloop REST or SOAP APIs, it's also possible to expose services via Groovy-based Spring controllers! TORO Integrate comes budled with Spring which is why creating beans and adding controllers are inherently supported in TORO Integrate.

This page will discuss how you can use Spring to create RESTful web services. You should head to Working with Web Pages if you intend to serve web content via Spring MVC.

Example

Below is a simple controller with a service that accepts GET requests at /person/new.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping('person')
class PersonController {

    @RequestMapping(method = RequestMethod.GET, value = 'new')
    Person generate(@RequestParam('first-name') String firstName, @RequestParam('last-name') String lastName) {
        String id = UUID.randomUUID().toString()
        return new Person(id:id, firstName:firstName, lastName:lastName)
    }

}

The response of this endpoint will be a new Person, whose class is defined as:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import com.fasterxml.jackson.annotation.JsonProperty

class Person {

    String id

    @JsonProperty('first-name')
    String firstName

    @JsonProperty('last-name')
    String lastName

}

The response will be represented in a certain format like, but not limited to, JSON1:

1
2
3
4
5
{
  "id": "569e97e9-0d29-43ec-b8d5-c505d3ee6a8y",
  "first-name": "John",
  "last-name": "Doe"
}

To try this out:

  1. Create the Groovy classes Person and PersonController, respectively, and add them to an Integrate Package's code directory.
  2. Wait for the classes to compile and;
  3. Send a request to the endpoint at /person/new2:
1
2
3
curl -X GET \
  '<http|https>://<host>:<port>/<api-prefix>/person/new?first-name=John&last-name=Doe' \
  -H 'Cache-Control: no-cache'

Breakdown

So, what really happened up there?

In Spring, you can create RESTful web services via annotations. This is precisely what we did in the example and below are the annotations we used and their purposes:

  • @RestController

    According to Spring, this is a convenience annotation that applies of both @Controller and @ResponseBody.

    @Controller is used to indicate that the annotated class will serve the role of a controller. Controller classes will be scanned by the dispatcher3 for mapped methods (methods annotated with @RequestMapping).

    The @ResponseBody annotation, on the other hand, is used to indicate that the value returned by the annotated method should be serialized to the response body through an HttpMessageConverter. @RestController applies @ResponseBody to all of the @RestController-annotated class's methods and therefore writes directly to the response body as opposed to resolving a view and then rendering the corresponding HTML template.

  • @RequestMapping

    As stated in its class documentation page, this is an annotation used for mapping web requests onto handler classes or handler methods. In other words, it specifies which HTTP requests would be handled by a controller and its method (using paths). This is why you were able to access the web service at /person/new.

    For HTTP method-specific variants, you may use the following method-level annotations:

  • @RequestParam

    According to Spring's documentation, this annotation is used to indicate that a method parameter must be bound to a web request parameter. This is why we were able to get the values of the first-name and last-name query parameters and transpose them to the firstName and lastName method parameters (respectively).

Although unused in the example, another frequently used annotation when creating RESTful web services is @PathVariable. It works like @RequestParam, annotated to method parameters, but is for binding URI template variables (specified in @RequestMapping annotations) to method parameters.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@RestController
@RequestMapping('person')
class PersonController {

    @GetMapping('/{id}')
    Person findPerson(@PathVariable String id) {
        // ...
    }

}

With the code above, a request to /person/569e97e9-0d29-43ec-b8d5-c505d3ee6a8y would mean that the id method parameter would be later on assigned the value of "569e97e9-0d29-43ec-b8d5-c505d3ee6a8y".

Request Content-Type Detection

When determining the Content-Type of a request, TORO Integrate goes beyond simply checking the request's Content-Type header. It iterates through certain steps in order, described below, and checks for a successful match. Once a match is determined, it skips all the other succeeding steps and proceeds to deduce the request's Content-Type according to the specifics of the step it matched.

  1. If the consumes property of a (handler class or method's4) @RequestMapping annotation exists and the request's Content-Type header matches any of the consumes property's values, then the matching value is assumed to be the request's Content-Type.
  2. If the request has a non-null query parameter named dataFormat and it matches any of the valid values (json, xml, and default), then the matching value's equivalent Content-Type is used. If dataFormat is an empty string, then it uses the respective Content-Type of the pre-configured default Content-Type5.
  3. If the request's Content-Type header exists, then this is assumed to be the Content-Type of the request.
  4. If the request's Accept header exists, then this is assumed to be the Content-Type of the request.

If none of the options above matched, then an error is thrown.

Parameter Mapping

On top of the variable mapping Spring already does, TORO Integrate adds a couple of important changes to the default Spring MVC implementation:

  • InputStream, Reader, byte[], CharSequence, or String-typed method parameters with the name body are assigned the content of the request or a request parameter called body. If neither is present and your code is annotated with XML or JSON, then the value set by the annotation is used; otherwise, an exception is thrown.
  • DataWrapper parameters are populated with parsed XML or JSON data. TORO Integrate uses Groovy's JsonSluper or XmlSlurper to parse the request's content and assigns the parsed content to DataWrapper's data property. The data property's data type varies depending on the parsed content but your code doesn't have to worry about that; such is the dynamic nature of Groovy.
  • If a required path variable is missing, TORO Integrate will attempt to retrieve it from the request parameter map.
  • String method parameters with the name internalId are used as the service call's Tracker document ID6. Tracker documents represent recorded service calls. Setting the call's ID is useful if you intend to update an existing Tracker document. If it isn't set, TORO Integrate will create a new Tracker document for the service invocation (if it's set to track the request).
  • String method parameters with the name path are assigned the value of the request's URI.
  • String method parameters with the name method are assigned the String representation of the request's HTTP method.
  • ESBPackage method parameters are set to hold the object representation of the Integrate Package containing the Groovy controller class.
  • MonitorRule method parameters are assigned the object representation of the Monitor Rule that the request matched.
  • An InvokeCount method parameter can be added to get the total number of invocations (per timeframe) made by the user configured to run the service (under the rule which matched the request).
  • MonitorStats method parameters can also be added to obtain both MonitorRule and InvokeCount objects (described above); it is a wrapper for both objects.
  • A Map method parameter named parameters would be filled in with the following entries:
    • "request" -> contains the request object
    • "response" -> contains the response object
    • "internalId" -> contains the Tracker document ID of the service call
    • "path" -> the request URI
    • "esbPackage" -> the Integrate Package which contains the service
    • "method" -> the request HTTP method
    • "body" -> the body of the request

Supported Handler Method Return Types

Spring is pretty flexible when it comes to return types; it includes support for the following:

@ResponseBody annotation

As explained above, handler methods should be annotated with @ResponseBody if their return values must be sent as the body of the HTTP response.

For the complete and detailed list, please refer to this document section from Spring which enumerates supported method return types.

As said in the linked Spring document, you can always return custom types and like the types above, registered HttpMessageConverters will take care of the conversion work for you (as long as you annotate the method with @ResponseBody). For convenience, TORO Integrate has included the APIResponse class which you may return from your RESTful web service handler methods.

Response Content-Type Resolution

Similar to when resolving HTTP request Content-Types, TORO Integrate also performs a certain ordered series of checks in order to determine the appropriate HTTP response Content-Type. Once a check matches, all the other proceeding checks are ignored.

  1. If the produces property of a (handler class or method's7) @RequestMapping annotation exists and the request's Accept header matches any of the produces property's values, then the matching value is assumed to be the response's Content-Type.
  2. If the request has a non-null query parameter named dataFormat and it matches any of the valid values (json, xml, and default), then the matching value's equivalent Content-Type is used. If dataFormat is an empty string, then it uses the respective Content-Type of the pre-configured default Content-Type4.
  3. If the request's Accept header exists, then this is assumed to be the appropriate Content-Type of the response.

If none of the options above matched, then an error is thrown.


  1. You may choose other formats by creating and registering your own custom HttpMessageConverters and then specifying the produces property of the @RequestMapping annotation. 

  2. You must prepend the path with the root URL where your TORO Integrate instance is hosted at (e.g. localhost:8080) and the prefix of your API endpoints, which is set to api by default. 

  3. Because @Controller is an implementation of @Component

  4. If defined, the consumes property of a method's @RequestMapping annotation takes precedence over that of a class's. 

  5. The default format is set via the default.format instance property

  6. Service calls are made through HTTP requests or Integrate Endpoints

  7. If defined, the produces property of a method's @RequestMapping annotation takes precedence over that of a class's.