samxiao
samxiao

Reputation: 2667

POST error with Sinatra with JSON

I'm learning Ruby + Sinatra, and found a good post here which talks about how to upload files.

post '/upload/:filename' do
  userdir = "./upload"
  FileUtils.mkdir_p(userdir) unless File.exists?(userdir)
  filename = File.join(userdir, params[:filename])
  datafile = params[:data]
  File.open(filename, 'wb') do |file|
    file.write(datafile[:tempfile].read)
  end
end

I can use the following cURL command to upload files fine.

curl -v -F "data=@/Users/me/Desktop/test.pdf"  http://localhost:4567/upload/test.pdf

But now I had decided to use JSON to handle all incoming/outgoing responses. I tried this, but it didn't seem to work.

curl -i -X POST -H Accept:application/json -H Content-Type:application/json -d '{file:{filename:"test.pdf",md5sum:"ab3d2f"}}'  --data-binary @/Users/me/Desktop/test.pdf 'http://localhost:4567/upload/test.pdf'

I also received an error like this:

NoMethodError at /upload/test.pdf
undefined method `get' for #<WebTest:0x101374dd8>
file: web.rb location: POST /upload/:filename line: 48

What should I do now?

Edited:

Line 48 is this file.write(datafile[:tempfile].read

Please help!

Upvotes: 1

Views: 1545

Answers (3)

samxiao
samxiao

Reputation: 2667

Now I can get it working with the below curl command with JSON:

curl -v -F 'json=[ {"filename": "@/Users/me/Desktop/test.pdf", "md5sum": "1496f9b6f42b7ed8260eadeb158c33f4", type": "generic"}, {"filename": "@/Users/me/Desktop/test2.pdf", "md5sum": "1496f9b6f42b7ed8260eadeb158c33f4", type": "generic"} ]' http://localhost:4567/upload

However, since I don't use -F 'file=@/Users/me/Desktop/test.pdf' to POST my file, how do I retrieve the file on the server-side using JSON objects?

The below is no longer working.

tempfile = params[:file][:tempfile]
filename = params[:file][:filename]
dest = "#{userdir}/#{filename}"
FileUtils.cp(tempfile.path, dest) if not File.exists?(dest)

Upvotes: 0

Miikka
Miikka

Reputation: 4653

I'm not sure what you're expecting your second curl command to do, but I'm pretty sure it's not doing what you want. If you give more than one --data or -d parameters to curl, their values will be joined together, separated by &.

For example, if you have a file test.txt with contents This is a test. and you do request like this:

curl -i -X POST -H Accept:application/json -H Content-Type:application/json \
    -d '{file:{filename:"test.txt",md5sum:"ab3d2f"}}'                  \
    --data-binary @test.txt 'http://localhost:4567/upload/test.txt'

This is what the request looks like:

POST /upload/test.txt HTTP/1.1
User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8r zlib/1.2.5
Host: localhost:4567
Accept:application/json
Content-Type:application/json
Content-Length: 61

{file:{filename:"test.txt",md5sum:"ab3d2f"}}&This is a test.

The request body is not valid JSON. It's not valid URL-encoded form data or multi-part form data either.

It's not obvious how to combine JSON requests with file uploads. Here are some options:

  • Just use multipart/form-data. That is, do what you're doing right now with your Ruby code and -F option to curl.
  • Use multipart/form-data with one part of JSON for metadata and other part with the raw uploaded file.
  • Embed the file into JSON by base64-encoding it.
  • Do some kind of two-request workflow where first you post the metadata as JSON and then you upload the file in the POST body.

I'd probably go with the first option, because it's so widely supported.

Upvotes: 2

berkes
berkes

Reputation: 27553

Seems like your Ruby code is broken; you forgot an end:

post '/upload/:filename' do
  userdir = "./upload"
  FileUtils.mkdir_p(userdir) unless File.exists?(userdir)
  filename = File.join(userdir, params[:filename])
  datafile = params[:data]
  File.open(filename, 'wb') do |file|
    file.write(datafile[:tempfile].read)
  end
end

Upvotes: 1

Related Questions