Hello, there.

I'm building a clojure API to my website that is basically a wrapper around the original web API. One of the features that I'm not being able to implement is file sending via POST requests, basically what I would do in shell with curl -F foo=bar [email protected] foobar.com.

I'm using clojure-http-client, and initially tried the form (resourcefully/post "foobar.com" {} {:foo "bar" :baz (File. "bak.jpg")}), but the :baz field was ignored by the receiving script, as if I had send only :foo. Later, I've tried changing File. to FileInputStream, as [the line 51][2] of client.clj seems to be checking for this particular class, but still got the same results.

Then I created a php page that simply prints $_POST to check out my request, and apparently the objects' data is being sent literally. Take a look:

Clojure=> (resourcefully/post "http://ptchan.org/pttest.php" {} {:foo "bar" :baz "/tmp/bak.jpg"}) {:body-seq ("Array" "(" " [foo] => bar" " [baz] => /tmp/bak.jpg" ")"), :code 200, :msg "OK", :method "POST", :headers {:date ("Fri, 02 Oct 2009 11:41:15 GMT"), :vary ("Accept-Encoding"), :content-length ("53"), :connection ("close"), :content-type ("text/html"), :server ("Apache/2.2.9 (Debian) PHP/5.2.6-1+lenny3 with Suhosin-Patch"), :x-powered-by ("PHP/5.2.6-1+lenny3")}, :get-header #, :cookies nil, :url "http://ptchan.org/pttest.php"}

Clojure=> (resourcefully/post "http://ptchan.org/pttest.php" {} {:foo "bar" :baz (File. "/tmp/bak.jpg")}) {:body-seq ("Array" "(" " [foo] => bar" " [baz] => /tmp/bak.jpg" ")"), :code 200, :msg "OK", :method "POST", :headers {:date ("Fri, 02 Oct 2009 11:41:30 GMT"), :vary ("Accept-Encoding"), :content-length ("53"), :connection ("close"), :content-type ("text/html"), :server ("Apache/2.2.9 (Debian) PHP/5.2.6-1+lenny3 with Suhosin-Patch"), :x-powered-by ("PHP/5.2.6-1+lenny3")}, :get-header #, :cookies nil, :url "http://ptchan.org/pttest.php"}

Clojure=> (resourcefully/post "http://ptchan.org/pttest.php" {} {:foo "bar" :baz (FileInputStream. "/tmp/bak.jpg")}) {:body-seq ("Array" "(" " [foo] => bar" " [baz] => java.io.FileInputStream@320f6398" ")"), :code 200, :msg "OK", :method "POST", :headers {:date ("Fri, 02 Oct 2009 11:41:47 GMT"), :vary ("Accept-Encoding"), :content-length ("73"), :connection ("close"), :content-type ("text/html"), :server ("Apache/2.2.9 (Debian) PHP/5.2.6-1+lenny3 with Suhosin-Patch"), :x-powered-by ("PHP/5.2.6-1+lenny3")}, :get-header #, :cookies nil, :url "http://ptchan.org/pttest.php"}

I'm not really sure how to proceed. Any suggestion? General tips on debugging are also welcome!


+3  A: 

I'm not sure it's possible using clojure-http-client. As far as I see in the source code, if you pass a map as the body argument, it will URL encode every element and send it. It appears that you can only POST files as entire bodies, without any other arguments. So nu support for multipart.

(let [out (.getOutputStream connection)]
  (string? body) (spit out body)
  (map? body) (spit out (url-encode body))
  (instance? InputStream body)
  (let [bytes (make-array Byte/TYPE 1000)]
    (loop [#^InputStream stream body
           bytes-read (.read stream bytes)]
      (when (pos? bytes-read)
        (.write out bytes 0 bytes-read)
        (recur stream (.read stream bytes))))))
(.close out)))
Ionuț G. Stan
+4  A: 

Try using clojure-apache-http, a Clojure wrapper for the full-featured Apache HTTP libraries. It does support multipart/form-data POST.