Search code examples
rubyhttpputrest-clientnet-http

Uploading data via HTTP PUT without multi-part gutter


I'm trying to upload static HTML+friends content to a raw Nexus 3 repository. As far as the documentation goes, the only way to upload files is using HTTP PUT. Sure enough,

curl -v --user 'user:pw' --upload-file my_file.html  \
    http://my.nexus/repository/my-raw-repo/my_file.html

works as expected and the file can be accessed at http://my.nexus/repository/my-raw-repo/index.html

Adding this deployment to my Rake process has turned out to be hasslesome, though. Here are some things I have tried.

  1. With net/http:

    require 'net/http'
    uri = URI("http://my.nexus/repository/my-raw-repo")
    file = "index.html"
    Net::HTTP.start(uri.host, uri.port) do |http|
        request = Net::HTTP::Put.new "#{repo_url}/#{file}"
        request.basic_auth(user, pw)
    
        <snip>
    
        response = http.request request
        raise "Upload failed: #{response.code} #{response.message}\n#{response.body}" \
           unless response.is_a?(Net::HTTPSuccess)
    end
    

    I tried several variants for <snip>.

    1. Set body to file content as string:

      request.body = File.read("#{file}")`
      

      Request completes without error but Nexus shows file size 0 bytes.

    2. Send form data to file content as stream:

      request.set_form([['upload', File.open("#{file}")]], 'multipart/form-data')
      

      That one is kind of obvious: multi-part gutter is added which Nexus does not remove:

      enter image description here

  2. With rest-client:

    require 'rest-client'
    file = "index.html"
    begin
        RestClient::Request.execute(
            method: :put,
            url: "http://my.nexus/repository/my-raw-repo/#{file}",
            <snip>
            user: user,
            password: pw
        )
    rescue RestClient::ExceptionWithResponse => e
        raise "Upload failed: #{e.response}"
    end
    

    And for <snip> (guessing around the less than clear documentation):

    1. body: File.read("#{file}")
      --> 0 bytes.
    2. payload: File.read("#{file}")
      --> 0 bytes.
    3. payload: { file: File.new("#{file}") }
      --> Multi-part gutter remains.
    4. payload: { multipart: false, file: File.new("#{file}") }
      --> Multi-part gutter remains.

    I think I tried several combination more which I don't remember, with similar results.

Note bene: I've left out the parts related to SSL with a self-signed certificate because that should be unrelated.

How can I tell either library (or any other) to do PUT with data but without multi-part? Or if that's not the issue, what am I doing wrong?

I want to use an all-Ruby solution (i.e. not delegate to system curl) so that the build can be (as) portable (as possible).


Solution

  • With net/http, set the request body to the file content as stream:

    request.body_stream = File.open("#{file}")
    request.content_length = File.size("#{file}")
    request.content_type = "text/plain" # ?!
    

    Determining the proper MIME type from the file alone is an adventure in itself.