q# Capturing data

Reference | Web Services


Aside from the simplest GET and HEAD requests, most web service requests include data to be processed and/or stored. This data can be included in four parts of the HTTP request:

  • The request body
  • The request path (the part of the URL after the domain and before the ? symbol)
  • Query parameters (the name/value pairs after the ? symbol)
  • Request headers

Granitic provides functionality to automatically capture path, query parameter and body data and parse it into any Go struct that you nominate, assuming that you are using an instance of ws.WsHandler as your handler component.

Data capture target

The struct that you nominate to receive data is referred to as the target or target object. An instance will be automatically be created by your handler. There are number of ways in which your code can tell Granitic which type to instantiate by implementing interfaces or specific method signatures on your logic component. The logic component documentation explains this in detail.

Request body parsing

Data encoded in the HTTP request’s body is parsed by a component implementing ws.Unmarshaller that is injected into the Unmarshaller field on your handler.

If you have enabled the JSONWs or XMLWs facility, Granitic will automatically inject an Unmarshaller into your handler.

Mapping body data to struct fields

How data in the body is mapped to fields in your target struct depends on the implemention of ws.Unmarshaller. The built-in Unmarshallers for JSON and XML are documented in the JSONWs](fac-json-ws.md) and XMLWs facility documentation.

Providing a custom Unmarshaller

A common pattern for web-services is that the majority of endpoints on a service support a single text-based standard for request and response bodies (e.g. all JSON). It is also common for a small number of endpoints to use a different standard (e.g. receiving binary or HTML form encoded data). For those cases you can write your own component implementing ws.Unmarshaller and explicit inject them into your handler in your component definition files.

Errors during parsing

If the request body cannot be parsed into your target object, a FrameworkError will be recorded. Framework errors are explained in the web service error handling documentation documentation, but the practical effect is the the client will receive an HTTP 400 response.

Path binding

Extracting information from a request’s path and injecting it into your target object is known as path binding. Path binding allows REST-like behaviour where information about resources to read or manipulate are embedded in the request path.

Consider a request:

/artist/12/album/2 GET

There are two IDs embedded in the path. Assuming we have target object like:

type AlbumQuery struct {
	ArtistID int
	AlbumID int
}

We can set up our handler to capture path information into the target object like:

"getAlbumHandler": {
  "type": "handler.WsHandler",
  "HTTPMethod": "GET",
  "PathPattern": "^/artist/([\\d]+)/album/([\\d]+)",
  "BindPathParams": ["ArtistID", "AlbumID"]
}

Capture groups

The regular expression for the endpoint URL on this handler (with JSON escaping removed) is:

^/artist/([\d]+)/album/([\d]+)

Which defines two regular expression capture groups in brackets. Both capture groups in this example match a sequence of digits.

The value of the field BindPathParams is a list of field names on the target object. The order is significant - the first field in the list will be populated with the value from the first capture group and so on.

Using regular expressions to enforce type safety

In the example above, the handler will only match the client’s request if the values provided in the path are integers. The client would just receive an HTTP 404 response if they requested: /artist/-12/album/true, for example. It is recommended that you adopt this practise.

Query parameter binding

Query parameters are the name-value pairs after the ? separator in the request URL.

Consider a request:

/artist-album?artistID=12&albumID=2

Assuming the same target object from the path binding example, you have two options when defining a handler

Auto-binding

"getAlbumHandler": {
  "type": "handler.WsHandler",
  "HTTPMethod": "GET",
  "PathPattern": "^/artist-album,
  "AutoBindQuery": true
} 

With AutoBindQuery set to true, the values of query parameters will be injected into fields on your target object where the field names exactly (case sensitive) match the query parameter name.

Explicit binding

"getAlbumHandler": {
  "type": "handler.WsHandler",
  "HTTPMethod": "GET",
  "PathPattern": "^/artist-album,
  "FieldQueryParam": {
    "ArtistID": "ArtistID",
    "AlbumID": "AlbumID"
  }
}

FieldQueryParam is a string/string map where the keys are field names on the target object and the values are the names of query parameters. This approach allows you define names of query parameters that break the rules of Go struct field names e.g:

"FieldQueryParam": {
  "ArtistID": "artist-id",
  "AlbumID": "album-id"
}

Missing values

If a query parameter is not provided, no error will be thrown. This scenario should be handled by validation

Incorrect types

If a query parameter is provided where the value is incompatible with the type of the field on the target object, a framework error will be raised.

Path and query supported types

Path and query binding supports the same set of types to parse data into. These can be:

  • Any Go basic type (except uintptr, complex64 and complex128)
  • Any of Granitic’s nilable struct types
  • A slice of any of the above

For slices, the query/path parameter value should be a comma delimited list of values.

For numeric types a framework error will be raised if the parameter value is numeric, but doesn’t fit into the target type.

HTTP request headers

There is no explicit support in Granitic for binding HTTP request headers to target objects. You may choose to allow your logic component to have access to the headers (and the underlying HTTP request and response objects) by setting AllowDirectHTTPAccess to true on your handler.

There are integration points for IAM, instrumentation, versioning and identification where you will have access to HTTP request headers without having to set AllowDirectHTTPAccess to true.


Next: Nilable types

Prev: Endpoints and handlers