Akka HTTP server receives file with other fields - scala

I have created a small Akka HTTP server to receive an uploaded file.
path("upload"){
uploadedFile("csv"){
case (metadata, file) =>{
println("file received " + file.length() );
complete("hahahah")
}
}
}
I can receive the file successfully but I cannot access other fields in this POST request. The field "csv" contains the file to be uploaded, while another field, "name", contains the user-defined name. I cannot access the data in "name". Can anybody give me some clues about how to get it?

You can use fromFields('user) to get user name. But unfortunately you will get this exception: java.lang.IllegalStateException: Substream Source cannot be materialized more than once It's known issue: https://github.com/akka/akka-http/issues/90
As workaround, you can use toStrictEntity directive:
toStrictEntity(3.seconds) {
formFields('user) { (user) =>
uploadedFile("csv") {
case (metadata, file) => {
println(s"file received by $user" + file.length())
complete("hahahah")
}
}
}
}
}
I don't think it's a good idea because you will read the entire request entity into memory and it works if you have the small entity.
As a better solution, you can implement your own uploadedFile directive that will extract needed parts and fields from your multipart form data, see uploadedFile source code as example: https://github.com/akka/akka-http/blob/v10.0.10/akka-http/src/main/scala/akka/http/scaladsl/server/directives/FileUploadDirectives.scala

Related

Handling and manipulating a list of http responses

I'm currently trying to implement API logic to fetch multiple images from a server.
This server accepts an image id and return an HTTP response that contains the image in PNG format as an entity.
Right now, we want to add a new endpoint that accepts a list of images IDs and return a list of all the images:
I have done the following:
def getImagesFromIds(IdsList: List[String]): Future[List[HttpResponse]] = {
Future.sequence {
IdsList.map(
id => getImageById(id)
)
}
}
this function will receive a list of ids and will call the getImageById to fetch all the images, it will return a list of HttpResponse.
And for the route definition, I have done the following:
def getImagesByIdsListRoute: Route = get {
path("by-ids-list") {
entity(as[List[String]]){
upcs =>
complete(getImagesFromIds(upcs))
}
}
}
But I'm getting the following error message:
no implicits found for parameter m: marshalling.toresponsemarshallable[list[httpresponse]]
Does Any one know how we can marshall a list of http responses, or if there is any way to improve this logic to fetch multiple http responses ?
If I understand correctly, you want to download multiple images and return them as a HTTP response.
The problems with your current attempt
The call to the API made via getImageById returns a HttpResponse. You can't be sure what is the result of this API call. If it fails, the response won't contain any image at all.
You are trying to return List[HttpResponse] as your response. How should this response be serialized? Akka doesn't know what you mean by that and tries to find a marshaller which will serialize your object (for example to JSON) but can't find one.
Returning a list of images requires zipping them. You can't return multiple entities in a single HTTP response.
Possible approach
You have to change getImageById so that it checks what is in the HttpResponse and returns the entity bytes.
Example:
response match {
case HttpResponse(StatusCodes.OK, _, entity, _) =>
entity.dataBytes
case resp # HttpResponse(code, _, _, _) =>
// Response failed and we don't care about the response entity
// Details: https://doc.akka.io/docs/akka-http/current/implications-of-streaming-http-entity.html
resp.discardEntityBytes()
// Decide yourself how you want to handle failures
throw new RuntimeException("Request failed, response code: " + code)
}
dataBytes returns a Source so you'll end up with a List of Sources. You have to concatenate them via, for example via concat.
The result stream has to be zipped via Compression.gzip.
Finally, the stream can be put in the complete method of getImagesByIdsListRoute.

Relay Modern BadRequestError: Missing multipart field ‘operations’

I am trying to upload file to my server. using Altair i can do it without any error but when i use Relay.js for uploading it server throws following error.
BadRequestError: Missing multipart field ‘operations’ (https://github.com/jaydenseric/graphql-multipart-request-spec).
at Busboy.<anonymous> (/home/dotsinspace/Documents/dev/truck.pe__server/node_modules/.pnpm/graphql-upload#9.0.0_graphql#15.3.0/node_modules/graphql-upload/processRequest.js:362:11)
at Object.onceWrapper (events.js:420:28)
at Busboy.emit (events.js:326:22)
at Busboy.EventEmitter.emit (domain.js:486:12)
at Busboy.emit (/home/dotsinspace/Documents/dev/truck.pe__server/node_modules/.pnpm/busboy#0.3.1/node_modules/busboy/lib/main.js:37:33)
at /home/dotsinspace/Documents/dev/truck.pe__server/node_modules/.pnpm/busboy#0.3.1/node_modules/busboy/lib/types/multipart.js:52:13
at processTicksAndRejections (internal/process/task_queues.js:75:11)
Following are my graphql code and mutation which i am trying to commit.
#Graphql
graphql`
mutation AccountUploadMutation($profilePicture: Image!) {
AccountUpload(profilePicture: $profilePicture) {
message,
status
}
}
`
#Mutation
commitMutation(Environment, {
'mutation': AccountUploadMutation,
'variables': { 'profilePicture': v },
'uploadables': { 'file': v },
'onCompleted': (response, error) => Response({}, { response, error })
})
and I am totally confused about uploading part to..in uploadables you have to provide file..but my server looks for variable with profilePicture as image how can i deal with it.
It looks like you have an issue the multipart parsing configuration in your backend.
My guess is that the Relay Network is sending your GraphQL query in the mutlipart field "operation", but your backend is looking for the field "operations" (plural). To fix the error, confirm that your Network is sending the query in the operations field, or change your backend to read whatever field it's actually being sent on.
Another possibility is you're not sending your query in the multipart format at all. If you followed the Network documentation's example for sending your request, then you are just sending a JSON object, not a multipart form:
// Example from Relay docs. Sends a JSON object, not a multipart
// form as expected by your backend
function fetchQuery(
operation,
variables,
cacheConfig,
uploadables,
) {
return fetch('/graphql', {
method: 'POST',
headers: {
// Add authentication and other headers here
'content-type': 'application/json'
},
body: JSON.stringify({
query: operation.text, // GraphQL text from input
variables,
}),
}).then(response => {
return response.json();
});
}
// Create a network layer from the fetch function
const network = Network.create(fetchQuery);
If this is the case, write your fetchQuery function to fetch data using a multipart form. See this example: fetch post with multipart form data

java.util.NoSuchElementException: No attribute named is defined

I have a question please:
i would like to read multiple xml files (file1.xml, file2.xml....), for each file i read in memory then i edit and send request. for reading and editing its work fine without any problem, but when i send request:
//some declarations
.......
var payload=""
try{
myscenario= scenario ("scenario Name")
.exec{mysession : Session =>
//open files, list all files
for (file <-listofmyfile) {
//some regex that i reuse in payload
// payload contains the change that i would like to add for each xml file
payload=(// some function and text message.... "XXX")
}
mysession
}
.exec("protocol used".requestReply.message(StringBody( session =>
session(payload).as[String]))
.check(substring("XXX"))
)
}
setUp(myscenario.inject....)}
i get this 2 errors :
[ERROR] i.g.c.s.w.BufferedFileChannelWriter - Buffer overflow, you shouldn't be logging that much data. Truncating.
[ERROR] i.g.t.a.RequestReply - crashed on session Session
java.util.NoSuchElementException: No attribute named 'payload' is defined

Why is Akka-Http Routing going wrong here?

So basically, I'm writing some code that will make it possible for users to upload a file to a server. I've already succeeded in uploading a file via an HTML form (with MultiPart.FormData), but when I try 'curl -X POST -F file="filepath" localhost:8080/upload', I get a '404 not found' message.
I already read the documentation about Akka, but I just can't get to know why it works in one way and not in the other. What am I doing wrong here?
val route =
post {
path("upload") {
fileUpload("file") {
case (metadata, byteSource) =>
val sink = FileIO.toPath(Paths.get("path of the image") resolve metadata.fileName)
val writeResult = byteSource.runWith(sink)
onSuccess(writeResult) { _ =>
complete("file got uploaded")
}
}
}
} ~
complete("404 not found")
You can see in path directive source that it accepts a path prefix with the path end. So if you use path("upload") it will accept only paths ended with /upload/ but won't accept paths ended with /upload (without path end symbol /).
If you wont to use both /upload/ and /upload paths, you should use
pathPrefix("upload") ~ pathEndOrSingleSlash
Also, you can use ignoreTrailingSlash directive.

What is the correct way to process REST requests in Contoller

I have created a controller for an "applications" table. The web and REST interfaces are working but I think the add and edit functions should be better.
When I tested add and edit I found the data needed to be posted in web FORM format (not JSON).
I found I needed to use "$this->request->input('json_decode')" in the save to decode the JSON data. I thought this happened automagically.
This function now works for add (edit is similar) and displays my json/add.ctp so I can return the successful record to the user.
public function add() {
if ($this->request->is('post')) {
$this->Application->create();
//Is the request REST passing a JSON object?
if (preg_match('/\.json/', $this->request->here)){
//This is a REST call
$this->set('status', $this->Application->save($this->request->input('json_decode')));
} else {
//This is an interactive session
if ($this->Application->save($this->request->data)) {
$this->Session->setFlash(__('The application has been saved.'));
return $this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The application could not be saved. Please, try again.'));
}
}
}
}
I used the "$this->request->here" to see if it ends in ".json". Is this the "correct" way to process the REST call?
There is an entire section in the CakePHP Book for this. I think it will answer your question(s):
http://book.cakephp.org/2.0/en/development/rest.html
The question is, does your action accept JSON data & Form Data? or just JSON data?
The .json is purely for the output of your data, you are able to send JSON data with the .xml extension, the difference being once the data is sterilised, it will output in XML.
if($this->request->is('post')) {
if(empty($this->request->data)){
$data = $this->request->input('json_decode', TRUE);
} else {
$data = $this->request->data;
}
} else {
$data = $this->params['url'];
}
Above is kind of what you should be doing, check if the data comes from a form, if not, decode JSON, and if it is NOT a POST, save parameters that have been included into the URL.
I am not saying the above is the "right" way to do it, but thats probably what you are looking for.