kore

An easy to use, scalable and secure web application framework for writing web APIs in C.
Commits | Files | Refs | README | LICENSE | git clone https://git.kore.io/kore.git

commit 1616320490ba3ac211bab167ff539d398f0408c4
parent c665b7d92648d5b839d40b198610aa05d11cceb8
Author: Joris Vink <joris@coders.se>
Date:   Sun, 10 Aug 2014 19:33:43 +0200

Add an html5 video streaming example

Diffstat:
examples/video_stream/.gitignore | 5+++++
examples/video_stream/README.md | 22++++++++++++++++++++++
examples/video_stream/assets/video.html | 18++++++++++++++++++
examples/video_stream/conf/video_stream.conf | 16++++++++++++++++
examples/video_stream/src/stream.c | 279+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
examples/video_stream/videos/placeholder | 1+
6 files changed, 341 insertions(+), 0 deletions(-)

diff --git a/examples/video_stream/.gitignore b/examples/video_stream/.gitignore @@ -0,0 +1,5 @@ +*.o +.objs +stream.so +assets.h +cert diff --git a/examples/video_stream/README.md b/examples/video_stream/README.md @@ -0,0 +1,22 @@ +A simple HTML5 video streaming service using Kore. + +Building: +``` + You must first place a test video inside the videos/ folder. I tested + this using Big Buck Bunny (ogg version) on Chrome. But any video that + can be played with HTML5 works. + + If you did not save your video as videos/video.ogg make sure you + update the assets/video.html file to point to the right video. + + When done, run a kore build. +``` + +Run: +``` + kore run +``` + +Visit the URI and you should see a video stream. + +Frontend parts from video.js: http://www.videojs.com/ diff --git a/examples/video_stream/assets/video.html b/examples/video_stream/assets/video.html @@ -0,0 +1,18 @@ +<!DOCTYPE> +<html> +<head> + <link href="https:////vjs.zencdn.net/4.7/video-js.css" rel="stylesheet"> + <script src="https:////vjs.zencdn.net/4.7/video.js"></script> + <title>Video stream over Kore</title> +</head> + +<body> + +<video class="video-js vjs-default-skin" width="640" + height="240" controls preload="auto" data-setup='{"example_option":true}'> + <source src="/video.ogg" type="video/ogg"> +Your browser does not support the video tag. +</video> + +</body> +</html> diff --git a/examples/video_stream/conf/video_stream.conf b/examples/video_stream/conf/video_stream.conf @@ -0,0 +1,16 @@ +# Placeholder configuration + +bind 127.0.0.1 8888 +load ./video_stream.so init + +spdy_idle_time 600 +http_keepalive_time 600 + +domain 127.0.0.1 { + certfile cert/server.crt + certkey cert/server.key + accesslog access.log + + static / serve_page + dynamic ^/[a-z]*.[a-z0-9]{3}$ video_stream +} diff --git a/examples/video_stream/src/stream.c b/examples/video_stream/src/stream.c @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2014 Joris Vink <joris@coders.se> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/queue.h> + +#include <fcntl.h> + +#include <kore/kore.h> +#include <kore/http.h> + +#include "assets.h" + +struct video { + int fd; + int ref; + off_t size; + char *path; + u_int8_t *data; + void *base; + + TAILQ_ENTRY(video) list; +}; + +void init(int); +int serve_page(struct http_request *); +int video_stream(struct http_request *); + +static void video_unmap(struct video *); +static int video_stream_finish(struct netbuf *); +static int video_mmap(struct http_request *, struct video *); +static int video_open(struct http_request *, struct video **); + +TAILQ_HEAD(, video) videos; + +void +init(int state) +{ + switch (state) { + case KORE_MODULE_LOAD: + TAILQ_INIT(&videos); + break; + case KORE_MODULE_UNLOAD: + fatal("cannot reload this module, i should fix this"); + break; + } +} + +int +serve_page(struct http_request *req) +{ + http_response_header(req, "content-type", "text/html"); + http_response_stream(req, 200, asset_video_html, + asset_len_video_html, NULL, NULL); + + return (KORE_RESULT_OK); +} + +int +video_stream(struct http_request *req) +{ + struct video *v; + off_t start, end; + int n, err, l, status; + char *header, *bytes, *range[3], rb[128], *ext, ctype[32]; + + if (!video_open(req, &v)) + return (KORE_RESULT_OK); + + if ((ext = strrchr(req->path, '.')) == NULL) { + http_response(req, 400, NULL, 0); + return (KORE_RESULT_OK); + } + + l = snprintf(ctype, sizeof(ctype), "video/%s", ext + 1); + if (l == -1 || (size_t)l >= sizeof(ctype)) { + http_response(req, 500, NULL, 0); + return (KORE_RESULT_OK); + } + + kore_log(LOG_NOTICE, "%p: opened %s (%s) for streaming (%ld ref:%d)", + req->owner, v->path, ctype, v->size, v->ref); + + if (http_request_header(req, "range", &header)) { + if ((bytes = strchr(header, '=')) == NULL) { + http_response(req, 416, NULL, 0); + return (KORE_RESULT_OK); + } + + bytes++; + n = kore_split_string(bytes, "-", range, 2); + if (n == 0) { + http_response(req, 416, NULL, 0); + return (KORE_RESULT_OK); + } + + if (n >= 1) { + start = kore_strtonum64(range[0], 10, &err); + if (err != KORE_RESULT_OK) { + http_response(req, 416, NULL, 0); + return (KORE_RESULT_OK); + } + } + + if (n > 1) { + end = kore_strtonum64(range[1], 10, &err); + if (err != KORE_RESULT_OK) { + http_response(req, 416, NULL, 0); + return (KORE_RESULT_OK); + } + } else { + end = 0; + } + + if (end == 0) + end = v->size; + + if (start > end || start > v->size || end > v->size) { + http_response(req, 416, NULL, 0); + return (KORE_RESULT_OK); + } + + status = 206; + l = snprintf(rb, sizeof(rb), "bytes %ld-%ld/%ld", + start, end - 1, v->size); + if (l == -1 || (size_t)l >= sizeof(rb)) { + http_response(req, 500, NULL, 0); + return (KORE_RESULT_OK); + } + + kore_log(LOG_NOTICE, "%p: %s sending: %ld-%ld/%ld", + req->owner, v->path, start, end - 1, v->size); + http_response_header(req, "content-range", rb); + } else { + start = 0; + status = 200; + end = v->size; + } + + http_response_header(req, "content-type", ctype); + http_response_header(req, "accept-ranges", "bytes"); + http_response_stream(req, status, v->data + start, + end - start, video_stream_finish, v); + + return (KORE_RESULT_OK); +} + +static int +video_open(struct http_request *req, struct video **out) +{ + int l; + struct stat st; + struct video *v; + char fpath[MAXPATHLEN]; + + l = snprintf(fpath, sizeof(fpath), "videos%s", req->path); + if (l == -1 || (size_t)l >= sizeof(fpath)) + return (KORE_RESULT_ERROR); + + TAILQ_FOREACH(v, &videos, list) { + if (!strcmp(v->path, fpath)) { + if (video_mmap(req, v)) { + *out = v; + return (KORE_RESULT_OK); + } + + close(v->fd); + TAILQ_REMOVE(&videos, v, list); + kore_mem_free(v->path); + kore_mem_free(v); + + http_response(req, 500, NULL, 0); + return (KORE_RESULT_ERROR); + } + } + + v = kore_malloc(sizeof(*v)); + v->ref = 0; + v->base = NULL; + v->data = NULL; + v->path = kore_strdup(fpath); + + if ((v->fd = open(fpath, O_RDONLY)) == -1) { + kore_mem_free(v->path); + kore_mem_free(v); + + if (errno == ENOENT) + http_response(req, 404, NULL, 0); + else + http_response(req, 500, NULL, 0); + + return (KORE_RESULT_ERROR); + } + + if (fstat(v->fd, &st) == -1) { + close(v->fd); + kore_mem_free(v->path); + kore_mem_free(v); + + http_response(req, 500, NULL, 0); + return (KORE_RESULT_ERROR); + } + + v->size = st.st_size; + if (!video_mmap(req, v)) { + close(v->fd); + kore_mem_free(v->path); + kore_mem_free(v); + + http_response(req, 500, NULL, 0); + return (KORE_RESULT_ERROR); + } + + *out = v; + TAILQ_INSERT_TAIL(&videos, v, list); + + return (KORE_RESULT_OK); +} + +static int +video_mmap(struct http_request *req, struct video *v) +{ + if (v->base != NULL && v->data != NULL) { + v->ref++; + return (KORE_RESULT_OK); + } + + v->base = mmap(NULL, v->size, PROT_READ, MAP_SHARED, v->fd, 0); + if (v->base == MAP_FAILED) + return (KORE_RESULT_ERROR); + + v->ref++; + v->data = v->base; + + return (KORE_RESULT_OK); +} + +static int +video_stream_finish(struct netbuf *nb) +{ + struct video *v = nb->extra; + + v->ref--; + kore_log(LOG_NOTICE, "%p: video stream %s done (%d/%d ref:%d)", + nb->owner, v->path, nb->s_off, nb->b_len, v->ref); + + if (v->ref == 0) + video_unmap(v); + + return (KORE_RESULT_OK); +} + +static void +video_unmap(struct video *v) +{ + if (munmap(v->base, v->size) == -1) { + kore_log(LOG_ERR, "munmap(%s): %s", v->path, errno_s); + } else { + v->base = NULL; + v->data = NULL; + kore_log(LOG_NOTICE, + "unmapped %s for streaming, no refs left", v->path); + } +} diff --git a/examples/video_stream/videos/placeholder b/examples/video_stream/videos/placeholder @@ -0,0 +1 @@ +Drop your HTML5 videos in here