Search code examples
pythonrestcurllibcurlpycurl

POSTing a file and passing a parameter using pycurl


I have figured out how to do GET, PUTs, DELETEs and basic POST using pycurl.

Nevertheless, I can't figure out what is the equivalent of this curl command line (which works perfectly fine) to python:

curl -u admin:geoserver -XPOST -H 'Content-type: application/vnd.ogc.sld+xml' 
-d @/Users/rburhum/src/calthorpe/calthorpe/server/calthorpe/media/styles/1_my-scenario
"http://127.0.0.1:8080/geoserver/rest/styles?name=1_my-scenario" -v

I have seen the samples at the repo. Nevertheless, passing one parameter (in this case name) and a file to be uploaded doesn't seem to work.

For PUT calls, I have successfully used:

filesize = path.getsize(sldFile)
f = open(sldFile,'rb')

c = pycurl.Curl()

c.setopt(pycurl.HTTPHEADER, ["Content-type: application/vnd.ogc.sld+xml"])
c.setopt(pycurl.USERPWD, GEOSERVER_USER + ':' + GEOSERVER_PASSWORD)    
c.setopt(pycurl.INFILESIZE, filesize)
c.setopt(c.URL, str(GEOSERVER_URL + '/rest/styles/' + path.basename(sldFile)))
c.setopt(pycurl.PUT, 1)
c.setopt(pycurl.INFILE, f)
c.perform()
f.close()

thus, I naively thought that the POST equivalent would be:

filesize = path.getsize(sldFile)
f = open(sldFile,'rb')

c = pycurl.Curl()

c.setopt(c.URL, str(GEOSERVER_URL + '/rest/styles?name=' + path.basename(sldFile)))
c.setopt(pycurl.POST, 1)
c.setopt(pycurl.HTTPHEADER, ["Content-type: application/vnd.ogc.sld+xml"])
c.setopt(pycurl.USERPWD, GEOSERVER_USER + ':' + GEOSERVER_PASSWORD)    
c.setopt(pycurl.INFILESIZE, filesize)
c.setopt(pycurl.INFILE, f)
c.perform()
f.close()

The verbose output of the curl command displays this:

(calthorpe_env)rburhum@peru.local ~/src/calthorpe/calthorpe/server/calthorpe $calthorpe/server/calthorpe POST -H 'Content-type: application/vnd.ogc.sld+xml' -d@/Users/rburhum/src/calthorpe/calthorpe/server/calthorpe/media/styles/1_my-scenario "http://127.0.0.1:8080/geoserver/rest/styles?name=1_my-scenario" -v * About to connect() to 127.0.0.1 port 8080 (#0) * Trying 127.0.0.1... connected * Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0) * Server auth using Basic with user 'admin'

POST /geoserver/rest/styles?name=1_my-scenario HTTP/1.1 Authorization: Basic YWRtaW46Z2Vvc2VydmVy User-Agent: curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5 Host: 127.0.0.1:8080 Accept: / Content-type: application/vnd.ogc.sld+xml Content-Length: 28135 Expect: 100-continue

  > < HTTP/1.1 100 Continue < HTTP/1.1 201 Created < Date: Mon, 05 Mar 2012 07:39:43 GMT < Location: http://127.0.0.1:8080/geoserver/rest/styles/1_my-scenario < Server: Noelios-Restlet-Engine/1.0..8 < Transfer-Encoding: chunked < * Connection #0 to host 127.0.0.1 left intact * Closing connection #0

I can clearly see that the content length is correct.

In contrast, when I do a verbose output of my POST code above, I can see that the content length is -1 (and hence the file is not being passed and the server gives back a 500). I have mocked with the HTTPPOST variable, which under certain combinations makes the content length be correct, but I still cannot get the exact equivalent of the CURL command above to work.


Solution

  • I had to combine several examples to find the right combination, and of course, I used some help from some nice people at the IRC channel. The only way I could manage to get it to work is to do a slight variation of this pycurl sample which wasn't working as described for me.

    class FileReader:
        def __init__(self, fp):
            self.fp = fp
        def read_callback(self, size):
            return self.fp.read(size)
    

    then later, I would setup the call like this:

    c = pycurl.Curl()
    c.setopt(c.URL, str(GEOSERVER_URL + '/rest/styles?name=' + path.basename(sldFile)))
    c.setopt(pycurl.POST, 1)
    filesize = path.getsize(sldFile)
    f = open(sldFile,'rb')
    c.setopt(pycurl.POSTFIELDSIZE, filesize)
    c.setopt(pycurl.READFUNCTION, FileReader(f).read_callback)
    c.setopt(pycurl.HTTPHEADER, ["Content-type: application/vnd.ogc.sld+xml"])
    c.setopt(pycurl.USERPWD, GEOSERVER_USER + ':' + GEOSERVER_PASSWORD)
    
    c.perform()
    f.close()
    

    Notice that the original sample did not use a POSTFIELDSIZE (only had INFILESIZE), without it, I could not get it to work.

    As a side note, in my case, sldFile has already been slugify'ed, so no need to urlencode, otherwise, you may want to do that.