REST Endpoints

Representational State Transfer (REST) endpoint is a URL that runs a script. REST endpoints are configured programmatically. You can define REST endpoints in ScriptRunner, for example, to:

  • Use in dashboard gadgets.

  • Receive notifications from external systems.

  • Plug gaps in the official REST API.

  • Allow all your XHRs to proxy through to other systems.

Adding a REST Endpoint

Follow this task to create a custom REST endpoint:

  1. Select the Cog icon, and then select General Configuration.

  2. Scroll to the ScriptRunner section in the left-hand navigation, and then select REST Endpoints.

  3. Select Add a New Item, and then select the Custom Endpoint

Use the following REST endpoint example to examine the different parts of the script:

import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.json.JsonBuilder
import groovy.transform.BaseScript

import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

@BaseScript CustomEndpointDelegate delegate // <1>

doSomething( // <2>
    // bitbucket-administrators is a group we have added ourselves
    httpMethod: "GET", groups: ["bitbucket-administrators"] // <3>
) { MultivaluedMap queryParams, String body -> // <4>
    Response.ok(new JsonBuilder([abc: 42]).toString()).build() // <5>
}
Copy

Line 8: This line makes methods in your script recognizable as endpoints, which is required.

Line 10: The name of the REST endpoint, which forms part of the URL. In this example, it is doSomething.

Line 12: This line configures the endpoint and determines which HTTP verb to handle and what groups to allow.

Line 13: This line contains parameters that are provided to your method body.

Line 14: The body of your method, where you will return a javax.ws.rs.core.Response object.

You can add this REST endpoint to the list of configured endpoints as an inline script or by copying into a file and adding that file as a script file. To test this endpoint, type this text into your browser:

<bitbucket_base_url>/rest/scriptrunner/latest/custom/doSomething

Notice the last part of the text is the name doSomething.

Alternatively, you could type this into the command line utility:

curl -u admin:admin <bitbucket_base_url>/rest/scriptrunner/latest/custom/doSomething {"abc":42}
  • Again, notice the name doSomething in each command.

  • admin:admin corresponds to a username and password.

If you are using a file, you can change the response. You may need to select the Scan button on the REST Endpoints page before calls to the endpoint return the new response. See the section on Script Root Scanning below.

Configuration

The general format of a method defining a REST endpoint is:

groovy
methodName (Map configuration, Closure closure)

For the configuration, only the following options are supported:

KeyValue

httpMethod

Choose one of:  GET,  POST,  PUT,  DELETE

groups

One or more groups. If the requesting user is in any of the groups, the request is allowed.

Either or both of these can be omitted. If you omit the groups attribute, the endpoint will be available to unauthenticated users.

Use these parameters for the closure:

ParameterTypeDescription

MultivaluedMap

queryParams

This corresponds to the URL parameters.

String

Content

This is the body of the request for  httpMethod  (POST,  PUT, etc.).

HttpServletRequest

Request

This returns the requesting user for the instance.

You can use any of these forms for your closure:

something() { MultivaluedMap queryParams -> something() { MultivaluedMap queryParams, String body -> something() { MultivaluedMap queryParams, String body, HttpServletRequest request -> something() { MultivaluedMap queryParams, HttpServletRequest request->

The contents of your closure depends on what you need access to.

Where the closure signature contains the body variable, the request input stream is read before your closure is executed, and the read data is passed to the closure in the body.

The request input stream can only be read once. If you want to avoid the request input stream from being read before your code executes, for instance if you are reading file uploads, use the final form of the closure: 

groovy
something() { MultivaluedMap queryParams, HttpServletRequest request->

Access Request URL

Sometimes you may need to use the URL path after your method name. In the following example, you want to retrieve  /foo/bar:

groovy
<base_url>/rest/scriptrunner/latest/custom/doSomething/foo/bar

Use the 3-parameter form of the closure definition and call the  getAdditionalPath  method from the base class.

For example:

groovy
doSomething() { MultivaluedMap queryParams, String body, HttpServletRequest request -> def extraPath = getAdditionalPath(request) // extraPath will contain /foo/bar when called as above }

In previous versions, an  extraPath  variable was used in the scripts. However, this is not thread-safe. Use the method above.

Script Root Scanning

As well as manually adding scripts or files via the UI, ScriptRunner scans your script roots for scripts that contain REST endpoints and automatically register them. To enable the scanning, set the property  plugin.rest.scripts.package  to a comma-delimited list of packages. For example:

groovy
-Dplugin.rest.scripts.package=com.acme.rest

On plugin enablement, scripts/classes under this package are scanned and registered if the scripts contain the following line:

groovy
@BaseScript CustomEndpointDelegate delegate

Package Declarations and File Paths

Your REST Endpoint's code must begin with a package declaration that matches the package configured in the system property. Likewise, the file path in your script root must match that package declaration as well.

For example, if your plugin.rest.scripts.package  system property is com.acme.rest and you want to create a custom REST Endpoint with a file named MyCustomRestEndpoint.groovy, then:

  1. The first line of the MyCustomRestEndpoint.groovy file should be package com.acme.rest .
  2. The file should have a line like @BaseScript CustomEndpointDelegate delegate  as normal.
  3. The path to the file within your script root should be com/acme/rest/MyCustomRestEndpoint.groovy .

Subpackages should be okay, so long as the sub-directory matches the package. For example, you might put the file in com/acme/rest/widgets so long as your package declaration is com.acme.rest.widgets in the top line of the file.

If you are receiving unexpected HTTP 500 errors when trying to access your REST Endpoints that were added through Script Root Scanning, check your package declaration. Case sensitivity may be an issue as well, depending on your filesystem.


Examples

Allow Cross-Domain Requests

This example demonstrates how to access the official REST API from another domain. Bitbucket does not support cross-origin requests (CORS).

If you want to GET the following REST endpoint from http://some.domain.com/, the code should look like this example:

<bitbucket_base_url>/rest/api/1.0/projects/PROJECTKEY/repos/reposlug


However, you may receive an error similar to this:

Origin http://some.domain.com is not allowed by Access-Control-Allow-Origin.

Most of this code is involved with proxying the original request through to the official REST API. The extraPath captures the needed location to proxy the request to.

@BaseScript CustomEndpointDelegate delegate

// replace this url with your Bitbucket instance url
def http = new RESTClient("https://bitbucket.instance.com")

// add authentication to proxy request
http.client.addRequestInterceptor(new HttpRequestInterceptor() {

    void process(HttpRequest httpRequest, HttpContext httpContext) {
        httpRequest.addHeader('Authorization', 'Basic ' + 'username:password'.bytes.encodeBase64().toString())
        httpRequest.addHeader('X-Atlassian-Token', "no-check")
    }
})

bitbucketproxy(
    httpMethod: "GET", groups: ["bitbucket-administrators"]
) { MultivaluedMap queryParams, String body, HttpServletRequest request ->

    // get the path after the method name, so we can proxy the request
    def extraPath = getAdditionalPath(request)

    HttpResponse response = http.request(GET, JSON) {
        uri.path = extraPath
    }

    Response
        .ok(new JsonBuilder(response.data).toString())
        .header("Access-Control-Allow-Origin", "*") // allows all origins to access resource
        .build()
}
Copy

To test, you could use the following code:

curl -v -X GET -u admin:admin <bitbucket_base_url>/rest/scriptrunner/latest/custom/bitbucketproxy/rest/api/1.0/projects/PROJECTKEY/repos/reposlug

The following header is returned and allows all domains to access the resource:

Access-Control-Allow-Origin: *

You can have multiple methods with the same name in the same file, which is useful to do simple CRUD REST APIs.

An example:

POST /bitbucketproxy - proxy POST requests (create) PUT /bitbucketproxy - proxy PUT requests (update) DELETE /bitbucketproxy - proxy DELETE requests (delete) GET /bitbucketproxy - proxy GET requests (get)

Further Examples



On this page