From cbc49a3b537e2e7cc49166f9d85e730d0f7d6275 Mon Sep 17 00:00:00 2001 From: Steven Barth Date: Wed, 18 Feb 2009 21:46:55 +0000 Subject: [PATCH] More nixio fixes, initial httpclient --- libs/httpclient/Makefile | 2 + libs/httpclient/luasrc/httpclient.lua | 217 ++++++++++++++++++++++++++++++++++ libs/nixio/lua/nixio/util.lua | 33 +++++- libs/nixio/src/nixio.c | 12 ++ libs/nixio/src/nixio.h | 4 + 5 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 libs/httpclient/Makefile create mode 100644 libs/httpclient/luasrc/httpclient.lua diff --git a/libs/httpclient/Makefile b/libs/httpclient/Makefile new file mode 100644 index 000000000..f7fac7740 --- /dev/null +++ b/libs/httpclient/Makefile @@ -0,0 +1,2 @@ +include ../../build/config.mk +include ../../build/module.mk diff --git a/libs/httpclient/luasrc/httpclient.lua b/libs/httpclient/luasrc/httpclient.lua new file mode 100644 index 000000000..6681f82aa --- /dev/null +++ b/libs/httpclient/luasrc/httpclient.lua @@ -0,0 +1,217 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2009 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id$ +]]-- + +require "nixio.util" +local nixio = require "nixio" + +local ltn12 = require "luci.ltn12" +local util = require "luci.util" +local table = require "table" +local http = require "luci.http.protocol" + +local type, pairs, tonumber, print = type, pairs, tonumber, print + +module "luci.httpclient" + +function chunksource(sock, buffer) + buffer = buffer or "" + return function() + local output + local _, endp, count = buffer:find("^([0-9a-fA-F]+);?.-\r\n") + while not count and #buffer <= 1024 do + local newblock, code = sock:recv(1024 - #buffer) + if not newblock then + return nil, code + end + buffer = buffer .. newblock + _, endp, count = buffer:find("^([0-9a-fA-F]+);?.-\r\n") + end + count = tonumber(count, 16) + if not count then + return nil, -1, "invalid encoding" + elseif count == 0 then + return nil + elseif count + 2 <= #buffer - endp then + output = buffer:sub(endp+1, endp+count) + buffer = buffer:sub(endp+count+3) + return output + else + output = buffer:sub(endp+1, endp+count) + buffer = "" + if count - #output > 0 then + local remain, code = sock:recvall(count-#output) + if not remain then + return nil, code + end + output = output .. remain + count, code = sock:recvall(2) + else + count, code = sock:recvall(count+2-#buffer+endp) + end + if not count then + return nil, code + end + return output + end + end +end + + +function request_to_buffer(uri, options) + local source, code, msg = request_to_source(uri, options) + local output = {} + + if not source then + return nil, code, msg + end + + source, code = ltn12.pump.all(source, (ltn12.sink.table(output))) + + if not source then + return nil, code + end + + return table.concat(output) +end + +function request_to_source(uri, options) + local status, response, buffer, sock = request_raw(uri, options) + if not status then + return status, response, buffer + elseif status ~= 200 and status ~= 206 then + return nil, status, response + end + + if response["Transfer-Encoding"] == "chunked" then + return chunksource(sock, buffer) + else + return ltn12.source.cat(ltn12.source.string(buffer), sock:blocksource()) + end +end + +-- +-- GET HTTP-resource +-- +function request_raw(uri, options) + options = options or {} + local pr, host, port, path = uri:match("(%w+)://([%w-.]+):?([0-9]*)(.*)") + if not host then + return nil, -1, "unable to parse URI" + end + + if pr ~= "http" then + return nil, -2, "protocol not supported" + end + + port = #port > 0 and port or "80" + path = #path > 0 and path or "/" + + options.depth = options.depth or 10 + local headers = options.headers or {} + local protocol = options.protocol or "HTTP/1.1" + local method = options.method or "GET" + headers["User-Agent"] = headers["User-Agent"] or "LuCI httpclient 0.1" + + if headers.Connection == nil then + headers.Connection = "close" + end + + local sock, code, msg = nixio.connect(host, port) + if not sock then + return nil, code, msg + end + + sock:setsockopt("socket", "sndtimeo", options.sndtimeo or 15) + sock:setsockopt("socket", "rcvtimeo", options.rcvtimeo or 15) + + -- Pre assemble fixes + if protocol == "HTTP/1.1" then + headers.Host = headers.Host or host + end + + if type(options.body) == "table" then + options.body = http.urlencode_params(options.body) + headers["Content-Type"] = headers["Content-Type"] or + "application/x-www-form-urlencoded" + end + + if type(options.body) == "string" then + headers["Content-Length"] = headers["Content-Length"] or #options.body + end + + -- Assemble message + local message = {method .. " " .. path .. " " .. protocol} + + for k, v in pairs(headers) do + if v then + message[#message+1] = k .. ": " .. v + end + end + message[#message+1] = "" + message[#message+1] = "" + + -- Send request + sock:sendall(table.concat(message, "\r\n")) + + if type(options.body) == "string" then + sock:sendall(options.body) + end + + -- Create source and fetch response + local linesrc = sock:linesource() + local line, code, error = linesrc() + + if not line then + return nil, code, error + end + + local protocol, status, msg = line:match("^(HTTP/[0-9.]+) ([0-9]+) (.*)") + + if not protocol then + return nil, -3, "invalid response magic: " .. line + end + + local response = {Status=line} + + line = linesrc() + while line and line ~= "" do + local key, val = line:match("^([%w-]+)%s?:%s?(.*)") + if key and key ~= "Status" then + response[key] = val + end + line = linesrc() + end + + if not line then + return nil, -4, "protocol error" + end + + -- Follow + local code = tonumber(status) + if code and options.depth > 0 then + if code == 301 or code == 302 or code == 307 and response.Location then + local nexturi = response.Location + if not nexturi:find("https?://") then + nexturi = pr .. "://" .. host .. ":" .. port .. nexturi + end + + options.depth = options.depth - 1 + sock:close() + + return request_raw(nexturi, options) + end + end + + return code, response, linesrc(true), sock +end \ No newline at end of file diff --git a/libs/nixio/lua/nixio/util.lua b/libs/nixio/lua/nixio/util.lua index 34800b454..cb9fcf56f 100644 --- a/libs/nixio/lua/nixio/util.lua +++ b/libs/nixio/lua/nixio/util.lua @@ -77,11 +77,12 @@ function socket.linesource(self, limit) if flush then line = buffer:sub(bpos + 1) buffer = "" + bpos = 0 return line end while not line do - _, endp, line = buffer:find("^(.-)\r?\n", bpos + 1) + _, endp, line = buffer:find("(.-)\r?\n", bpos + 1) if line then bpos = endp return line @@ -89,6 +90,8 @@ function socket.linesource(self, limit) local newblock, code = self:recv(limit + bpos - #buffer) if not newblock then return nil, code + elseif #newblock == 0 then + return nil end buffer = buffer:sub(bpos + 1) .. newblock bpos = 0 @@ -97,4 +100,32 @@ function socket.linesource(self, limit) end end end +end + +function socket.blocksource(self, bs, limit) + bs = bs or BUFFERSIZE + return function() + local toread = bs + if limit then + if limit < 1 then + return nil + elseif limit < toread then + toread = limit + end + end + + local block, code, msg = self:recv(toread) + + if not block then + return nil, code + elseif #block == 0 then + return nil + else + if limit then + limit = limit - #block + end + + return block + end + end end \ No newline at end of file diff --git a/libs/nixio/src/nixio.c b/libs/nixio/src/nixio.c index 5f098be31..a4ef7462e 100644 --- a/libs/nixio/src/nixio.c +++ b/libs/nixio/src/nixio.c @@ -123,6 +123,18 @@ LUALIB_API int luaopen_nixio(lua_State *L) { lua_pushnumber(L, VERSION); lua_setfield(L, -2, "version"); + /* some constants */ + lua_createtable(L, 0, 1); + + NIXIO_PUSH_CONSTANT(EACCES); + NIXIO_PUSH_CONSTANT(ENOSYS); + NIXIO_PUSH_CONSTANT(EINVAL); + NIXIO_PUSH_CONSTANT(EWOULDBLOCK); + NIXIO_PUSH_CONSTANT(EAGAIN); + NIXIO_PUSH_CONSTANT(ENOMEM); + + lua_setfield(L, -2, "const"); + /* remove meta table */ lua_remove(L, -2); diff --git a/libs/nixio/src/nixio.h b/libs/nixio/src/nixio.h index e4bb6d6de..cdb43599e 100644 --- a/libs/nixio/src/nixio.h +++ b/libs/nixio/src/nixio.h @@ -6,6 +6,10 @@ #define NIXIO_BUFFERSIZE 8096 #define _FILE_OFFSET_BITS 64 +#define NIXIO_PUSH_CONSTANT(x) \ + lua_pushinteger(L, x); \ + lua_setfield(L, -2, #x); + /* uClibc: broken as always */ #define _LARGEFILE_SOURCE -- 2.11.0