Sending an image file via a HTTP get or post from SketchUp

So this particular programming conundrum I find myself in is quite a bit above my Ruby skills or any programming skills in general I guess.

Using an HTML (Post) form I am able to upload an image to my server side CGI script and from there I am able to take the data and then save it on my server. The bones of HTML required to do this, with all of the formatting fluff stripped away is essentially:


<form enctype="multipart/form-data" action="logoupload.pl" method="post">
	<input name="imagefiletoupload" type="file">
	<input id="newlogo" name="imglogo" value="Update Logo">
</form>

I really donā€™t understand how this works, other than the HTML form is able to select the file from the client end and then transfer it to my CGI/Perl file (logoupload.pl). On the server end I have some code in place that checks for errors, size etcā€¦ and then saves the file to the directory of my choosing.

This is all good, I can make this work.

Now what I want to try and do is within Ruby (given a path to an existing image file) send that image file to my same server side CGI script using some type of GET or POST call.

I have no idea how to do this.

My typical way I make calls to my server (or any other website address) is something like this:

some_url = "http://design.medeek.com/resources/somescript.pl?var1=#{@Var1}&var2=#{Var2}&var3=#{@Var3}"
	
require 'open-uri'

begin
		open(some_url) { |io|
            		url_response = io.read
			
			# Then parse the response with some code here or convert JSON to ruby array etc...
			
          	}

rescue StandardError => e
         	UI.messagebox ("Unable to connect to server, action aborted.")
end

How would I send an image via a URL call like this? Or is there a better way to do this?

After some digging about on the internet I think what I am looking for may be found here:

Well Iā€™ve tried my best to get it to work and it is still not working, this one is tough. Here is my code for now:


# Send HTTP request to Medeek Server with Image Upload

	require "net/http"
	require "uri"

	# Token used to terminate the file in the post body. Make sure it is not present in the file you're uploading.
	boundary1 = "AaB03x"

	uri = URI.parse("http://design.medeek.com/imageupload.pl")

	
	post_body = []

	post_body << "--#{boundary1}\r\n"
	post_body << "Content-Disposition: form-data; name=\"action2\"; filename=\"#{File.basename(imagefilename)}\"\r\n"
	post_body << "Content-Type: #{mime_type}\r\n\r\n"
	post_body << File.read(imagefilename)
	post_body << "\r\n--#{boundary1}\r\n"

	post_body << "Content-Disposition: form-data; name=\"shn\"\r\n\r\n"
	post_body << "AE23SE32IM19CX53"
	post_body << "\r\n\r\n--#{boundary1}\r\n"

	post_body << "Content-Disposition: form-data; name=\"timestamp\"\r\n\r\n"
	post_body << new_timestamp
	post_body << "\r\n\r\n--#{boundary1}\r\n"

	post_body << "Content-Disposition: form-data; name=\"action\"\r\n\r\n"
	post_body << "submitlogo"
	post_body << "\r\n\r\n--#{boundary1}--\r\n"

	http = Net::HTTP.new(uri.host, uri.port)
	request = Net::HTTP::Post.new(uri.request_uri)

	request.body = post_body.join
	request["Content-Type"] = "multipart/form-data, boundary=#{boundary1}"

	medeek_response = http.request(request)

	puts "#{medeek_response.code}"
	puts "#{medeek_response.body}"

I really have no idea what all those carriage returns and new lines are doing in the code, Iā€™m just trying to follow the documentation but I think there is something messed up in the way I have these special characters inserted and that is what is making this not work.

Maybe someone can spot what I have wrong with the building of the post_body.

If I use the above HTML form with a couple of parameters/inputs added, it works flawlessly and the image file is uploaded to the server and processed by my CGI and saved in the appropriate folder. Ruby on the other hand is somehow munging it up, but Iā€™m not sure how yet.

For those who need to or would like to send files via SketchUp here is what Iā€™ve got and it seems to finally work:

# Send HTTP request to Medeek Server with Image Upload

require "net/http"
require "uri"

uri = URI.parse("http://design.medeek.com/imageupload.pl")
req = Net::HTTP::Post.new(uri)
req.set_form([['shn', '19AD55XC21WO78PQ21'], ['timestamp', new_timestamp], ['action', 'copylogo'], ['action2', File.open(imagefilename), {filename: "#{File.basename(imagefilename)}"}]], 'multipart/form-data')
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
    medeek_response = http.request(req)

    if medeek_response.body =~ /Success/i
        # Do nothing upload successful
    else
        UI.messagebox ("Upload of logo to the Medeek Server failed, please try again.")
        puts "#{medeek_response.code}"
		
    end
    puts "#{medeek_response.body}"
end
2 Likes

Well I guess this one wasnā€™t above my programming pay grade but it did take about 8 hours of mucking about and some serious hair pulling until I finally found a solution that worked.

Make sure to close the file object in the block.

1 Like

How do I go about that?

f = File.open(imagefilename)
ā€¦ use it ā€¦
f.close

Itā€™s safest to wrap file IO in a begin ā€¦ rescue ā€¦ with the f.close within the ensure clause.

Or ā€¦ use another method, perhaps IO.read(imagefilename) which closes the file when it returns.

1 Like

Re the ā€˜fileā€™, you might consider, instead of

File.open(imagefilename)

use the following, which opens the file read-only and binary, which would be normal for an image file.

File.open(imagefilename, 'rb')
Revised code
require "net/http"
require "uri"

imagefilename = '/mnt/c/r_su/medeek.rb'
new_timestamp = 'TIMESTAMP'

file = File.open imagefilename, 'rb'

fields = [
  ['shn', '19AD55XC21WO78PQ21'],
  ['timestamp', new_timestamp],
  ['action', 'copylogo'],
  ['action2', file, {filename: "#{File.basename(imagefilename)}"}]
]

uri = URI.parse("http://[::1]:40001")
req = Net::HTTP::Post.new(uri)
req.set_form(fields, 'multipart/form-data')
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
  medeek_response = http.request(req)

  if medeek_response.body =~ /Success/i
      # Do nothing upload successful
  else
      STDOUT.puts "Upload of logo to the Medeek Server failed, please try again."
      STDOUT.puts "#{medeek_response.code}"
  end
  puts "#{medeek_response.body}"
end
file.close unless file.closed?

As to all the strange ā€˜post_bodyā€™ code from the example, thatā€™s how ā€˜multipart/form-dataā€™ encodes the data ā€˜pairsā€™ in the body of the POST request. Using net/http is much easierā€¦

As to closing the file handle, thatā€™s very important, especially on Windows. Windows is much different that macOS (and Ubuntu, etc), as it does not allow file operations if the file has an open handleā€¦

1 Like

Would it be okay to put the open and close for the file before this entire section of code?

Or would addin the ā€˜rbā€™ switch be sufficient.

I donā€™t have a close method called but it seems to work fine on Windows.

On a related note there is no method ā€œcloseā€ that I can see for files in Ruby:

https://docs.ruby-lang.org/en/2.7.0/File.html

Doesnā€™t the file handle close automatically?

The File class is a subclass of the IO class.

NO ā€¦ unless you use a method that specifically says it closes the file, ā€¦

ā€¦ or you use the block form of ::open.

Example of block form File::open ā€¦

def upload_image()
  require "net/http"
  require "uri"

  imagefilename = '/mnt/c/r_su/medeek.rb'
  new_timestamp = 'TIMESTAMP'

  File.open(imagefilename, 'rb') { |file|

    fields = [
      ['shn', '19AD55XC21WO78PQ21'],
      ['timestamp', new_timestamp],
      ['action', 'copylogo'],
      ['action2', file, {filename: "#{File.basename(imagefilename)}"}]
    ]

    uri = URI.parse("http://[::1]:40001")
    req = Net::HTTP::Post.new(uri)
    req.set_form(fields, 'multipart/form-data')
    res = Net::HTTP.start(uri.hostname, uri.port) do |http|
      medeek_response = http.request(req)

      if medeek_response.body =~ /Success/i
        # Do nothing upload successful
      else
        puts "Upload of logo to the Medeek Server failed, please try again."
        puts "#{medeek_response.code}"
      end
      puts "#{medeek_response.body}"
    end

  }
  # The file should be closed.
end
1 Like

Okay, I get what your doing, your wrapping the whole thing with the file open method, which automatically closes out the file, thank-you for the clarification.

1 Like

One last question:

Why do you proceed the puts with STDOUT in some cases and not others? Iā€™ve actually never seen that construct before.

Greg would need to answer this. I just use the global puts myself.

1 Like

First of all, sorry for not noticing the STDOUT. I ran the code in stand-alone Ruby, as I was also running a server that allowed me to look at the body the Net::HTTP request was sending.

Using STDOUT is kind of an autopilot thing with me, as sometimes I use STDOUT.syswrite, which canā€™t be used without an IO instance with it.

So, donā€™t use STDOUT with SU, just use puts. Also, what Dan suggested (using File.open with a block) would work fine.

I didnā€™t add it to the code I listed, but if you use File.open without a block, I would put the
file.close unless file.closed? statement in a begin/ensure block to make sure it runs even if an error is raised.

Would it be okay to put the open and close for the file before this entire section of code?

The close call (or the end of the File.open block) must be after all the Net::HTTP code finishes. The Net::HTTP code pulls from the opened object (file) as it streams the data to the web server. Your file may be small, but this can be used with large files (~100Mb or larger)ā€¦

See Class: File ā€” Core Ruby-2.7.8 pp225. If you see the ā€˜Table of Contentsā€™ pane (you may need to be full screen with the browser window), type ā€˜closeā€™ in its search box, and youā€™ll see IO.close listed, it should also be in the main doc window if nothing is ā€˜folded/collapsedā€™. ā€˜RDocā€™ docs, which is what youā€™re viewing at docs.ruby-lang.org, doesnā€™t show ā€˜ancestorā€™ methods very well. Most ā€˜YARDā€™ derivatives do show themā€¦

Doesnā€™t the file handle close automatically?

Only when using a block.

2 Likes

I suppose I mostly solved this one on my own but I really appreciate you guys schooling me on my Ruby code, Iā€™m still very much a newbie when it comes to actually coding. Thank-you.