I'm developing a REST API with Play 2 and I'm wondering how to implement file upload functionality.
I've read the official Play documentation but it just provides a multipart/form-data example, while my backend does not provide any form... it just consists of a REST API to be invoked by a JavaScript client or whatever else.
That said, what's the correct way to implement such an API? Should I implement a PartHandler and then still use the mutipartFormData parser? How should I pass the file content to the API? Is there any exhaustive example on this topic?
Any help would be really appreciated.
You should look into BodyParsers: http://www.playframework.com/documentation/2.2.x/ScalaBodyParsers
What you are trying to do is not especially complicated, especially if you are only handling smaller files that would fit in memory. After all uploading a file is just about sending the file as a body of a POST or something like that. It is not any different from receiving some XML or JSON in a request.
Hope this helps
import org.apache.http.entity.mime._
import java.io.File
import org.apache.http.entity.mime.content._
import java.io.ByteArrayOutputStream
import play.api.libs.ws.WS
val contents ="contents string"
val file = File.createTempFile("sample", ".txt")
val bw = new java.io.BufferedWriter(new java.io.FileWriter(file)
bw.write(new_contents);
bw.close();
builder.addPart("file", new FileBody(file, org.apache.http.entity.ContentType.create("text/plain"), "sample"))
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
val entity = builder.build
val outputstream = new ByteArrayOutputStream
entity.writeTo(outputstream)
val header = (entity.getContentType.getName -> entity.getContentType.getValue)
val response = WS.url("/post/file").withHeaders(header).post(outputstream.toByteArray())
To pass your contents, depending on your client side, you can encode the contents to Base64 at client side to pass the contents as Json (You can use Json body parser). Then on the server side you can decode the contents using a Base64 decoder (e.g. Apache Commons) to get the byte array. It will be as simple as
Base64.decodeBase64(YourEncodedFileContent)
When you have the byte array you can simply write it on disk or save it into database etc. We are using this approach in production and it works fine however we only handle small file uploads.
OK, thank you all for your suggestions... here below is how I solved my issue:
object Files extends Controller {
def upload = SecuredAction[Files.type]("upload").async(parse.multipartFormData(partHandler)) { implicit request =>
future { request.body.files.head.ref match {
case Some((data, fileName, contentType)) => Ok(success(Json.obj("fileName" -> fileName)))
case _ => BadRequest
}}.recover { case e =>
InternalServerError(error(errorProcessingRequest(e.getMessage)))
}
}
...
private def partHandler = {
parse.Multipart.handleFilePart {
case parse.Multipart.FileInfo(partName, fileName, contentType) =>
Iteratee.fold[Array[Byte], ByteArrayOutputStream](
new ByteArrayOutputStream
) { (outputStream, data) =>
outputStream.write(data)
outputStream
}.map { outputStream =>
outputStream.close()
Some(outputStream.toByteArray, fileName, contentType.get)
}
}
}
}
I hope it helps.
while my backend does not provide any form... it just consists of a REST API to be invoked by a JavaScript client
Then your backend is not a REST API. You should follow the HATEOAS principle, so you should respond with links and forms along with data to every GET request. You don't have to send back HTML, you can describe these things with hypermedia json or xml media types, for example with JSON-LD, HAL+JSON, ATOM+XML, etc... So you have to describe your upload form in your preferred hypermedia, and let the REST client to turn that description into a real HTML file upload form (if the client is HTML). After that you can send a multipart/form-data as usual (REST is media type agnostic, so you can send data in any media type you want, not just in a JSON format). Check the AJAX file upload techniques for further detail...
Related
I would like to send data of different data types via websocket of different data types to a python server. At first, I used to only send a blob that I got from MediaRecorder. That worked perfectly fine.
Now I would like to add a functionality that sends a string to the server and want it to be handled depending on what type is sent.
My idea was to build a json string like so:
myjson={"type": "type_a", "content": "info"}
I tried adding a blob to this dict like shown below.
messageJson = JSON.stringify({"type": "recordedAudio", "content": blob});
When I tried to transform that back to a webm file it didn't. (Media player cannot play file, but no Error message)
async def handler(websocket):
while True:
message = await websocket.recv()
messageAsJSON = json.loads(message)
messageType = messageAsJSON["type"]
messageContent = messageAsJSON["content"]
encode_data = json.dumps(messageContent).encode('utf-8')
if messageType == "recordedAudio":
if os.path.isfile("output.wav"):
os.remove("output.wav")
with open("output.wav", 'ab') as f:
f.write(encode_data)
Where did I go wrong? From my understanding of blobs something like this should be possible, but I'm not sure. Do I have to convert the blob some how before adding it to the dict?
I use an html table where it's content can be changed with mouse drag and drop implemented. Technically, you can move the data from any table cell to another. The table size 50 row * 10 column with each cell given a unique identifier. I want to export it to .xlsx format with C# EPPlus library, and give back the exported file to client.
So I need the pass the whole table data upon a button press and post it to either a web api or an mvc controller, create an excel file (like the original html table data) and send it back to download with browser.
So the idea is to create an array which contains each of table cell's value ( of course there should be empty cells in that array), and post that array to controller.
The problem with that approach lies in the download, if I call the api or mvc controller with regular jquery's ajax.post it did not recognize the response as a file.
C# code after ajax post:
[HttpPost]
public IHttpActionResult PostSavedReportExcel([FromBody]List<SavedReports> savedReports, [FromUri] string dateid)
{
//some excel creation code
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(new MemoryStream(package.GetAsByteArray()))
};
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = dateid + "_report.xlsx"
};
ResponseMessageResult responseMessageResult = ResponseMessage(response);
return responseMessageResult;
}
Usually, for this kind of result I could use window.location = myurltocontroller to download properly , but that is only for GET requests, POST anything is not possible.
I found some answers which could help me in this topic:
JavaScript post request like a form submit
This points out I should go with creating a form, which passes the values, but I do not know how to do so in case of arrays (the table consists 50*10 = 500 values which I have to pass in the form)
I tried some only frontend solutions to the html-excel export problem, which of course does not require to build files on api side, but free jquery add-ins are deprecated, not customizeable, handle only .xls formats, etc.
I found EPPlus nuget package a highly customizeable tool, that is why I want to try this is at first place.
So the question is: how can I post an array of 500 elements, that the controller will recognize, generate the file, and make it automatically download from browser?
If you can provide some code that would be fantastic, but giving me the right direction is also helpful.
Thank you.
You can use fetch() (docs) to send the request from the JS frontend. When the browser (JS) has received the response, it can then offer its binary content as a download. Something like this:
fetch("http://your-api/convert-to-excel", // Send the POST request to the Backend
{
method:"POST",
body: JSON.stringify(
[[1,2],[3,4]] // Here you can put your matrix
)
})
.then(response => response.blob())
.then(blob => {
// Put the response BLOB into a virtual download from JS
if (navigator.appVersion.toString().indexOf('.NET') > 0) {
window.navigator.msSaveBlob(blob, "my-excel-export.xlsx");
} else {
var a = window.document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = "my-excel-export.xlsx";
a.click();
}});
So the JS part of the browser actually first downloads the file behind the scenes, and only when it's done, it's triggering the "download" from the browsers memory into a file on the HD.
This is a quite common scenario with REST APIs that require bearer token authentication.
I am calling a REST API in my project for creating some records.
Everything is working fine but I got a challenge, the JSON request body is too big (having thousands of keys and values).
Now I want to compress the request body. I tried it using JavaScript
var reqJSON = { ... } // too big JSON object
var compressedJSON = JSON.stringify(reqJSON, null, 0); // converting JSON to String (compression)
Now I am sending the string in the request body and converting this string into JSON at the server-side.
I am curious, is it the correct way of JSON compression? If yes how can I check the difference in the request body size?
Thanks for your time.
That isn't compression at all.
var reqJSON = { ... } // too big JSON object
That will give you a JavaScript object, not JSON. Possibly your Ajax library will convert it to JSON if you pass it. There's no way for us to know. If the data is to get to the server then it will need serializing to some format that can be sent over the wire so something must be converting it before the HTTP request is made.
var compressedJSON = JSON.stringify(reqJSON, null, 0); // converting JSON to String (compression)
That will give you JSON. There's no compression involved though.
If you want to compress it then you'd need to look for a library that can do actual compression.
You can use gzip for compress json its working fine
Hello i'm trying to read this list from a file
this
this.testJson = {
list:[
{src:"x1.jpg",title:"x1",song:"x1.mp3"},
{src:"x2.jpg",title:"x2",song:"x2.mp3"},
{src:"x3.jpg",title:"x3",song:"x3.mp3"},
{src:"x4.jpg",title:"x4",song:"x4.mp3"}
]
}
to
this.testJson = {
list:[
// read x.txt or x.txt from a URL
]
}
and x.txt contain
{src:"x1.jpg",title:"x1",song:"x1.mp3"},
{src:"x2.jpg",title:"x2",song:"x2.mp3"},
{src:"x3.jpg",title:"x3",song:"x3.mp3"},
{src:"x4.jpg",title:"x4",song:"x4.mp3"}
as i do not have any java script experience , can somebody help me with this ?
thanks in advance
You have to expose that file from a web-server so your JavaScript can make an http request on that file.
To load resources from JavaScript you have to make an XMLHttpRequest or better known as AJAX request.
Actually this requires some setup so using a library would be easier. My favourite one is axios. It has a really simple API and handles response parse as well, so when you load axios on your web-site this is an approach you might follow :
axios.get('path-to-file').then(function(response){
this.testJson.list = response.data
});
Note that your x.txt does not seem like a valid JSON. It has to be a valid one so axios can parse it. If you decide to parse the file on your own you have to use JSON API.
I have an Custom Document Library Action to Alfresco files, and when I press this button opens a new page with an applet (javascript) to make changes to a file, but I'm doing the modifications in base64 and to "appear" on the screen with this :
var stringPDF = "<object data=\"data:application/pdf;base64," +
JSON.parse(pdfbase64).message + "\"
type=\"application/pdf\"width=\"100%\"
height=\"100%\"></object>";$("#pdfTexto").html(stringPDF);
But I really need is to change the file, for when the repository again, there have to change, not just display. How do I change the existing file's contents to the new with the change?
I use this URL to make GET of the file:
http://localhost:8080/share/proxy/alfresco/slingshot/node/content/workspace/SpacesStore/21384098-19dc-4d3f-bcc1-9fdc647c05dc/latexexemplo.pdf
Then I convert to the base64... And I make the changes...
But if I want to make a POST to change the content, how can I make this?
Thanks in advance.
As I mentionned in my response to this question :
The fastest and easiest way to achieve that is to leverage the RESTfull API
This will also ensure compatibility with new versions of alfresco.
Note that you need to provide the noderef for the document to update in the form property updatenoderef and that the property majorversion is a boolean flag to specify if the new version is a minor/major version of the document.
Here is a sample code that might help you with your usecase:
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost uploadFile = new HttpPost(<alfresco-service-uri>+"/api/upload?alf_ticket="+<al-ticket>);
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.addTextBody("username", "<username>", ContentType.TEXT_PLAIN);
builder.addTextBody("updatenoderef", <noderef>, ContentType.TEXT_PLAIN);
builder.addTextBody("...", "...", ContentType.TEXT_PLAIN);
builder.addBinaryBody("filedata", <InputStream>, ContentType.DEFAULT_BINARY, <filename>);
HttpEntity multipart = builder.build();
uploadFile.setEntity(multipart);
CloseableHttpResponse response = httpClient.execute(uploadFile);
String responseString = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
JSONObject responseJson = new JSONObject(responseString);
if (response.getStatusLine().getStatusCode()!=200){
throw new Exception("Couldn't upload file to the repository, webscript response :" + responseString );
}
Note 1: You need to replace these tockens <*> with your own values/vars
Note 2: If you have problem retrieving a ticket, check this link, or this one
Note 3: To do this in JavaScript instead of java, visit this link and try to use js to post the parameters I referred as instructed !
Note 4: Since you are working on share, you are most probably authenticated.
If it is the case, you can access your alfresco repo through the proxy endpoint in share and all requests will have authentication ticket attached to them before getting forwarded to your repo !
In other terms, use this endpoint :
/share/proxy/alfresco/api/upload
Instead of :
/alfresco/service/api/upload
and You won't even have to attach a ticket to your requests.
You need to follow these steps to achieve what you are looking for.
1) Reading File:
To display content of PDF file already uploaded you need to read content of file. You are able to do it successfully using following API call.
http://localhost:8080/share/proxy/alfresco/slingshot/node/content/workspace/SpacesStore/21384098-19dc-4d3f-bcc1-9fdc647c05dc/latexexemplo.pdf
2) Capture New Content:
Capture new file content from User from applet. I guess you are storing it in some String variable.
3) Edit Existing File Content:
Issue here is that you cannot simply edit any pdf file using any of out of box Alfresco REST API (as far as I know). So you need to create your own RESTFul API which could edit pdf file's content. You can consider using some third party libraries to do this job. You need to plugin logic of editing pdf in RESTFul API
4) Changes back to Repo:
Call Your API from Step 3:
You could also have look at this plugins which could fulfill your requirements.
https://addons.alfresco.com/addons/alfresco-pdf-toolkit
Hope this helps.