receiver.lua 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. -- Copyright 2009 Steven Barth <steven@midlink.org>
  2. -- Licensed to the public under the Apache License 2.0.
  3. require "nixio.util"
  4. local nixio = require "nixio"
  5. local httpc = require "luci.httpclient"
  6. local ltn12 = require "luci.ltn12"
  7. local print, tonumber, require, unpack = print, tonumber, require, unpack
  8. module "luci.httpclient.receiver"
  9. local function prepare_fd(target)
  10. -- Open fd for appending
  11. local oflags = nixio.open_flags("wronly", "creat")
  12. local file, code, msg = nixio.open(target, oflags)
  13. if not file then
  14. return file, code, msg
  15. end
  16. -- Acquire lock
  17. local stat, code, msg = file:lock("tlock")
  18. if not stat then
  19. return stat, code, msg
  20. end
  21. file:seek(0, "end")
  22. return file
  23. end
  24. local function splice_async(sock, pipeout, pipein, file, cb)
  25. local ssize = 65536
  26. local smode = nixio.splice_flags("move", "more", "nonblock")
  27. -- Set pipe non-blocking otherwise we might end in a deadlock
  28. local stat, code, msg = pipein:setblocking(false)
  29. if stat then
  30. stat, code, msg = pipeout:setblocking(false)
  31. end
  32. if not stat then
  33. return stat, code, msg
  34. end
  35. local pollsock = {
  36. {fd=sock, events=nixio.poll_flags("in")}
  37. }
  38. local pollfile = {
  39. {fd=file, events=nixio.poll_flags("out")}
  40. }
  41. local done
  42. local active -- Older splice implementations sometimes don't detect EOS
  43. repeat
  44. active = false
  45. -- Socket -> Pipe
  46. repeat
  47. nixio.poll(pollsock, 15000)
  48. stat, code, msg = nixio.splice(sock, pipeout, ssize, smode)
  49. if stat == nil then
  50. return stat, code, msg
  51. elseif stat == 0 then
  52. done = true
  53. break
  54. elseif stat then
  55. active = true
  56. end
  57. until stat == false
  58. -- Pipe -> File
  59. repeat
  60. nixio.poll(pollfile, 15000)
  61. stat, code, msg = nixio.splice(pipein, file, ssize, smode)
  62. if stat == nil then
  63. return stat, code, msg
  64. elseif stat then
  65. active = true
  66. end
  67. until stat == false
  68. if cb then
  69. cb(file)
  70. end
  71. if not active then
  72. -- We did not splice any data, maybe EOS, fallback to default
  73. return false
  74. end
  75. until done
  76. pipein:close()
  77. pipeout:close()
  78. sock:close()
  79. file:close()
  80. return true
  81. end
  82. local function splice_sync(sock, pipeout, pipein, file, cb)
  83. local os = require "os"
  84. local ssize = 65536
  85. local smode = nixio.splice_flags("move", "more")
  86. local stat
  87. -- This is probably the only forking http-client ;-)
  88. local pid, code, msg = nixio.fork()
  89. if not pid then
  90. return pid, code, msg
  91. elseif pid == 0 then
  92. pipein:close()
  93. file:close()
  94. repeat
  95. stat, code = nixio.splice(sock, pipeout, ssize, smode)
  96. until not stat or stat == 0
  97. pipeout:close()
  98. sock:close()
  99. os.exit(stat or code)
  100. else
  101. pipeout:close()
  102. sock:close()
  103. repeat
  104. stat, code, msg = nixio.splice(pipein, file, ssize, smode)
  105. if cb then
  106. cb(file)
  107. end
  108. until not stat or stat == 0
  109. pipein:close()
  110. file:close()
  111. if not stat then
  112. nixio.kill(pid, 15)
  113. nixio.wait(pid)
  114. return stat, code, msg
  115. else
  116. pid, msg, code = nixio.wait(pid)
  117. if msg == "exited" then
  118. if code == 0 then
  119. return true
  120. else
  121. return nil, code, nixio.strerror(code)
  122. end
  123. else
  124. return nil, -0x11, "broken pump"
  125. end
  126. end
  127. end
  128. end
  129. function request_to_file(uri, target, options, cbs)
  130. options = options or {}
  131. cbs = cbs or {}
  132. options.headers = options.headers or {}
  133. local hdr = options.headers
  134. local file, code, msg
  135. if target then
  136. file, code, msg = prepare_fd(target)
  137. if not file then
  138. return file, code, msg
  139. end
  140. local off = file:tell()
  141. -- Set content range
  142. if off > 0 then
  143. hdr.Range = hdr.Range or ("bytes=" .. off .. "-")
  144. end
  145. end
  146. local code, resp, buffer, sock = httpc.request_raw(uri, options)
  147. if not code then
  148. -- No success
  149. if file then
  150. file:close()
  151. end
  152. return code, resp, buffer
  153. elseif hdr.Range and code ~= 206 then
  154. -- We wanted a part but we got the while file
  155. sock:close()
  156. if file then
  157. file:close()
  158. end
  159. return nil, -4, code, resp
  160. elseif not hdr.Range and code ~= 200 then
  161. -- We encountered an error
  162. sock:close()
  163. if file then
  164. file:close()
  165. end
  166. return nil, -4, code, resp
  167. end
  168. if cbs.on_header then
  169. local stat = {cbs.on_header(file, code, resp)}
  170. if stat[1] == false then
  171. if file then
  172. file:close()
  173. end
  174. sock:close()
  175. return unpack(stat)
  176. elseif stat[2] then
  177. file = file and stat[2]
  178. end
  179. end
  180. if not file then
  181. return nil, -5, "no target given"
  182. end
  183. local chunked = resp.headers["Transfer-Encoding"] == "chunked"
  184. local stat
  185. -- Write the buffer to file
  186. file:writeall(buffer)
  187. repeat
  188. if not options.splice or not sock:is_socket() or chunked then
  189. break
  190. end
  191. -- This is a plain TCP socket and there is no encoding so we can splice
  192. local pipein, pipeout, msg = nixio.pipe()
  193. if not pipein then
  194. sock:close()
  195. file:close()
  196. return pipein, pipeout, msg
  197. end
  198. -- Adjust splice values
  199. local ssize = 65536
  200. local smode = nixio.splice_flags("move", "more")
  201. -- Splicing 512 bytes should never block on a fresh pipe
  202. local stat, code, msg = nixio.splice(sock, pipeout, 512, smode)
  203. if stat == nil then
  204. break
  205. end
  206. -- Now do the real splicing
  207. local cb = cbs.on_write
  208. if options.splice == "asynchronous" then
  209. stat, code, msg = splice_async(sock, pipeout, pipein, file, cb)
  210. elseif options.splice == "synchronous" then
  211. stat, code, msg = splice_sync(sock, pipeout, pipein, file, cb)
  212. else
  213. break
  214. end
  215. if stat == false then
  216. break
  217. end
  218. return stat, code, msg
  219. until true
  220. local src = chunked and httpc.chunksource(sock) or sock:blocksource()
  221. local snk = file:sink()
  222. if cbs.on_write then
  223. src = ltn12.source.chain(src, function(chunk)
  224. cbs.on_write(file)
  225. return chunk
  226. end)
  227. end
  228. -- Fallback to read/write
  229. stat, code, msg = ltn12.pump.all(src, snk)
  230. file:close()
  231. sock:close()
  232. return stat and true, code, msg
  233. end