I'm trying to upload a file to a scriptrunner REST custom end point. The file upload is being initiated from a dialog box with the following code
(function ($) {
$("#dialog-close-button").click(function (e) {
e.preventDefault();
AJS.dialog2("#img-dialog").hide();
});
$("#img-form").submit(function(e) {
var fd = new FormData($("#img-form").get(0));
$.ajax({
type: "POST",
data: fd,
url: "/rest/scriptrunner/latest/custom/adminImgUploadDialogSave",
dataType : 'json',
processData: false,
contentType: false,
headers: {
'Content-Type' : 'multipart/form-data',
'X-Atlassian-Token': 'no-check',
'Access-Control-Allow-Origin' : '*'
},
}).done(function(response) {
if (response.status=="ok") {
AJS.dialog2("#img-dialog").hide();
}
});
e.preventDefault();
return false;
});
})(AJS.$);
In the rest endpoint I have
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript
import groovy.json.JsonBuilder
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response
import javax.servlet.http.HttpServletRequest
@BaseScript CustomEndpointDelegate delegate
adminImgUploadDialogSave(
httpMethod: "POST", groups: ["jira-administrators"]
) { MultivaluedMap queryParams, String body, HttpServletRequest req ->
log.error("adminImgUploadDialogSave")
log.error(body)
log.error(req.getParts().toString())
//if (filename==null) {
// return Response.ok(new JsonBuilder([status: 'not ok']).toString()).build()
//} else {
// do stuff
// return Response.ok(new JsonBuilder([status: 'ok']).toString()).build()
//}
}
body contains what I would expect
-----------------------------183231904016123 Content-Disposition: form-data; name="atl_token" BD3R-R2T8-HXRR-WMXA|b2e4ac8fc23366985d8cb8d8b7e9e128d47a5c0b|lin -----------------------------183231904016123 Content-Disposition: form-data; name="image"; filename="nodetype-child.png" Content-Type: image/png lots of binary data -----------------------------183231904016123--
but req.getParts() always returns an empty list.
How do I load/parse the form data?
First of all, I created a new class `StringMultiPartFormParser ` which parses the form and places all the plain form parameters into `parameters` and all files into `files`.
package mypackage
import groovy.transform.CompileStatic
import org.apache.commons.fileupload.FileItem
import org.apache.commons.fileupload.FileUpload
import org.apache.commons.fileupload.UploadContext
import org.apache.commons.fileupload.disk.DiskFileItemFactory
import javax.servlet.http.HttpServletRequest
@CompileStatic
class StringMultiPartFormParser implements UploadContext {
private final String contentType
private final String characterEncoding
private final Long contentLength
private final String body
Map<String, String> parameters = [:]
Map<String, FileItem> files = [:]
StringMultiPartFormParser(HttpServletRequest request, String body) {
this.contentType = request.contentType
this.characterEncoding = request.characterEncoding
this.contentLength = request.contentLength
this.body = body
new FileUpload(new DiskFileItemFactory()).parseRequest(this).forEach { item ->
if (item.isFormField()) {
parameters.put(item.getFieldName(), item.getString())
} else {
files.put(item.getFieldName(), item)
}
}
}
@Override
long contentLength() {
return contentLength
}
@Override
String getCharacterEncoding() {
return characterEncoding
}
@Override
String getContentType() {
return contentType
}
@Override
int getContentLength() {
return -1
}
@Override
InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(body.getBytes())
}
}
And I use it in the REST endpoint like this:
package mypackage
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript
import javax.servlet.http.HttpServletRequest
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response
@BaseScript CustomEndpointDelegate delegate
uploadTemporaryAttachment(
httpMethod: 'POST',
groups: ['administrators']
) { MultivaluedMap queryParams, String body, HttpServletRequest request ->
final form = new StringMultiPartFormParser(request, body)
final someParameter = form.parameters.get('someParameter')
final file = form.files.get('file')
// Parameters and files validation goes here
// ...
//
log.error(someParameter)
log.error(file.getName())
log.error(file.getContentType())
log.error(file.getSize())
return Response.status(200).entity([
someParameter: someParameter,
fileName: file.getName(),
fileContentType: file.getContentType(),
fileSize: file.getSize(),
]).build()
}
Hey, Daniel. So, after hacking with this at length, I concluded that there's not a really robust way to do this with ScriptRunner out of the box. Like you, I was able to hack together something that handled uploading basic text files, but images proved more problematic. I've submitted a bug report for that. https://productsupport.adaptavist.com/browse/SRPLAT-211
We may just end up documenting a working example or pointing people to how to roll their own REST Endpoint using JAX, but you can watch that for further developments on multipart support in ScriptRunner REST Endpoints.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I ended up doing this
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import com.atlassian.jira.config.properties.ApplicationProperties
import com.atlassian.jira.component.ComponentAccessor
import groovy.transform.BaseScript
import groovy.json.JsonBuilder
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response
import javax.servlet.http.HttpServletRequest
import java.io.FileOutputStream
import groovy.json.JsonSlurper
import java.util.regex.*
@BaseScript CustomEndpointDelegate delegate
TestImageUpload(httpMethod: "POST") { MultivaluedMap queryParams, String body, HttpServletRequest req ->
def params = new JsonSlurper().parseText(body)
Pattern re = Pattern.compile('^data:[^;]*;base64,(?<data>.*)$')
Matcher m1 = re.matcher((String)params['image'])
if (m1.matches()) {
String imageb64 = m1.group('data')
byte[] filedata = imageb64.decodeBase64()
String filename = '/var/something/somefile'
File file = new File(filename)
if (!file.exists()) {
file.createNewFile()
}
FileOutputStream fos = new FileOutputStream(filename)
fos.write(filedata)
fos.close()
return Response.ok(new JsonBuilder([status: 'ok']).toString()).build()
} else {
return Response.ok(new JsonBuilder([status: 'not ok']).toString()).build()
}
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Vitaliy, have you tried this approach with StringMultiPartFormParser?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi - this wasn't possible until a recent release.
In the most recent releases we have added a new closure signature where we do not attempt to read the request body (and corrupt it for binary files).
A worked example is here: https://library.adaptavist.com/entity/file-upload-to-rest-endpoint
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.