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

Revision 5135, 5.9 KB (checked in by Cyrus, 4 years ago)

Merge from fonosfera

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    local file, code, msg
175   
176    if target then
177        file, code, msg = prepare_fd(target)
178        if not file then
179            return file, code, msg
180        end
181   
182        local off = file:tell()
183       
184        -- Set content range
185        if off > 0 then
186            hdr.Range = hdr.Range or ("bytes=" .. off .. "-") 
187        end
188    end
189   
190    local code, resp, buffer, sock = httpc.request_raw(uri, options)
191    if not code then
192        -- No success
193        if file then
194            file:close()
195        end
196        return code, resp, buffer
197    elseif hdr.Range and code ~= 206 then
198        -- We wanted a part but we got the while file
199        sock:close()
200        if file then
201            file:close()
202        end
203        return nil, -4, code, resp
204    elseif not hdr.Range and code ~= 200 then
205        -- We encountered an error
206        sock:close()
207        if file then
208            file:close()
209        end
210        return nil, -4, code, resp
211    end
212   
213    if cbs.on_header then
214        local stat = {cbs.on_header(file, code, resp)}
215        if stat[1] == false then
216            if file then
217                file:close()
218            end
219            sock:close()
220            return unpack(stat)
221        elseif stat[2] then
222            file = file and stat[2]
223        end
224    end
225   
226    if not file then
227        return nil, -5, "no target given"
228    end
229
230    local chunked = resp.headers["Transfer-Encoding"] == "chunked"
231    local stat
232
233    -- Write the buffer to file
234    file:writeall(buffer)
235   
236    repeat
237        if not options.splice or not sock:is_socket() or chunked then
238            break
239        end
240       
241        -- This is a plain TCP socket and there is no encoding so we can splice
242   
243        local pipein, pipeout, msg = nixio.pipe()
244        if not pipein then
245            sock:close()
246            file:close()
247            return pipein, pipeout, msg
248        end
249       
250       
251        -- Adjust splice values
252        local ssize = 65536
253        local smode = nixio.splice_flags("move", "more")
254       
255        -- Splicing 512 bytes should never block on a fresh pipe
256        local stat, code, msg = nixio.splice(sock, pipeout, 512, smode)
257        if stat == nil then
258            break
259        end
260       
261        -- Now do the real splicing
262        local cb = cbs.on_write
263        if options.splice == "asynchronous" then
264            stat, code, msg = splice_async(sock, pipeout, pipein, file, cb)
265        elseif options.splice == "synchronous" then
266            stat, code, msg = splice_sync(sock, pipeout, pipein, file, cb)
267        else
268            break
269        end
270       
271        if stat == false then
272            break
273        end
274
275        return stat, code, msg
276    until true
277   
278    local src = chunked and httpc.chunksource(sock) or sock:blocksource()
279    local snk = file:sink()
280   
281    if cbs.on_write then
282        src = ltn12.source.chain(src, function(chunk)
283            cbs.on_write(file)
284            return chunk
285        end)
286    end
287   
288    -- Fallback to read/write
289    stat, code, msg = ltn12.pump.all(src, snk)
290
291    file:close()
292    sock:close()
293    return stat and true, code, msg
294end
295
Note: See TracBrowser for help on using the browser.