Support Lua template

Signed-off-by: Jianhui Zhao <jianhuizhao329@gmail.com>
main
Jianhui Zhao 2017-12-05 16:46:57 +08:00
parent 4d27dc49ac
commit e4d8ce13ef
9 changed files with 779 additions and 8 deletions

View File

@ -16,7 +16,7 @@ you can choose CyaSSl(wolfssl).
* use [libev] as its event backend
* support HTTPS: alternative OpenSSL and CyaSSl(wolfssl)
* flexible and you can easily extend your application to have HTTP/HTTPS services
* Lua template: embed LUA code into HTML code, like embedding PHP to HTML(soon be supported)
* Lua template: embed LUA code into HTML code, like embedding PHP to HTML
* code structure is concise and understandable, also suitable for learning
# Why use [libev] as its backend?

View File

@ -15,7 +15,7 @@ SSL后端可以选择OpenSSL和CyaSSl(wolfssl),如果你对大小敏感,那
* 使用[libev]作为其事件后端
* 支持HTTPS: 可以选择OpenSSL和CyaSSl(wolfssl)
* 可伸缩你可以非常方便的扩展你的应用程序使之具备HTTP/HTTPS服务
* Lua模板嵌入LUA代码到HTML中就像嵌入PHP到HTML中一样(即将支持)
* Lua模板嵌入LUA代码到HTML中就像嵌入PHP到HTML中一样
* 代码结构简洁通俗易懂,亦适合学习
# 为什么使用[libev]作为其事件后端?

View File

@ -1,2 +1,6 @@
add_executable(helloworld helloworld.c)
target_link_libraries(helloworld uhttp)
add_executable(lua_template lua_template.c)
target_link_libraries(lua_template uhttp)

View File

@ -0,0 +1,56 @@
#include <ev.h>
#include <stdio.h>
#include <uhttp.h>
static void signal_cb(struct ev_loop *loop, ev_signal *w, int revents)
{
printf("Got signal: %d\n", w->signum);
ev_break(loop, EVBREAK_ALL);
}
static void lua_template(struct uh_connection *con)
{
uh_template(con);
}
int main(int argc, char **argv)
{
struct ev_loop *loop = EV_DEFAULT;
ev_signal *sig_watcher = NULL;
struct uh_server *srv = NULL;
uh_log_info("libuhttp version: %s", uh_version());
sig_watcher = calloc(1, sizeof(ev_signal));
if (!sig_watcher)
return -1;
ev_signal_init(sig_watcher, signal_cb, SIGINT);
ev_signal_start(loop, sig_watcher);
srv = uh_server_new(loop, "0.0.0.0", 8000);
if (!srv) {
uh_log_err("uh_server_new failed");
goto err;
}
#if (UHTTP_SSL_ENABLED)
if (uh_ssl_init(srv, "server-cert.pem", "server-key.pem") < 0)
goto err;
#endif
uh_register_default_hook(srv, lua_template);
uh_log_info("Listen on 8000...");
ev_run(loop, 0);
err:
free(sig_watcher);
uh_server_free(srv);
return 0;
}

View File

@ -7,10 +7,11 @@ set(UHTTP_VERSION_PATCH 1)
find_package(Libev REQUIRED)
find_package(OpenSSL)
find_package(CyaSSL)
find_package(Lua)
include_directories(${LIBEV_INCLUDE_DIRS})
set(EXTRA_LIBS ${LIBEV_LIBRARIES})
set(SOURCE_FILES uhttp.c log.c buf.c ssl.c parser.c)
include_directories(${LIBEV_INCLUDE_DIR} ${LUA_INCLUDE_DIR})
set(EXTRA_LIBS ${LIBEV_LIBRARIES} ${LUA_LIBRARIES})
set(SOURCE_FILES uhttp.c log.c buf.c ssl.c parser.c template.c)
set(UHTTP_DEBUG_CONFIG 0)
option(UHTTP_DEBUG "Turn on debug" OFF)

View File

@ -18,6 +18,8 @@
#ifndef _UHTTP_INTERNAL_H
#define _UHTTP_INTERNAL_H
#include <lua.h>
#include "list.h"
#include "uhttp/uhttp.h"
@ -52,6 +54,8 @@ struct uh_server {
#if (UHTTP_SSL_ENABLED)
void *ssl_ctx;
#endif
char *docroot;
lua_State *L;
ev_io read_watcher;
struct ev_loop *loop;
uh_hookfn_t default_cb;
@ -81,6 +85,8 @@ struct uh_connection {
unsigned char flags;
struct uh_buf read_buf;
struct uh_buf write_buf;
ev_child child_watcher;
ev_io read_watcher_lua;
ev_io read_watcher;
ev_io write_watcher;
ev_timer timer_watcher;

View File

@ -38,6 +38,12 @@ struct uh_server *uh_server_new(struct ev_loop *loop, const char *ipaddr, int po
/* frees a uhttp server instance. */
void uh_server_free(struct uh_server *srv);
/* Sets document root. */
void uh_set_docroot(struct uh_server *srv, const char *path);
/* Processing Lua templates */
void uh_template(struct uh_connection *con);
/* Sends data to the connection. */
int uh_send(struct uh_connection *con, const void *buf, int len);
@ -111,11 +117,11 @@ struct uh_str *uh_get_query(struct uh_connection *con);
struct uh_str uh_get_var(struct uh_connection *con, const char *name);
struct uh_str *uh_get_header(struct uh_connection *con, const char *name);
int uh_get_con_sock(struct uh_connection *con);
/* Unescapes strings like '%7B1,%202,%203%7D' would become '{1, 2, 3}' */
int uh_unescape(const char *str, int len, char *out, int olen);
int uh_get_con_sock(struct uh_connection *con);
#if (UHTTP_SSL_ENABLED)
/* Init ssl for the server */
int uh_ssl_init(struct uh_server *srv, const char *cert, const char *key);

677
src/template.c 100755
View File

@ -0,0 +1,677 @@
/*
* Copyright (C) 2017 Jianhui Zhao <jianhuizhao329@gmail.com>
*
* based on https://git.lede-project.org/project/luci.git modules/luci-base/src/template_parser.c
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "internal.h"
#include "uhttp/uhttp.h"
/* code types */
#define T_TYPE_INIT 0
#define T_TYPE_TEXT 1
#define T_TYPE_COMMENT 2
#define T_TYPE_EXPR 3
#define T_TYPE_INCLUDE 4
#define T_TYPE_CODE 5
#define T_TYPE_EOF 6
/* leading and trailing code for different types */
static const char *gen_code[9][2] = {
{ NULL, NULL }, /* T_TYPE_INIT */
{ "io.write(\"", "\")" }, /* T_TYPE_TEXT */
{ NULL, NULL }, /* T_TYPE_COMMENT */
{ "io.write(tostring(", " or \"\"))" }, /* T_TYPE_EXPR */
{ "include(\"", "\")" }, /* T_TYPE_INCLUDE */
{ NULL, " " }, /* T_TYPE_CODE */
{ NULL, NULL }, /* T_TYPE_EOF */
};
/* buffer object */
struct template_buffer {
char *data;
char *dptr;
unsigned int size;
unsigned int fill;
};
struct template_chunk {
const char *s;
const char *e;
int type;
int line;
};
/* parser state */
struct template_parser {
int fd;
uint32_t size;
char *data;
char *off;
char *gc;
int line;
int in_expr;
int strip_before;
int strip_after;
struct template_chunk prv_chunk;
struct template_chunk cur_chunk;
const char *file;
};
/* initialize a buffer object */
struct template_buffer *buf_init(int size)
{
struct template_buffer *buf;
if (size <= 0)
size = 1024;
buf = (struct template_buffer *)malloc(sizeof(struct template_buffer));
if (buf != NULL)
{
buf->fill = 0;
buf->size = size;
buf->data = malloc(buf->size);
if (buf->data != NULL) {
buf->dptr = buf->data;
buf->data[0] = 0;
return buf;
}
free(buf);
}
return NULL;
}
/* grow buffer */
static int buf_grow(struct template_buffer *buf, int size)
{
unsigned int off = (buf->dptr - buf->data);
char *data;
if (size <= 0)
size = 1024;
data = realloc(buf->data, buf->size + size);
if (data != NULL) {
buf->data = data;
buf->dptr = data + off;
buf->size += size;
return buf->size;
}
return 0;
}
/* put one char into buffer object */
static int buf_putchar(struct template_buffer *buf, char c)
{
if( ((buf->fill + 1) >= buf->size) && !buf_grow(buf, 0) )
return 0;
*(buf->dptr++) = c;
*(buf->dptr) = 0;
buf->fill++;
return 1;
}
/* append data to buffer */
static int buf_append(struct template_buffer *buf, const char *s, int len)
{
if ((buf->fill + len + 1) >= buf->size) {
if (!buf_grow(buf, len + 1))
return 0;
}
memcpy(buf->dptr, s, len);
buf->fill += len;
buf->dptr += len;
*(buf->dptr) = 0;
return len;
}
/* read buffer length */
static int buf_length(struct template_buffer *buf)
{
return buf->fill;
}
/* destroy buffer object and return pointer to data */
static char *buf_destroy(struct template_buffer *buf)
{
char *data = buf->data;
free(buf);
return data;
}
static void luastr_escape(struct template_buffer *out, const char *s, unsigned int l,
int escape_xml)
{
int esl;
char esq[8];
char *ptr;
for (ptr = (char *)s; ptr < (s + l); ptr++) {
switch (*ptr) {
case '\\':
buf_append(out, "\\\\", 2);
break;
case '"':
if (escape_xml)
buf_append(out, "&#34;", 5);
else
buf_append(out, "\\\"", 2);
break;
case '\n':
buf_append(out, "\\n", 2);
break;
case '\'':
case '&':
case '<':
case '>':
if (escape_xml) {
esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr);
buf_append(out, esq, esl);
break;
}
default:
buf_putchar(out, *ptr);
}
}
}
/* Simple strstr() like function that takes len arguments for both haystack and needle. */
static char *strfind(char *haystack, int hslen, const char *needle, int ndlen)
{
int match = 0;
int i, j;
for( i = 0; i < hslen; i++ ) {
if( haystack[i] == needle[0] ) {
match = ((ndlen == 1) || ((i + ndlen) <= hslen));
for( j = 1; (j < ndlen) && ((i + j) < hslen); j++ ) {
if( haystack[i+j] != needle[j] ) {
match = 0;
break;
}
}
if( match )
return &haystack[i];
}
}
return NULL;
}
static int template_error(lua_State *L, struct template_parser *parser)
{
const char *err = luaL_checkstring(L, -1);
const char *off = parser->prv_chunk.s;
const char *ptr;
char msg[1024];
int line = 0;
int chunkline = 0;
if ((ptr = strfind((char *)err, strlen(err), "]:", 2)) != NULL) {
chunkline = atoi(ptr + 2) - parser->prv_chunk.line;
while (*ptr) {
if (*ptr++ == ' ') {
err = ptr;
break;
}
}
}
if (strfind((char *)err, strlen(err), "'char(27)'", 10) != NULL) {
off = parser->data + parser->size;
err = "'%>' expected before end of file";
chunkline = 0;
}
for (ptr = parser->data; ptr < off; ptr++)
if (*ptr == '\n')
line++;
snprintf(msg, sizeof(msg), "Syntax error in %s:%d: %s",
parser->file ? parser->file : "[string]", line + chunkline, err ? err : "(unknown error)");
lua_pushnil(L);
lua_pushinteger(L, line + chunkline);
lua_pushstring(L, msg);
return 3;
}
static void template_close(struct template_parser *parser)
{
if (!parser)
return;
if (parser->gc != NULL)
free(parser->gc);
/* if file is not set, we were parsing a string */
if (parser->file) {
if ((parser->data != NULL) && (parser->data != MAP_FAILED))
munmap(parser->data, parser->size);
if (parser->fd >= 0)
close(parser->fd);
}
free(parser);
}
static void template_text(struct template_parser *parser, const char *e)
{
const char *s = parser->off;
if (s < (parser->data + parser->size)) {
if (parser->strip_after) {
while ((s <= e) && isspace(*s))
s++;
}
parser->cur_chunk.type = T_TYPE_TEXT;
} else {
parser->cur_chunk.type = T_TYPE_EOF;
}
parser->cur_chunk.line = parser->line;
parser->cur_chunk.s = s;
parser->cur_chunk.e = e;
}
static void template_code(struct template_parser *parser, const char *e)
{
const char *s = parser->off;
parser->strip_before = 0;
parser->strip_after = 0;
if (*s == '-') {
parser->strip_before = 1;
for (s++; (s <= e) && (*s == ' ' || *s == '\t'); s++);
}
if (*(e-1) == '-') {
parser->strip_after = 1;
for (e--; (e >= s) && (*e == ' ' || *e == '\t'); e--);
}
switch (*s) {
/* comment */
case '#':
s++;
parser->cur_chunk.type = T_TYPE_COMMENT;
break;
/* include */
case '+':
s++;
parser->cur_chunk.type = T_TYPE_INCLUDE;
break;
/* expr */
case '=':
s++;
parser->cur_chunk.type = T_TYPE_EXPR;
break;
/* code */
default:
parser->cur_chunk.type = T_TYPE_CODE;
break;
}
parser->cur_chunk.line = parser->line;
parser->cur_chunk.s = s;
parser->cur_chunk.e = e;
}
static const char *template_format_chunk(struct template_parser *parser, size_t *sz)
{
const char *s, *p;
const char *head, *tail;
struct template_chunk *c = &parser->prv_chunk;
struct template_buffer *buf;
*sz = 0;
s = parser->gc = NULL;
if (parser->strip_before && c->type == T_TYPE_TEXT) {
while ((c->e > c->s) && isspace(*(c->e - 1)))
c->e--;
}
/* empty chunk */
if (c->s == c->e) {
if (c->type == T_TYPE_EOF) {
*sz = 0;
s = NULL;
} else {
*sz = 1;
s = " ";
}
} else if ((buf = buf_init(c->e - c->s)) != NULL) { /* format chunk */
if ((head = gen_code[c->type][0]) != NULL)
buf_append(buf, head, strlen(head));
switch (c->type) {
case T_TYPE_TEXT:
luastr_escape(buf, c->s, c->e - c->s, 0);
break;
case T_TYPE_EXPR:
buf_append(buf, c->s, c->e - c->s);
for (p = c->s; p < c->e; p++)
parser->line += (*p == '\n');
break;
case T_TYPE_INCLUDE:
luastr_escape(buf, c->s, c->e - c->s, 0);
break;
case T_TYPE_CODE:
buf_append(buf, c->s, c->e - c->s);
for (p = c->s; p < c->e; p++)
parser->line += (*p == '\n');
break;
}
if ((tail = gen_code[c->type][1]) != NULL)
buf_append(buf, tail, strlen(tail));
*sz = buf_length(buf);
s = parser->gc = buf_destroy(buf);
if (!*sz) {
*sz = 1;
s = " ";
}
}
return s;
}
static const char *template_reader(lua_State *L, void *ud, size_t *sz)
{
struct template_parser *parser = ud;
int rem = parser->size - (parser->off - parser->data);
char *tag;
parser->prv_chunk = parser->cur_chunk;
/* free previous string */
if (parser->gc) {
free(parser->gc);
parser->gc = NULL;
}
/* before tag */
if (!parser->in_expr) {
if ((tag = strfind(parser->off, rem, "<%", 2)) != NULL) {
template_text(parser, tag);
parser->off = tag + 2;
parser->in_expr = 1;
} else {
template_text(parser, parser->data + parser->size);
parser->off = parser->data + parser->size;
}
} else { /* inside tag */
if ((tag = strfind(parser->off, rem, "%>", 2)) != NULL) {
template_code(parser, tag);
parser->off = tag + 2;
parser->in_expr = 0;
} else {
/* unexpected EOF */
template_code(parser, parser->data + parser->size);
*sz = 1;
return "\033";
}
}
return template_format_chunk(parser, sz);
}
static int template_L_do_parse(lua_State *L, struct template_parser *parser, const char *chunkname)
{
int lua_status, rv;
if (!parser) {
lua_pushnil(L);
lua_pushinteger(L, errno);
lua_pushstring(L, strerror(errno));
return 3;
}
#if LUA_VERSION_NUM > 501
lua_status = lua_load(L, template_reader, parser, chunkname, NULL);
#else
lua_status = lua_load(L, template_reader, parser, chunkname);
#endif
if (lua_status == 0)
rv = 1;
else
rv = template_error(L, parser);
template_close(parser);
return rv;
}
struct template_parser * template_open(const char *file)
{
struct stat s;
struct template_parser *parser;
if (!(parser = malloc(sizeof(*parser))))
goto err;
memset(parser, 0, sizeof(*parser));
parser->fd = -1;
parser->file = file;
if (stat(file, &s))
goto err;
if ((parser->fd = open(file, O_RDONLY)) < 0)
goto err;
parser->size = s.st_size;
parser->data = mmap(NULL, parser->size, PROT_READ, MAP_PRIVATE,
parser->fd, 0);
if (parser->data != MAP_FAILED) {
parser->off = parser->data;
parser->cur_chunk.type = T_TYPE_INIT;
parser->cur_chunk.s = parser->data;
parser->cur_chunk.e = parser->data;
return parser;
}
err:
template_close(parser);
return NULL;
}
static void template_read_cb(struct ev_loop *loop, ev_io *w, int revents)
{
struct uh_connection *con = container_of(w, struct uh_connection, read_watcher_lua);
char buf[1024] = "";
int len;
len = read(w->fd, buf, sizeof(buf));
if (len > 0)
uh_send_chunk(con, buf, len);
else if (len == 0) {
uh_send_chunk(con, NULL, 0);
close(w->fd);
ev_io_stop(con->srv->loop, w);
if (!(con->flags & UH_CON_CLOSE))
con->flags |= UH_CON_REUSE;
}
}
static void child_cb(struct ev_loop *loop, ev_child *w, int revents)
{
struct uh_connection *con = container_of(w, struct uh_connection, child_watcher);
ev_child_stop(con->srv->loop, w);
}
void uh_template(struct uh_connection *con)
{
struct template_parser *parser;
lua_State *L = con->srv->L;
pid_t pid;
int pipefd[2];
static char path[PATH_MAX] = "";
struct stat st;
struct sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
struct uh_str *ustr;
strcpy(path, con->srv->docroot);
strncat(path, con->req.path.at, con->req.path.len);
if (stat(path, &st) < 0) {
uh_send_error(con, HTTP_STATUS_NOT_FOUND, NULL);
return;
}
uh_log_debug("Path:%s", path);
if (!L) {
L = luaL_newstate();
if (!L) {
uh_log_err("cannot create LUA state: not enough memory");
goto err;
}
con->srv->L = L;
luaL_openlibs(L);
}
/*
* Add all variables to the global environment of LUA.
* eg. <%=_UHTTP["REMOTE_HOST"]%>
*/
lua_newtable(L);
lua_pushstring(L, uh_get_method_str(con));
lua_setfield(L, -2, "HTTP_METHOD");
getpeername(con->sock, (struct sockaddr *)&addr, &addrlen);
lua_pushstring(L, inet_ntoa(addr.sin_addr));
lua_setfield(L, -2, "REMOTE_HOST");
ustr = uh_get_url(con);
lua_pushlstring(L, ustr->at, ustr->len);
lua_setfield(L, -2, "HTTP_URL");
ustr = uh_get_path(con);
lua_pushlstring(L, ustr->at, ustr->len);
lua_setfield(L, -2, "HTTP_PATH");
lua_setglobal(L, "_UHTTP");
if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) < 0) {
uh_log_err("pipe");
goto err;
}
uh_send_head(con, HTTP_STATUS_OK, -1, "Pragma: no-cache\r\nCache-Control: no-cache\r\n");
pid = fork();
switch (pid) {
case -1:
uh_log_err("fork");
goto err;
break;
case 0:
close(0);
close(1);
close(pipefd[0]);
dup2(pipefd[1], 1);
parser = template_open(path);
if (!parser) {
uh_log_err("template_open failed");
return;
}
if ((template_L_do_parse(L, parser, path) != 1) || lua_pcall(L, 0, 0, 0)) {
uh_printf_chunk(con, "<h2><b>Lua Error</b></h2>\n%s\n", lua_tostring(L, -1));
uh_printf_chunk(con, "</body></html>\n");
uh_send_chunk(con, NULL, 0);
lua_pop(L, -1);
} else {
uh_send_chunk(con, NULL, 0);
}
exit(0);
break;
default:
close(pipefd[1]);
ev_io_init(&con->read_watcher_lua, template_read_cb, pipefd[0], EV_READ);
ev_io_start(con->srv->loop, &con->read_watcher_lua);
ev_child_init(&con->child_watcher, child_cb, pid, 0);
ev_child_start(con->srv->loop, &con->child_watcher);
break;
}
return;
err:
uh_send_error(con, HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
}

View File

@ -21,6 +21,7 @@
#include <errno.h>
#include <assert.h>
#include <arpa/inet.h>
#include <signal.h>
#include "internal.h"
#include "uhttp/uhttp.h"
@ -346,9 +347,12 @@ struct uh_server *uh_server_new(struct ev_loop *loop, const char *ipaddr, int po
struct sockaddr_in addr;
int sock = -1, on = 1;
ev_io *read_watcher;
char buf[PATH_MAX] = "";
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
signal(SIGPIPE, SIG_IGN);
if (inet_pton(AF_INET, ipaddr, &addr.sin_addr) <= 0) {
uh_log_err("invalid ipaddr");
@ -360,6 +364,14 @@ struct uh_server *uh_server_new(struct ev_loop *loop, const char *ipaddr, int po
uh_log_err("calloc");
return NULL;
}
if (!realpath(".", buf)) {
uh_log_err("Unable to determine work dir");
goto err;
}
srv->docroot = strdup(buf);
uh_log_debug("docroot:%s", srv->docroot);
INIT_LIST_HEAD(&srv->hooks);
INIT_LIST_HEAD(&srv->connections);
@ -401,7 +413,10 @@ void uh_server_free(struct uh_server *srv)
if (srv) {
struct uh_connection *con, *tmp_c;
struct uh_hook *h, *tmp_h;
if (srv->docroot)
free(srv->docroot);
if (srv->sock > 0)
close(srv->sock);
@ -423,6 +438,12 @@ void uh_server_free(struct uh_server *srv)
}
}
void uh_set_docroot(struct uh_server *srv, const char *path)
{
srv->docroot = strdup(path);
uh_log_debug("docroot:%s", srv->docroot);
}
int uh_send(struct uh_connection *con, const void *buf, int len)
{
len = uh_buf_append(&con->write_buf, buf, len);