root/luci/trunk/libs/httpclient/luasrc/httpclient/receiver.lua @ 4889

Revision 4889, 5.7 KB (checked in by Cyrus, 4 years ago)

Drop support for luaposix and bitlib (obsoleted by nixio)
Mark luci.fs as deprecated

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