about summary refs log blame commit diff stats
path: root/src/io/http.nim
blob: 1ebcaf72bbf7bb50443aea395bc13a4f6172d8a2 (plain) (tree)
1
2
3
4
5
6
7
8
9
10




                    
                 
                 
                    

                     
                
                


                   


                                 
                    
                    
                    





                                                                               
                          











                                         














                                                                                                        
                                     

                        



                                      
                     
                                                        
                                        
                 



                         
                                                          
                                 
                 

                                        
                      


                                                                                                     
                                             
               



                                                 

              
                                                                          
                            





                                          
                                       






                                                   





                                                                              
             
                                                               

                                                                 





                                                                             
                             



                      

                                    

                                                       
                                                   
                                             
                                                      


                                        
                         

                                   

                                
                                           
                     

                              




                                                                           

                            


                      
import options
import streams
import strutils

import bindings/curl
import io/headers
import io/request
import ips/serialize
import types/blob
import types/formdata
import types/url
import utils/opt
import utils/twtstr

type
  HandleData* = ref HandleDataObj
  HandleDataObj = object
    curl*: CURL
    statusline: bool
    headers: Headers
    request: Request
    ostream*: Stream
    mime: curl_mime
    slist: curl_slist

func newHandleData(curl: CURL, request: Request, ostream: Stream): HandleData =
  let handleData = HandleData(
    headers: newHeaders(),
    curl: curl,
    ostream: ostream,
    request: request
  )
  return handleData

proc cleanup*(handleData: HandleData) =
  if handleData.mime != nil:
    curl_mime_free(handleData.mime)
  if handleData.slist != nil:
    curl_slist_free_all(handleData.slist)
  curl_easy_cleanup(handleData.curl)

template setopt(curl: CURL, opt: CURLoption, arg: typed) =
  discard curl_easy_setopt(curl, opt, arg)

template setopt(curl: CURL, opt: CURLoption, arg: string) =
  discard curl_easy_setopt(curl, opt, cstring(arg))

template getinfo(curl: CURL, info: CURLINFO, arg: typed) =
  discard curl_easy_getinfo(curl, info, arg)

proc curlWriteHeader(p: cstring, size: csize_t, nitems: csize_t, userdata: pointer): csize_t {.cdecl.} =
  var line = newString(nitems)
  for i in 0..<nitems:
    line[i] = p[i]

  let op = cast[HandleData](userdata)
  if not op.statusline:
    op.statusline = true
    try:
      op.ostream.swrite(int(CURLE_OK))
    except IOError: # Broken pipe
      return 0
    var status: clong
    op.curl.getinfo(CURLINFO_RESPONSE_CODE, addr status)
    op.ostream.swrite(cast[int](status))
    return nitems

  let k = line.until(':')

  if k.len == line.len:
    # empty line (last, before body) or invalid (=> error)
    op.ostream.swrite(op.headers)
    return nitems

  let v = line.substr(k.len + 1).strip()
  op.headers.add(k, v)
  return nitems

proc curlWriteBody(p: cstring, size: csize_t, nmemb: csize_t, userdata: pointer): csize_t {.cdecl.} =
  let handleData = cast[HandleData](userdata)
  if nmemb > 0:
    try:
      handleData.ostream.writeData(p, int(nmemb))
    except IOError: # Broken pipe
      return 0
  return nmemb

proc applyPostBody(curl: CURL, request: Request, handleData: HandleData) =
  if request.multipart.isOk:
    handleData.mime = curl_mime_init(curl)
    if handleData.mime == nil:
      # fail (TODO: raise?)
      handleData.ostream.swrite(-1)
      handleData.ostream.flush()
      return
    for entry in request.multipart.get:
      let part = curl_mime_addpart(handleData.mime)
      if part == nil:
        # fail (TODO: raise?)
        handleData.ostream.swrite(-1)
        handleData.ostream.flush()
        return
      curl_mime_name(part, cstring(entry.name))
      if entry.isstr:
        curl_mime_data(part, cstring(entry.svalue), csize_t(entry.svalue.len))
      else:
        let blob = entry.value
        if blob.isfile: #TODO ?
          curl_mime_filedata(part, cstring(WebFile(blob).path))
        else:
          curl_mime_data(part, blob.buffer, csize_t(blob.size))
        # may be overridden by curl_mime_filedata, so set it here
        curl_mime_filename(part, cstring(entry.filename))
    curl.setopt(CURLOPT_MIMEPOST, handleData.mime)
  elif request.body.issome:
    curl.setopt(CURLOPT_POSTFIELDS, cstring(request.body.get))
    curl.setopt(CURLOPT_POSTFIELDSIZE, request.body.get.len)

proc loadHttp*(curlm: CURLM, request: Request, ostream: Stream): HandleData =
  let curl = curl_easy_init()
  if curl == nil:
    ostream.swrite(-1)
    ostream.flush()
    return # fail
  let surl = request.url.serialize()
  curl.setopt(CURLOPT_URL, surl)
  let handleData = curl.newHandleData(request, ostream)
  curl.setopt(CURLOPT_WRITEDATA, handleData)
  curl.setopt(CURLOPT_WRITEFUNCTION, curlWriteBody)
  curl.setopt(CURLOPT_HEADERDATA, handleData)
  curl.setopt(CURLOPT_HEADERFUNCTION, curlWriteHeader)
  if request.proxy != nil:
    let purl = request.proxy.serialize()
    curl.setopt(CURLOPT_PROXY, purl)
  case request.httpmethod
  of HTTP_GET:
    curl.setopt(CURLOPT_HTTPGET, 1)
  of HTTP_POST:
    curl.setopt(CURLOPT_POST, 1)
    curl.applyPostBody(request, handleData)
  else: discard #TODO
  for k, v in request.headers:
    let header = k & ": " & v
    handleData.slist = curl_slist_append(handleData.slist, cstring(header))
  if handleData.slist != nil:
    curl.setopt(CURLOPT_HTTPHEADER, handleData.slist)
  let res = curl_multi_add_handle(curlm, curl)
  if res != CURLM_OK:
    ostream.swrite(int(res))
    ostream.flush()
    #TODO: raise here?
    return
  return handleData