1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
|
--
-- This file is part of LEM, a Lua Event Machine.
-- Copyright 2013 Emil Renner Berthing
--
-- LEM is free software: you can redistribute it and/or modify it
-- under the terms of the GNU Lesser General Public License as
-- published by the Free Software Foundation, either version 3 of
-- the License, or (at your option) any later version.
--
-- LEM is distributed in the hope that it will be useful, but
-- WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with LEM. If not, see <http://www.gnu.org/licenses/>.
--
local setmetatable = setmetatable
local tonumber = tonumber
local concat = table.concat
local io = require 'lem.io'
require 'lem.http'
local M = {}
local Response = {}
Response.__index = Response
M.Response = Response
function Response:body_chunked()
if self._body then return self._body end
local conn = self.conn
local rope, i = {}, 0
local line, err
while true do
line, err = conn:read('*l')
if not line then return nil, err end
local len = tonumber(line, 16)
if not len then return nil, 'expectation failed' end
if len == 0 then break end
local data, err = conn:read(len)
if not data then return nil, err end
i = i + 1
rope[i] = data
line, err = conn:read('*l')
if not line then return nil, err end
end
line, err = conn:read('*l')
if not line then return nil, err end
rope = concat(rope)
self._body = rope
return rope
end
function Response:body()
if self._body then return self._body end
if self.headers['transfer-encoding'] == 'chunked' then
return self:body_chunked()
end
local len, body, err = self.headers['content-length']
if len then
len = tonumber(len)
if not len then return nil, 'invalid content length' end
body, err = self.conn:read(len)
else
if self.headers['connection'] == 'close' then
body, err = self.client:read('*a')
else
return nil, 'no content length specified'
end
end
if not body then return nil, err end
self._body = body
return body
end
local Client = {}
Client.__index = Client
M.Client = Client
function M.new()
return setmetatable({
proto = false,
domain = false,
conn = false,
}, Client)
end
local req_get = "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n\r\n"
--local req_get = "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n"
local function close(self)
local c = self.conn
if c then
self.conn = false
return c:close()
end
return true
end
Client.close = close
local function fail(self, err)
self.proto = false
close(self)
return nil, err
end
function Client:get(url)
local proto, domain, uri = url:match('([a-zA-Z0-9]+)://([a-zA-Z0-9.]+)(/.*)')
if not proto then
error('Invalid URL', 2)
end
local c, err
local req = req_get:format(uri, domain)
local res
if proto == self.proto and domain == self.domain then
c = self.conn
if c:write(req) then
res = c:read('HTTPResponse')
end
end
if not res then
c = self.conn
if c then
c:close()
end
if proto == 'http' then
c, err = io.tcp.connect(domain, '80')
elseif proto == 'https' then
local ssl = self.ssl
if not ssl then
error('No ssl context defined', 2)
end
c, err = ssl:connect(domain, '443')
else
error('Unknown protocol', 2)
end
if not c then return fail(self, err) end
local ok
ok, err = c:write(req)
if not ok then return fail(self, err) end
res, err = c:read('HTTPResponse')
if not res then return fail(self, err) end
end
res.conn = c
setmetatable(res, Response)
self.proto = proto
self.domain = domain
self.conn = c
return res
end
function Client:download(url, filename)
local res, err = self:get(url)
if not res then return res, err end
local file
file, err = io.open(filename, 'w')
if not file then return file, err end
local ok
ok, err = file:write(res.body)
if not ok then return ok, err end
ok, err = file:close()
if not ok then return ok, err end
return true
end
return M
-- vim: set ts=2 sw=2 noet:
|