Hello all, I'm currently trying to implement a service for uploading Images to a Basex db. There is a frontend service that serves an HTML + Javascript page which receives the upload as an AJAX call from the browser then POSTs the request to the backend service for storing into the db.
There is no big issue when doing it directly by a command or a query. I've been even able to do it by using the following code inside the RestXQ of the frontend service:
client:connect("localhost", 1984, "user", "pass") ! client:query(., "db:store('files', '" || $name || "', .)", map{ "" : $file})
But when I POST the data to the backend RestXQ I always get the data stored into the DB as *base64* encoded text which messes up the successive retrieves. This is the code I use on the backend:
declare %rest:path("/service/files/{$name}") %rest:POST("{$body}") %rest:consumes("image/png", "image/jpg", "image/jpeg", "image/gif", "image/svg+xml") %output:method("text") function m:upload-maps($userid as xs:string, $name as xs:string, $body as item()){ let $stored := db:store("files", $name, $body) return rest:response <http:response status="201"/> </rest:response> };
I call this service from the frontend service with the following request where the media-type is according to the uploaded file extension:
<http:request href="{$url}" method="POST"> <http:body media-type="image/png"> {$data} </http:body> </http:request>
How do I ensure that the data gets into the DB with its correct raw binary format? Thanks, Marco.
Hi Marco,
If you specify the binary data inside the body element, it will already be converted to a textual representation by the BaseX client. Try this instead:
http:send-request( <http:request method="POST">, <http:body media-type="image/png"/> </http:request>, $url, $data )
Cheers Christian
Hello Christian, I'm sorry but I've tried to follow your suggestion rewriting the code as
let $f := file:write-binary("/tmp/" || $name, $map) let $response := http:send-request( <http:request method="POST">, <http:body media-type="image/png"/> </http:request>, $url, $body )
The file that is written to disk is correctly encoded as PNG whereas I continue to get the data base64 encoded on the backend. If it helps I may add that I replaced the backend service with an "nc" and the data arrives there base64 encoded so my issue has to be on the sender side as you correctly suspect. But where? :( Thanks again, Marco.
On 11/08/2016 11:46, Christian Grün wrote:
Hi Marco,
If you specify the binary data inside the body element, it will already be converted to a textual representation by the BaseX client. Try this instead:
http:send-request( <http:request method="POST">, <http:body media-type="image/png"/> </http:request>, $url, $data )
Cheers Christian
Hi Christian, I'm using Basex 8.5.1 and this is a faithful excerpt of the code:
module namespace ssce = "urn:ssce";
declare %basex:inline function ssce:upload-impl($name as xs:string, $data as item()){ let $f := file:write-binary("/tmp/" || $name, $data) let $response := http:send-request( <http:request method="POST">, <http:body media-type="{web:content-type($name)}"/> </http:request>, "http://localhost:10000", $data ) return if($response[1]/@status = 201) then () else error(xs:QName("error"),"Error sending") };
declare %rest:path("/ssce/{$name}") %rest:POST("{$body}") %rest:consumes("image/png", "image/jpg", "image/jpeg", "image/gif") %output:method("text") function ssce:upload($name as xs:string, $body as item()){ ssce:upload-impl($name, $body) };
This is the curl I use to upload an image:
curl --data-binary @image.png http://localhost:9984/ssce/nubis.png -H"Content-type:image/png"
And again the file:write-binary writes the png to file but opening a nc -l I can see that the request is base64 encoded.
Thanks for any hint, Marco.
On 11/08/2016 12:26, Christian Grün wrote:
let $f := file:write-binary("/tmp/" || $name, $map)
Is it supposed to be $body? What happens if you use CURL?
It works perfectly on my system. Could you possibly provide me with a self-contained example?
Christian
Hi Marco,
I’m confused: Are you working with two BaseX instances (one running on port 9984, one running on 10000)? Is there any chance to simplify this example and simulate the issue with a single BaseX instance? If not, could you please give me full advice what needs to be done to get it working?
I simplified your first function to…
declare %basex:inline function ssce:upload-impl($name as xs:string, $data as item()){ file:write-binary(file:temp-dir() || $name, $data) };
…and the binary file was correctly stored to disk.
Cheers Christian
Hi Christian, as I told, there's a frontend BaseX that receives requests from an HTML page (on port 9984) and forwards them to another BaseX instance that acts as backend service (port 8984). Up to receiving the Image from the page on the first BaseX and writing it to disk everything is fine for me too. When sending the Image through http:send-request as http post body it gets serialized as base64 encoded. I then replaced the second BaseX with an nc -l on port 10000 in order to educe the example and to ensure that the encoding occurs while sending and not on the receiving end. nc -l logs the incoming request and I have been able to verify that the post body arrives already as base64 encoded. The code I sent is the smallest example I have been able to produce. Sorry but I'm really not able to explain it differently. Regards, Marco.
Il 14 ago 2016 10:25 PM, "Christian Grün" christian.gruen@gmail.com ha scritto:
Hi Marco,
I’m confused: Are you working with two BaseX instances (one running on port 9984, one running on 10000)? Is there any chance to simplify this example and simulate the issue with a single BaseX instance? If not, could you please give me full advice what needs to be done to get it working?
I simplified your first function to…
declare %basex:inline function ssce:upload-impl($name as xs:string, $data as item()){ file:write-binary(file:temp-dir() || $name, $data) };
…and the binary file was correctly stored to disk.
Cheers Christian
The code I sent is the smallest example I have been able to produce.
If I got you right, data arrives correctly on port 9984; is that correct? If yes, we could get rid of CURL and RESTXQ (because we know everything is ok up to this point) and e.g. reduce your XQuery expression as follows:
http:send-request( <http:request method="POST">, <http:body media-type="image/png"/> </http:request>, "http://localhost:10000", bin:hex('414243') )
I then replaced the second BaseX with an nc -l on port 10000 [...]
I now need more hints regarding the second BaseX instance: What will it do when being addressed by localhost:10000? Do you have another RESTXQ function on top level that processes the request, or is it the plain BaseX REST API?
Ok the suggested reduction seems meaningful. Anyway that's what i do on the backend service, directly copied from my original post:
declare %rest:path("/service/files/{$name}") %rest:POST("{$body}") %rest:consumes("image/png", "image/jpg", "image/jpeg", "image/gif", "image/svg+xml") %output:method("text") function m:upload-maps($userid as xs:string, $name as xs:string, $body as item()){ let $stored := db:store("files", $name, $body) return rest:response <http:response http://response/ status="201"/> </rest:response> };
Il 14 ago 2016 10:52 PM, "Christian Grün" christian.gruen@gmail.com ha scritto:
The code I sent is the smallest example I have been able to produce.
If I got you right, data arrives correctly on port 9984; is that correct? If yes, we could get rid of CURL and RESTXQ (because we know everything is ok up to this point) and e.g. reduce your XQuery expression as follows:
http:send-request( <http:request method="POST">, <http:body media-type="image/png"/> </http:request>, "http://localhost:10000", bin:hex('414243') )
I then replaced the second BaseX with an nc -l on port 10000 [...]
I now need more hints regarding the second BaseX instance: What will it do when being addressed by localhost:10000? Do you have another RESTXQ function on top level that processes the request, or is it the plain BaseX REST API?
So this is what I tried:
* I modified the recent query and added a full path (.../service/files/image.png):
* I started the BaseX HTTP Server listening on port 10000
* I modified the RESTXQ function by removing $userid (otherwise, it could not be parsed) and wrapping rest:response with db:output() and adding %updating (probably you are working with MIXUPDATES=true?)
* I created a "files" database
* I evaluated the query.
The file will be stored as expected (in its binary form) in the database. What else could I do to understand what may be going wrong? Maybe it’s something else not related to what we’ve discussed so far? Does the problem also happen with a fresh installation of BaseX (with all the default options)?
Christian
QUERY:
http:send-request( <http:request method="POST">, <http:body media-type="image/png"/> </http:request>, "http://localhost:10000/service/files/image.png", bin:hex('414243') )
RESTXQ:
module namespace m = "m"; declare %updating %rest:path("/service/files/{$name}") %rest:POST("{$body}") function m:upload-maps($name, $body) { db:store("files", $name, $body) };
Hi Christian, I unzipped a version 8.5.1 I already had on my harddisk and the behaviour is the same. File is uploaded as base64 encoded. So I downloaded a version 8.5.3 and this time it behaves as expected (with and without MIXUPDATES) as the file appears as binary data in the raw folder. I suppose there has been some kind of fix between the two versions. Could this be? I don't know whether I can quickly move to the new version but I think I should ... Regards, Marco.
On 14/08/2016 23:34, Christian Grün wrote:
So this is what I tried:
- I modified the recent query and added a full path
(.../service/files/image.png):
I started the BaseX HTTP Server listening on port 10000
I modified the RESTXQ function by removing $userid (otherwise, it
could not be parsed) and wrapping rest:response with db:output() and adding %updating (probably you are working with MIXUPDATES=true?)
I created a "files" database
I evaluated the query.
The file will be stored as expected (in its binary form) in the database. What else could I do to understand what may be going wrong? Maybe it’s something else not related to what we’ve discussed so far? Does the problem also happen with a fresh installation of BaseX (with all the default options)?
Christian
QUERY:
http:send-request( <http:request method="POST">, <http:body media-type="image/png"/> </http:request>, "http://localhost:10000/service/files/image.png", bin:hex('414243') )
RESTXQ:
module namespace m = "m"; declare %updating %rest:path("/service/files/{$name}") %rest:POST("{$body}") function m:upload-maps($name, $body) { db:store("files", $name, $body) };
So I downloaded a version 8.5.3 and this time it behaves as expected
(with and without MIXUPDATES) as the file appears as binary data in the raw folder.
I suppose there has been some kind of fix between the two versions. Could
this be?
Wow. I guess so ;) I remember we fixed an issue regarding multipart messages, maybe that was related.
Thanks for reporting the positive outcome back to us!
Christian
basex-talk@mailman.uni-konstanz.de