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

tasks.c (7258B)



      1 /*
      2  * Copyright (c) 2014 Joris Vink <joris@coders.se>
      3  *
      4  * Permission to use, copy, modify, and distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 /*
     18  * In this example, we use the background tasks available in Kore
     19  * to fire off a POST to our /post_back page handler containing
     20  * the user argument that was passed to us in our GET request to /.
     21  *
     22  * This illustrates how Kore its background tasks in effect work and
     23  * how to operate on the channel in order to pass data back and forth.
     24  *
     25  * You need libcurl installed for this to build (including headers)
     26  *
     27  * Read README.md on how to build and run this example.
     28  */
     29 
     30 #include <curl/curl.h>
     31 
     32 #include <kore/kore.h>
     33 #include <kore/http.h>
     34 #include <kore/tasks.h>
     35 
     36 /* We need to allow some more syscalls on linux. */
     37 #if defined(__linux__)
     38 #include <kore/seccomp.h>
     39 
     40 KORE_SECCOMP_FILTER("tasks",
     41 	/* Allow sockets and libcurl to call connect. */
     42 	KORE_SYSCALL_ALLOW(bind),
     43 	KORE_SYSCALL_ALLOW(ioctl),
     44 	KORE_SYSCALL_ALLOW(connect),
     45 	KORE_SYSCALL_ALLOW(getsockopt),
     46 	KORE_SYSCALL_ALLOW(getsockname),
     47 	KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_INET),
     48 	KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_INET6),
     49 	KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_UNIX),
     50 	KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_NETLINK),
     51 
     52 	/* Other */
     53 	KORE_SYSCALL_ALLOW(ioctl),
     54 	KORE_SYSCALL_ALLOW(madvise),
     55 	KORE_SYSCALL_ALLOW(recvmsg),
     56 	KORE_SYSCALL_ALLOW(sendmmsg),
     57 	KORE_SYSCALL_ALLOW(getpeername),
     58 );
     59 #endif
     60 
     61 int		run_curl(struct kore_task *);
     62 int		post_back(struct http_request *);
     63 int		page_handler(struct http_request *);
     64 size_t		curl_write_cb(char *, size_t, size_t, void *);
     65 
     66 struct rstate {
     67 	struct kore_task	task;
     68 };
     69 
     70 int
     71 page_handler(struct http_request *req)
     72 {
     73 	u_int32_t	len;
     74 	struct rstate	*state;
     75 	char		*user, result[64];
     76 
     77 	/*
     78 	 * Lets check if a task has been created yet, this is important
     79 	 * as we only want to fire this off once and we will be called
     80 	 * again once it has been created.
     81 	 *
     82 	 * In this example, we'll store our state with our task in hdlr_extra.
     83 	 */
     84 	if (req->hdlr_extra == NULL) {
     85 		/* Grab the user argument */
     86 		http_populate_get(req);
     87 		if (!http_argument_get_string(req, "user", &user)) {
     88 			http_response(req, 500, "ERROR\n", 6);
     89 			return (KORE_RESULT_OK);
     90 		}
     91 
     92 		/*
     93 		 * Allocate rstate and bind it to the hdlr_extra field.
     94 		 * Kore automatically frees this when freeing the result.
     95 		 */
     96 		state = kore_malloc(sizeof(*state));
     97 		req->hdlr_extra = state;
     98 
     99 		/*
    100 		 * Create a new task that will execute the run_curl()
    101 		 * function and bind it to our request.
    102 		 *
    103 		 * Binding a task to a request means Kore will reschedule
    104 		 * the page handler for that request to refire after the
    105 		 * task has completed or when it writes on the task channel.
    106 		 */
    107 		kore_task_create(&state->task, run_curl);
    108 		kore_task_bind_request(&state->task, req);
    109 
    110 		/*
    111 		 * Start the task and write the user we received in our
    112 		 * GET request to its channel.
    113 		 */
    114 		kore_task_run(&state->task);
    115 		kore_task_channel_write(&state->task, user, strlen(user));
    116 
    117 		/*
    118 		 * Tell Kore to retry us later.
    119 		 */
    120 		return (KORE_RESULT_RETRY);
    121 	} else {
    122 		state = req->hdlr_extra;
    123 	}
    124 
    125 	/*
    126 	 * Our page handler is scheduled to be called when either the
    127 	 * task finishes or has written data onto the channel.
    128 	 *
    129 	 * In order to distinguish between the two we can inspect the
    130 	 * state of the task.
    131 	 */
    132 	if (kore_task_state(&state->task) != KORE_TASK_STATE_FINISHED) {
    133 		http_request_sleep(req);
    134 		return (KORE_RESULT_RETRY);
    135 	}
    136 
    137 	/*
    138 	 * Task is finished, check the result.
    139 	 */
    140 	if (kore_task_result(&state->task) != KORE_RESULT_OK) {
    141 		kore_task_destroy(&state->task);
    142 		http_response(req, 500, NULL, 0);
    143 		return (KORE_RESULT_OK);
    144 	}
    145 
    146 	/*
    147 	 * Lets read what our task has written to the channel.
    148 	 *
    149 	 * kore_task_channel_read() will return the amount of bytes
    150 	 * that it received for that read. If the returned bytes is
    151 	 * larger then the buffer you passed this is a sign of truncation
    152 	 * and should be treated carefully.
    153 	 */
    154 	len = kore_task_channel_read(&state->task, result, sizeof(result));
    155 	if (len > sizeof(result)) {
    156 		http_response(req, 500, NULL, 0);
    157 	} else {
    158 		http_response(req, 200, result, len);
    159 	}
    160 
    161 	/* We good, destroy the task. */
    162 	kore_task_destroy(&state->task);
    163 
    164 	return (KORE_RESULT_OK);
    165 }
    166 
    167 int
    168 post_back(struct http_request *req)
    169 {
    170 	char		*user;
    171 
    172 	if (req->method != HTTP_METHOD_POST) {
    173 		http_response(req, 500, NULL, 0);
    174 		return (KORE_RESULT_OK);
    175 	}
    176 
    177 	http_populate_post(req);
    178 	if (!http_argument_get_string(req, "user", &user)) {
    179 		http_response(req, 500, NULL, 0);
    180 		return (KORE_RESULT_OK);
    181 	}
    182 
    183 	/* Simply echo the supplied user argument back. */
    184 	http_response(req, 200, user, strlen(user));
    185 
    186 	return (KORE_RESULT_OK);
    187 }
    188 
    189 /*
    190  * This is the function that is executed by our task which is created
    191  * in the page_handler() callback.
    192  *
    193  * It sets up a CURL POST request to /post_back passing along the
    194  * user argument which it receives from its channel from page_handler().
    195  */
    196 int
    197 run_curl(struct kore_task *t)
    198 {
    199 	struct kore_buf		*b;
    200 	size_t			len;
    201 	CURLcode		res;
    202 	u_int8_t		*data;
    203 	CURL			*curl;
    204 	char			user[64], fields[128];
    205 
    206 	/*
    207 	 * Read the channel in order to obtain the user argument
    208 	 * that was written to it by page_handler().
    209 	 */
    210 	len = kore_task_channel_read(t, user, sizeof(user));
    211 	if (len > sizeof(user))
    212 		return (KORE_RESULT_ERROR);
    213 
    214 	if (!kore_snprintf(fields, sizeof(fields),
    215 	    NULL, "user=%.*s", len, user))
    216 		return (KORE_RESULT_ERROR);
    217 
    218 	if ((curl = curl_easy_init()) == NULL)
    219 		return (KORE_RESULT_ERROR);
    220 
    221 	b = kore_buf_alloc(128);
    222 
    223 	/* Do CURL magic. */
    224 	curl_easy_setopt(curl, CURLOPT_POST, 1);
    225 	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
    226 	curl_easy_setopt(curl, CURLOPT_WRITEDATA, b);
    227 	curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
    228 	curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
    229 	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, fields);
    230 	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
    231 #if !defined(KORE_NO_TLS)
    232 	curl_easy_setopt(curl, CURLOPT_URL, "https://127.0.0.1:8888/post_back");
    233 #else
    234 	curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:8888/post_back");
    235 #endif
    236 
    237 	res = curl_easy_perform(curl);
    238 	if (res != CURLE_OK) {
    239 		kore_buf_free(b);
    240 		curl_easy_cleanup(curl);
    241 		return (KORE_RESULT_ERROR);
    242 	}
    243 
    244 	/*
    245 	 * Grab the response from the CURL request and write the
    246 	 * result back to the task channel.
    247 	 */
    248 	data = kore_buf_release(b, &len);
    249 	kore_task_channel_write(t, data, len);
    250 	kore_free(data);
    251 
    252 	return (KORE_RESULT_OK);
    253 }
    254 
    255 size_t
    256 curl_write_cb(char *ptr, size_t size, size_t nmemb, void *udata)
    257 {
    258 	struct kore_buf		*b = udata;
    259 
    260 	kore_buf_append(b, ptr, size * nmemb);
    261 	return (size * nmemb);
    262 }