Threading is a fantastic and (relatively) simple way to run parallel HTTP requests against a Jira or Confluence instance. Multithreading can drastically cut down the length of time it takes for a piece of code to run, as each HTTP request is not waiting for the previous request to finish. In Groovy we can achieve threading by using the Thread class or the ExecutorService framework.
However, threading isn’t a viable solution when it comes to Jira (or Confluence) on the Cloud. Because Jira Cloud is a shared hosting environment, there are a number of reasons why Atlassian has put strict limitations on the use of threading. Chief among these concerns are performance and security. If anyone can run any number of threads that they want, this necessarily impacts the other users of the shared hosting environment.
Similarly, multithreading in a shared hosting environment can cause data inconsistencies and synchronization issues, which easily lend themselves to visions of major security issues.
Though we cannot use threading on Jira Cloud, we can use async. Simply put, the difference between the two is that threading uses concurrent or parallel threads of execution to achieve concurrency. That is, it literally splits the tasks into separate requests to the CPU.
Async, on the other hand, uses non-blocking code to achieve concurrency. That is, the script simply says “okay, we’re going to run this part of the script in the background, and when it’s done we’ll collect the results.” Async still technically uses threads, but it uses them in a way that is different than the ExecutorService. The material difference between the two is that threading creates new threads, which uses up resources. Async uses whatever threads are available from the existing thread pool. What’s important to know is that the Atlassian Cloud supports async, and so that’s what we use.
Let’s look at an example.
The process to actually declare an asynchronous method isn’t terribly complicated. The script below essentially does two things: it creates HTTP connections using an array of URLs, and it adds those tasks to a collection of asynchronous tasks. When the tasks have each completed, the results are added to a string buffer. When all the async tasks have completed, the contents of the string buffer are returned.
The process of creating the HTTP requests isn’t new or complicated, we do that all the time when working with the REST API. The only complication here is understanding the method that starts the future tasks, and understanding the structure of the loop that runs the requests.
import java.net.HttpURLConnection
import java.net.URL
import groovy.transform.CompileStatic
import java.util.concurrent.FutureTask
@CompileStatic
def async (Closure close) {
def task = new FutureTask(close)
new Thread(task).start()
return task
} //Tell Groovy to use static type checking, and define a function that we use to create new async requests
String username = "<username>"
String password = "<password>"
//Define the credentials that we'll use to authenticate against the server/DC version of Jira or Confluence
//If we want to authenticate against Jira or Confluence Cloud, we'd need to replace the password with an API token, and replace the username with an email address
// Define a list of URLs to fetch
def urls = [
"<URL>",
"<URL>",
"<URL>",
"<URL>",
"<URL>",
]
// Define a list to hold the async requests
def asyncResponses = []
def sb = []
// Loop over the list of URLs and make an async request for each one
urls.each {
url ->
//For each URL in the array
def asyncRequest = {
//Define a new async request object
HttpURLConnection connection = null
//Define a new HTTP URL Connection, but make it null
try {
// Create a connection to the URL
URL u = new URL(url)
connection = (HttpURLConnection) u.openConnection()
connection.setRequestMethod("GET")
//Create a new HTTP connection with the current URL, and set the request method as GET
connection.setConnectTimeout(5000)
connection.setReadTimeout(5000)
//Set the connection parameters
String authString = "${username}:${password}"
String authStringEncoded = authString.bytes.encodeBase64().toString()
connection.setRequestProperty("Authorization", "Basic ${authStringEncoded}")
//Set the authentication parameters
// Read the response and log the results
def responseCode = connection.getResponseCode()
def responseBody = connection.getInputStream().getText()
logger.warn("Response status code for ${url}: ${responseCode}")
sb.add("Response body for ${url}: ${responseBody}")
} catch (Exception e) {
// Catch any errors
logger.warn("Error fetching ${url}: ${e.getMessage()}")
} finally {
// Terminate the connection
if (connection != null) {
connection.disconnect()
}
}
}
asyncResponses.add(async (asyncRequest))
}
// Wait for all async responses to complete
asyncResponses.each {
asyncResponse ->
asyncResponse.get()
}
return sb
Leave a Reply