Skip to main content
Version: Current

Custom endpoints - basics

To create custom endpoints, you need to define a gpal file called web-handler.kts.

Once a web-handler.kts is available within your {product}/script directory, it is immediately available as an endpoint in the router.

Simple endpoint example

Here is a simple custom endpoint that provides all trades in the database:

webHandlers {
endpoint(GET, "all-trades") {
handleRequest {
db.getBulk(TRADE)
}
}
}

If you define the file myapp-web-handler.kts, it is available at myapp/all-trades.

Producing output

By default, the handleRequest function infers the output of the endpoint from the return value of the block. So, if you are only producing output and not receiving any input, you do not need type parameters. The output value is returned as JSON, using the standard serialisation mechanism.

This is sufficient for most cases, but you can customise the output.

Content type

If you want to override the default behaviour and specify an output type, use the call produces with a content type. The following content types are supported out of the box:

Content typeName in codeData
application/jsonContentType.APPLICATION_JSONJSON
application/octet-streamContentType.APPLICATION_OCTET_STREAMBinary
text/csvContentType.TEXT_CSVCSV
text/yamlContentType.TEXT_YAMLYAML
text/xmlContentType.TEXT_XMLXML

You can set multiple content types in the produces call; the client can specify which one is returned by setting the Accept header. If no Accept header is specified, then the first content type will be returned.

webHandlers {
endpoint(GET, "all-trades") {
produces(ContentType.TEXT_CSV)
handleRequest {
db.getBulk(TRADE)
}
}

endpoint(GET, "all-trades-multi") {
produces(ContentType.TEXT_CSV, ContentType.APPLICATION_JSON)
handleRequest {
db.getBulk(TRADE)
}
}
}

Return types

By default, the returned value is serialised using the default serialiser. However, this is overruled if the return type specified is in the table below.

If you specify a return type, the value returned and the behaviour will be as per the table below, regardless of the Accept header.

However, if the produces function is used, then the Accept header will always be respected.

Return typeBehaviourDefault Content-Type
UnitNo response is returnedn/a
StringThe string is returned as the responsen/a
ByteArrayThe byte array is returned as the responsen/a
File, PathThe file is streamed as the responseapplication/octet-stream
InputStreamThe input stream is streamed as the responseapplication/octet-stream

Receiving input

Endpoints can also receive input. For this, the http request must include a body. The body can be parsed and will be available in the body property of the handleRequest block. When endpoints receive input, you must provide type parameters for both the request body and the response type:

webHandlers {
endpoint<Trade, InsertResult<Trade>>(POST, "insert-trade") {
handleRequest {
db.insert(body)
}
}
}

Content type

As with producing output, you can specify the content type of the request body using the accepts function. An endpoint is able to accept multiple content types. If no content type is specified, then the endpoint defaults to accept application/json.

These content types are supported out of the box:

Content typeName in codeData
application/jsonContentType.APPLICATION_JSONJSON
text/csvContentType.TEXT_CSVCSV
text/yamlContentType.TEXT_YAMLYAML
text/xmlContentType.TEXT_XMLXML
webHandlers {
endpoint<Trade, InsertResult<Trade>>(POST, "insert-trade") {
accepts(ContentType.APPLICATION_JSON, ContentType.TEXT_XML)
handleRequest {
db.insert(body)
}
}
}

File uploads

To support file uploads, use the multipartEndpoint function. This function parses the request body as a multipart request and makes the files available in the fileUploads property of the handleRequest block.

webHandlers {
val tmp = Files.createTempDirectory("test")
multipartEndpoint("test") {
handleRequest {
body.fileUploads.forEach {
it.copyTo(tmp.resolve(it.fileName))
}
}
}
}

Permissioning and authorisation

Endpoints support a permissioning model very similar to Event Handlers and Request Servers. The permission function has different options for:

  • requiring specific permission codes
  • entity-level authorisation on the input - similar to Event Handlers
  • entity-level filtering on the output - similar to Request Servers

Furthermore, endpoints can be made available to unauthenticated users in the config block.

Permission codes

In this example, the all-trades endpoint is available to users with the TRADER permission code:

webHandlers {
endpoint(GET, "all-trades") {
permissioning {
permissionCodes("TRADER")
}
handleRequest {
db.getBulk(TRADE)
}
}
}

Entity-level authorisation - input

In this example, the insert-trade endpoint is available to all users; however, users can only insert trades in currencies for which they have permission:

endpoint<Trade, InsertResult<Trade>>(POST, "auth") {
permissioning {
requestAuth("CCY") {
field { currencyId }
}
}
handleRequest {
db.insert(body)
}
}

Entity-level filtering - output

In this example, the all-trades endpoint is available to all users; however, users can only see trades for the currencies they are permissioned to view:

webHandlers {
endpoint(GET, "all-trades") {
permissioning {
responseAuth("CCY", flow<Trade>()) {
field { currencyId }
}
}
handleRequest {
db.getBulk(TRADE)
}
}
}