Merge pull request 'Add support for HTTP `PUT`' (#3) from midokura-xavi/libweb:put into master
Reviewed-on: #3
This commit is contained in:
commit
b94f76033f
|
@ -5,3 +5,4 @@
|
|||
*.so.*
|
||||
examples/hello/hello
|
||||
examples/html/html
|
||||
examples/put/put
|
||||
|
|
|
@ -2,3 +2,4 @@ cmake_minimum_required(VERSION 3.13)
|
|||
add_subdirectory(headers)
|
||||
add_subdirectory(hello)
|
||||
add_subdirectory(html)
|
||||
add_subdirectory(put)
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
all: \
|
||||
headers \
|
||||
hello \
|
||||
html
|
||||
html \
|
||||
put
|
||||
|
||||
clean:
|
||||
+cd hello && $(MAKE) clean
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.13)
|
||||
project(put C)
|
||||
add_executable(${PROJECT_NAME} main.c)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE web dynstr)
|
|
@ -0,0 +1,26 @@
|
|||
.POSIX:
|
||||
|
||||
PROJECT = put
|
||||
DEPS = \
|
||||
main.o
|
||||
LIBWEB = ../../libweb.a
|
||||
DYNSTR = ../../dynstr/libdynstr.a
|
||||
CFLAGS = -I ../../include -I ../../dynstr/include -g
|
||||
LIBWEB_FLAGS = -L ../../ -l web
|
||||
DYNSTR_FLAGS = -L ../../dynstr -l dynstr
|
||||
|
||||
all: $(PROJECT)
|
||||
|
||||
clean:
|
||||
rm -f $(DEPS)
|
||||
|
||||
FORCE:
|
||||
|
||||
$(PROJECT): $(DEPS) $(LIBWEB) $(DYNSTR)
|
||||
$(CC) $(LDFLAGS) $(DEPS) $(LIBWEB_FLAGS) $(DYNSTR_FLAGS) -o $@
|
||||
|
||||
$(LIBWEB): FORCE
|
||||
+cd ../../ && $(MAKE)
|
||||
|
||||
$(DYNSTR): FORCE
|
||||
+cd ../../dynstr && $(MAKE)
|
|
@ -0,0 +1,18 @@
|
|||
# HTTP `PUT` example
|
||||
|
||||
This example shows a minimal setup for an application using `libweb` that only
|
||||
accepts `PUT` requests. When executed, it starts a HTTP/1.1 server on a random
|
||||
port that shall be printed to standard output. When a `PUT` request is received,
|
||||
`libweb` shall store the body into a file in the `/tmp` directory, and print the
|
||||
path of the temporary file to the standard output.
|
||||
|
||||
## How to build
|
||||
|
||||
If using `make(1)`, just run `make` from this directory.
|
||||
|
||||
If using CMake, examples are built by default when configuring the project
|
||||
from [the top-level `CMakeLists.txt`](../../CMakeLists.txt).
|
||||
|
||||
## How to run
|
||||
|
||||
Run the executable without any command line arguments.
|
|
@ -0,0 +1,90 @@
|
|||
#include <dynstr.h>
|
||||
#include <libweb/handler.h>
|
||||
#include <libweb/html.h>
|
||||
#include <libweb/http.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int on_put(const struct http_payload *const pl,
|
||||
struct http_response *const r, void *const user)
|
||||
{
|
||||
if (pl->expect_continue)
|
||||
{
|
||||
*r = (const struct http_response)
|
||||
{
|
||||
.status = HTTP_STATUS_CONTINUE
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("File uploaded to %s\n", pl->u.put.tmpname);
|
||||
|
||||
*r = (const struct http_response)
|
||||
{
|
||||
.status = HTTP_STATUS_OK
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_length(const unsigned long long len,
|
||||
const struct http_cookie *const c, struct http_response *const r,
|
||||
void *const user)
|
||||
{
|
||||
*r = (const struct http_response)
|
||||
{
|
||||
.status = HTTP_STATUS_OK
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int ret = EXIT_FAILURE;
|
||||
const struct handler_cfg cfg =
|
||||
{
|
||||
.tmpdir = "/tmp",
|
||||
.length = on_length
|
||||
};
|
||||
|
||||
struct handler *const h = handler_alloc(&cfg);
|
||||
static const char *const urls[] = {"/*"};
|
||||
|
||||
if (!h)
|
||||
{
|
||||
fprintf(stderr, "%s: handler_alloc failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof urls / sizeof *urls; i++)
|
||||
if (handler_add(h, urls[i], HTTP_OP_PUT, on_put, NULL))
|
||||
{
|
||||
fprintf(stderr, "%s: handler_add failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
unsigned short outport;
|
||||
|
||||
if (handler_listen(h, 0, &outport))
|
||||
{
|
||||
fprintf(stderr, "%s: handler_listen failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
printf("Listening on port %hu\n", outport);
|
||||
|
||||
if (handler_loop(h))
|
||||
{
|
||||
fprintf(stderr, "%s: handler_loop failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = EXIT_SUCCESS;
|
||||
|
||||
end:
|
||||
handler_free(h);
|
||||
return ret;
|
||||
}
|
268
http.c
268
http.c
|
@ -38,11 +38,11 @@ struct http_ctx
|
|||
char *resource, *field, *value, *boundary;
|
||||
size_t len;
|
||||
|
||||
struct post
|
||||
struct payload
|
||||
{
|
||||
char *path;
|
||||
unsigned long long len, read;
|
||||
} post;
|
||||
} payload;
|
||||
|
||||
union
|
||||
{
|
||||
|
@ -85,11 +85,19 @@ struct http_ctx
|
|||
char *name, *filename, *tmpname, *value;
|
||||
} *forms;
|
||||
} mf;
|
||||
|
||||
struct put
|
||||
{
|
||||
char *tmpname;
|
||||
int fd;
|
||||
off_t written;
|
||||
} put;
|
||||
} u;
|
||||
|
||||
struct http_arg *args;
|
||||
size_t n_args, n_headers;
|
||||
struct http_header *headers;
|
||||
bool has_length, expect_continue;
|
||||
} ctx;
|
||||
|
||||
struct write_ctx
|
||||
|
@ -340,6 +348,27 @@ end:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int get_op(const char *const line, const size_t n,
|
||||
enum http_op *const out)
|
||||
{
|
||||
static const char *const ops[] =
|
||||
{
|
||||
[HTTP_OP_GET] = "GET",
|
||||
[HTTP_OP_POST] = "POST",
|
||||
[HTTP_OP_HEAD] = "HEAD",
|
||||
[HTTP_OP_PUT] = "PUT"
|
||||
};
|
||||
|
||||
for (enum http_op op = 0; op < sizeof ops / sizeof *ops; op++)
|
||||
if (!strncmp(line, ops[op], n))
|
||||
{
|
||||
*out = op;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int start_line(struct http_ctx *const h)
|
||||
{
|
||||
const char *const line = h->line;
|
||||
|
@ -361,13 +390,7 @@ static int start_line(struct http_ctx *const h)
|
|||
struct ctx *const c = &h->ctx;
|
||||
const size_t n = op - line;
|
||||
|
||||
if (!strncmp(line, "GET", n))
|
||||
c->op = HTTP_OP_GET;
|
||||
else if (!strncmp(line, "POST", n))
|
||||
c->op = HTTP_OP_POST;
|
||||
else if (!strncmp(line, "HEAD", n))
|
||||
c->op = HTTP_OP_HEAD;
|
||||
else
|
||||
if (get_op(line, n, &c->op))
|
||||
{
|
||||
fprintf(stderr, "%s: unsupported HTTP op %.*s\n",
|
||||
__func__, (int)n, line);
|
||||
|
@ -467,6 +490,16 @@ static void ctx_free(struct ctx *const c)
|
|||
|
||||
free(m->forms);
|
||||
}
|
||||
else if (c->op == HTTP_OP_PUT)
|
||||
{
|
||||
struct put *const p = &c->u.put;
|
||||
|
||||
if (p->fd >= 0 && close(p->fd))
|
||||
fprintf(stderr, "%s: close(2) p->fd: %s\n",
|
||||
__func__, strerror(errno));
|
||||
|
||||
free(c->u.put.tmpname);
|
||||
}
|
||||
|
||||
free(c->field);
|
||||
free(c->value);
|
||||
|
@ -859,7 +892,7 @@ static int set_length(struct http_ctx *const h, const char *const len)
|
|||
char *end;
|
||||
|
||||
errno = 0;
|
||||
h->ctx.post.len = strtoull(len, &end, 10);
|
||||
const unsigned long long value = strtoull(len, &end, 10);
|
||||
|
||||
if (errno || *end != '\0')
|
||||
{
|
||||
|
@ -868,6 +901,26 @@ static int set_length(struct http_ctx *const h, const char *const len)
|
|||
return 1;
|
||||
}
|
||||
|
||||
struct ctx *const c = &h->ctx;
|
||||
|
||||
switch (c->op)
|
||||
{
|
||||
case HTTP_OP_POST:
|
||||
/* Fall through. */
|
||||
case HTTP_OP_PUT:
|
||||
c->payload.len = value;
|
||||
c->u.put = (const struct put){.fd = -1};
|
||||
break;
|
||||
|
||||
case HTTP_OP_GET:
|
||||
/* Fall through. */
|
||||
case HTTP_OP_HEAD:
|
||||
fprintf(stderr, "%s: unexpected header for HTTP op %d\n",
|
||||
__func__, c->op);
|
||||
return 1;
|
||||
}
|
||||
|
||||
c->has_length = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -887,6 +940,12 @@ static int set_content_type(struct http_ctx *const h, const char *const type)
|
|||
__func__, (int)n, type);
|
||||
return 1;
|
||||
}
|
||||
else if (h->ctx.op != HTTP_OP_POST)
|
||||
{
|
||||
fprintf(stderr, "%s: multipart/form-data only expected for POST\n",
|
||||
__func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *boundary = sep + 1;
|
||||
|
||||
|
@ -962,7 +1021,7 @@ static struct http_payload ctx_to_payload(const struct ctx *const c)
|
|||
};
|
||||
}
|
||||
|
||||
static int process_payload(struct http_ctx *const h, const char *const line)
|
||||
static int process_payload(struct http_ctx *const h)
|
||||
{
|
||||
struct ctx *const c = &h->ctx;
|
||||
const struct http_payload p = ctx_to_payload(c);
|
||||
|
@ -1008,25 +1067,28 @@ static int expect(struct http_ctx *const h, const char *const value)
|
|||
if (!strcmp(value, "100-continue"))
|
||||
{
|
||||
struct ctx *const c = &h->ctx;
|
||||
const struct http_payload p =
|
||||
|
||||
if (!c->has_length)
|
||||
{
|
||||
.u.post.expect_continue = true,
|
||||
.cookie =
|
||||
{
|
||||
.field = c->field,
|
||||
.value = c->value
|
||||
},
|
||||
fprintf(stderr, "%s: 100-continue without expected content\n",
|
||||
__func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
.op = c->op,
|
||||
.resource = c->resource
|
||||
};
|
||||
switch (c->op)
|
||||
{
|
||||
case HTTP_OP_POST:
|
||||
/* Fall through. */
|
||||
case HTTP_OP_PUT:
|
||||
c->expect_continue = true;
|
||||
break;
|
||||
|
||||
const int ret = h->cfg.payload(&p, &h->wctx.r, h->cfg.user);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return start_response(h);
|
||||
case HTTP_OP_GET:
|
||||
/* Fall through. */
|
||||
case HTTP_OP_HEAD:
|
||||
fprintf(stderr, "%s: unexpected op %d\n", __func__, c->op);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -1133,7 +1195,39 @@ static int check_length(struct http_ctx *const h)
|
|||
.value = c->value
|
||||
};
|
||||
|
||||
return h->cfg.length(c->post.len, &cookie, &h->wctx.r, h->cfg.user);
|
||||
return h->cfg.length(c->payload.len, &cookie, &h->wctx.r, h->cfg.user);
|
||||
}
|
||||
|
||||
static int process_expect(struct http_ctx *const h)
|
||||
{
|
||||
struct ctx *const c = &h->ctx;
|
||||
const int res = check_length(h);
|
||||
|
||||
if (res)
|
||||
{
|
||||
if (res < 0)
|
||||
{
|
||||
fprintf(stderr, "%s: check_length failed\n", __func__);
|
||||
return res;
|
||||
}
|
||||
|
||||
h->wctx.close = true;
|
||||
return start_response(h);
|
||||
}
|
||||
|
||||
struct http_payload p = ctx_to_payload(c);
|
||||
|
||||
p.expect_continue = true;
|
||||
|
||||
const int ret = h->cfg.payload(&p, &h->wctx.r, h->cfg.user);
|
||||
|
||||
h->wctx.op = c->op;
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
c->state = BODY_LINE;
|
||||
return start_response(h);
|
||||
}
|
||||
|
||||
static int header_cr_line(struct http_ctx *const h)
|
||||
|
@ -1148,12 +1242,14 @@ static int header_cr_line(struct http_ctx *const h)
|
|||
case HTTP_OP_GET:
|
||||
/* Fall through. */
|
||||
case HTTP_OP_HEAD:
|
||||
return process_payload(h, line);
|
||||
return process_payload(h);
|
||||
|
||||
case HTTP_OP_POST:
|
||||
{
|
||||
if (!c->post.len)
|
||||
return process_payload(h, line);
|
||||
if (!c->payload.len)
|
||||
return process_payload(h);
|
||||
else if (c->expect_continue)
|
||||
return process_expect(h);
|
||||
else if (c->boundary)
|
||||
{
|
||||
const int res = check_length(h);
|
||||
|
@ -1175,6 +1271,21 @@ static int header_cr_line(struct http_ctx *const h)
|
|||
c->state = BODY_LINE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
case HTTP_OP_PUT:
|
||||
if (!c->has_length)
|
||||
{
|
||||
fprintf(stderr, "%s: expected Content-Length header\n",
|
||||
__func__);
|
||||
return 1;
|
||||
}
|
||||
else if (!c->payload.len)
|
||||
return process_payload(h);
|
||||
else if (c->expect_continue)
|
||||
return process_expect(h);
|
||||
|
||||
c->state = BODY_LINE;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1472,7 +1583,7 @@ static int process_mf_line(struct http_ctx *const h)
|
|||
[MF_END_BOUNDARY_CR_LINE] = end_boundary_line
|
||||
};
|
||||
|
||||
h->ctx.post.read += strlen(h->line) + strlen("\r\n");
|
||||
h->ctx.payload.read += strlen(h->line) + strlen("\r\n");
|
||||
return state[h->ctx.u.mf.state](h);
|
||||
}
|
||||
|
||||
|
@ -1525,7 +1636,7 @@ static int read_mf_body_to_mem(struct http_ctx *const h, const void *const buf,
|
|||
memcpy(&h->line[m->written], buf, n);
|
||||
m->written += n;
|
||||
m->len += n;
|
||||
c->post.read += n;
|
||||
c->payload.read += n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1549,7 +1660,7 @@ static int read_mf_body_to_file(struct http_ctx *const h, const void *const buf,
|
|||
|
||||
m->written += res;
|
||||
m->len += res;
|
||||
c->post.read += res;
|
||||
c->payload.read += res;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1826,7 +1937,7 @@ static int read_multiform(struct http_ctx *const h, bool *const close)
|
|||
{
|
||||
/* Note: the larger the buffer below, the less CPU load. */
|
||||
char buf[sizeof h->line];
|
||||
struct post *const p = &h->ctx.post;
|
||||
struct payload *const p = &h->ctx.payload;
|
||||
const unsigned long long left = p->len - p->read;
|
||||
const size_t rem = left > sizeof buf ? sizeof buf : left;
|
||||
const int r = h->cfg.read(buf, rem, h->cfg.user);
|
||||
|
@ -1846,7 +1957,7 @@ static int read_body_to_mem(struct http_ctx *const h, bool *const close)
|
|||
return rw_error(r, close);
|
||||
|
||||
struct ctx *const c = &h->ctx;
|
||||
struct post *const p = &c->post;
|
||||
struct payload *const p = &c->payload;
|
||||
|
||||
if (p->read >= sizeof h->line - 1)
|
||||
{
|
||||
|
@ -1881,10 +1992,91 @@ static int read_body_to_mem(struct http_ctx *const h, bool *const close)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int read_put_body_to_file(struct http_ctx *const h,
|
||||
const void *const buf, const size_t n)
|
||||
{
|
||||
struct ctx *const c = &h->ctx;
|
||||
struct put *const put = &c->u.put;
|
||||
ssize_t res;
|
||||
|
||||
if (!put->tmpname && !(put->tmpname = get_tmp(h->cfg.tmpdir)))
|
||||
{
|
||||
fprintf(stderr, "%s: get_tmp failed\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
else if (put->fd < 0 && (put->fd = mkstemp(put->tmpname)) < 0)
|
||||
{
|
||||
fprintf(stderr, "%s: mkstemp(3): %s\n", __func__, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
else if ((res = pwrite(put->fd, buf, n, c->payload.read)) < 0)
|
||||
{
|
||||
fprintf(stderr, "%s: pwrite(2): %s\n", __func__, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
c->payload.read += res;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_to_file(struct http_ctx *const h, bool *const close)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
struct ctx *const c = &h->ctx;
|
||||
struct payload *const p = &c->payload;
|
||||
const unsigned long long left = p->len - p->read;
|
||||
const size_t rem = left > sizeof buf ? sizeof buf : left;
|
||||
const int r = h->cfg.read(buf, rem, h->cfg.user);
|
||||
|
||||
if (r <= 0)
|
||||
return rw_error(r, close);
|
||||
else if (read_put_body_to_file(h, buf, r))
|
||||
return -1;
|
||||
else if (p->read >= p->len)
|
||||
{
|
||||
const struct http_payload pl =
|
||||
{
|
||||
.cookie =
|
||||
{
|
||||
.field = c->field,
|
||||
.value = c->value
|
||||
},
|
||||
|
||||
.op = c->op,
|
||||
.resource = c->resource,
|
||||
.u.put =
|
||||
{
|
||||
.tmpname = c->u.put.tmpname
|
||||
}
|
||||
};
|
||||
|
||||
return send_payload(h, &pl);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_body(struct http_ctx *const h, bool *const close)
|
||||
{
|
||||
return h->ctx.boundary ? read_multiform(h, close)
|
||||
: read_body_to_mem(h, close);
|
||||
const struct ctx *const c = &h->ctx;
|
||||
|
||||
switch (c->op)
|
||||
{
|
||||
case HTTP_OP_POST:
|
||||
return c->boundary ? read_multiform(h, close)
|
||||
: read_body_to_mem(h, close);
|
||||
|
||||
case HTTP_OP_PUT:
|
||||
return read_to_file(h, close);
|
||||
|
||||
case HTTP_OP_GET:
|
||||
/* Fall through. */
|
||||
case HTTP_OP_HEAD:
|
||||
break;
|
||||
}
|
||||
|
||||
fprintf(stderr, "%s: unexpected op %d\n", __func__, c->op);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int process_line(struct http_ctx *const h)
|
||||
|
|
|
@ -16,7 +16,8 @@ struct http_payload
|
|||
{
|
||||
HTTP_OP_GET,
|
||||
HTTP_OP_POST,
|
||||
HTTP_OP_HEAD
|
||||
HTTP_OP_HEAD,
|
||||
HTTP_OP_PUT
|
||||
} op;
|
||||
|
||||
const char *resource;
|
||||
|
@ -30,7 +31,6 @@ struct http_payload
|
|||
{
|
||||
struct http_post
|
||||
{
|
||||
bool expect_continue;
|
||||
const char *data;
|
||||
size_t nfiles, npairs;
|
||||
|
||||
|
@ -44,6 +44,11 @@ struct http_payload
|
|||
const char *name, *tmpname, *filename;
|
||||
} *files;
|
||||
} post;
|
||||
|
||||
struct http_put
|
||||
{
|
||||
const char *tmpname;
|
||||
} put;
|
||||
} u;
|
||||
|
||||
const struct http_arg
|
||||
|
@ -53,6 +58,7 @@ struct http_payload
|
|||
|
||||
size_t n_args, n_headers;
|
||||
const struct http_header *headers;
|
||||
bool expect_continue;
|
||||
};
|
||||
|
||||
#define HTTP_STATUSES \
|
||||
|
|
Loading…
Reference in New Issue