view libsci/apic.c @ 35:084dee2bef50

man: updates
author David Demelier <markand@malikania.fr>
date Thu, 04 Aug 2022 18:01:29 +0200
parents 1d0ddf9e6efd
children b474f0985e39
line wrap: on
line source

/*
 * apic.c -- synchronous HTTP request
 *
 * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or 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 <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#include <curl/curl.h>

#include "apic.h"
#include "util.h"

struct curlpack {
	CURL *curl;
	CURLcode code;
	struct curl_slist *headers;
};

struct apiconf apiconf = {
	.baseurl = "http://127.0.0.1"
};

static size_t
writer(char *in, size_t n, size_t w, FILE *fp)
{
	if (fwrite(in, n, w, fp) != w)
		return 0;

	return w;
}

static inline char *
create_url(const char *fmt, va_list args)
{
	static _Thread_local char ret[1024];
	char page[128];
	va_list ap;

	ret[0] = 0;
	va_copy(ap, args);
	vsnprintf(page, sizeof (page), fmt, ap);
	va_end(ap);

	snprintf(ret, sizeof (ret), "%s/%s", apiconf.baseurl, page);

	return ret;
}

static inline FILE *
create_file(char **buf, size_t *bufsz)
{
	FILE *fp;

	*buf = NULL;
	*bufsz = 0;

	if (!(fp = open_memstream(buf, bufsz)))
		util_die("open_memstream: %s\n", strerror(errno));

	return fp;
}

static struct curlpack
create_curl(FILE *fp, const char *body, const char *url)
{
	struct curlpack pack = {0};

	pack.headers = curl_slist_append(pack.headers, "Content-Type: application/json");
	pack.curl = curl_easy_init();
	curl_easy_setopt(pack.curl, CURLOPT_HTTPHEADER, pack.headers);
	curl_easy_setopt(pack.curl, CURLOPT_URL, url);
	curl_easy_setopt(pack.curl, CURLOPT_FOLLOWLOCATION, 1L);
	curl_easy_setopt(pack.curl, CURLOPT_TIMEOUT, 3L);
	curl_easy_setopt(pack.curl, CURLOPT_WRITEFUNCTION, writer);
	curl_easy_setopt(pack.curl, CURLOPT_WRITEDATA, fp);
	curl_easy_setopt(pack.curl, CURLOPT_NOSIGNAL, 1L);

	/* Assume POST request if there is a body. */
	if (body) {
		curl_easy_setopt(pack.curl, CURLOPT_POSTFIELDS, body);
		curl_easy_setopt(pack.curl, CURLOPT_POSTFIELDSIZE, strlen(body));
	}

	pack.code = curl_easy_perform(pack.curl);

	return pack;
}

static json_t *
perform(struct apic *req, const char *body, const char *fmt, va_list ap)
{
	FILE *fp;
	char *response, *url;
	size_t responsesz;
	json_t *doc = NULL;
	json_error_t error;
	struct curlpack curl;

	memset(req, 0, sizeof (*req));

	url  = create_url(fmt, ap);
	fp   = create_file(&response, &responsesz);
	curl = create_curl(fp, body, url);

	/* Perform that request now. */
	fclose(fp);

	if (curl.code != CURLE_OK)
		snprintf(req->error, sizeof (req->error), "%s", curl_easy_strerror(curl.code));
	else {
		curl_easy_getinfo(curl.curl, CURLINFO_RESPONSE_CODE, &req->status);

		if (req->status != 200)
			snprintf(req->error, sizeof (req->error), "HTTP returned %ld", req->status);
		if (response[0] && !(doc = json_loads(response, 0, &error)))
			snprintf(req->error, sizeof (req->error), "JSON parse error: %s", error.text);
	}

	curl_easy_cleanup(curl.curl);
	curl_slist_free_all(curl.headers);

	free(response);

	return doc;
}

static json_t *
get(struct apic *req, const char *fmt, ...)
{
	va_list ap;
	json_t *ret;

	va_start(ap, fmt);
	ret = perform(req, NULL, fmt, ap);
	va_end(ap);

	if (!ret || (!json_is_object(ret) && !json_is_array(ret)))
		snprintf(req->error, sizeof (req->error), "invalid JSON document received");

	return ret;
}

static int
create(struct apic *req, json_t *doc, const char *fmt, ...)
{
	va_list ap;
	json_t *ret;
	char *body;

	memset(req, 0, sizeof (*req));
	body = util_json_dump(doc);

	va_start(ap, fmt);
	ret = perform(req, body, fmt, ap);
	va_end(ap);

	/* TODO: update id. */
	(void)ret;

	free(body);
	json_decref(ret);

	return 0;
}

json_t *
apic_get(struct apic *req, const char *fmt, ...)
{
	assert(req);
	assert(fmt);

	va_list ap;
	json_t *ret;

	va_start(ap, fmt);
	ret = perform(req, NULL, fmt, ap);
	va_end(ap);

	return ret;
}

json_t *
apic_post(struct apic *req, const json_t *doc, const char *fmt, ...)
{
	assert(req);
	assert(fmt);

	va_list ap;
	json_t *ret;
	char *body;

	if (!(body = json_dumps(doc, JSON_COMPACT)))
		util_die("%s", strerror(ENOMEM));

	va_start(ap, fmt);
	ret = perform(req, body, fmt, ap);
	va_end(ap);

	free(body);

	return ret;
}

int
apic_job_add(struct apic *req, json_t *job)
{
	assert(req);
	assert(job);

	return create(req, job, "api/v1/jobs");
}

json_t *
apic_job_todo(struct apic *req, const char *worker_name)
{
	assert(req);
	assert(worker_name);

	return get(req, "api/v1/todo/%s", worker_name);
}

int
apic_jobresult_add(struct apic *req, json_t *res)
{
	assert(req);
	assert(res);

	return create(req, res, "api/v1/jobresults");
}

int
apic_project_save(struct apic *req, json_t *p)
{
	assert(req);
	assert(p);

	return create(req, p, "api/v1/projects");
}

json_t *
apic_project_list(struct apic *req)
{
	assert(req);

	return get(req, "api/v1/projects");
}

json_t *
apic_project_find(struct apic *req, const char *name)
{
	assert(req);
	assert(name);

	return get(req, "api/v1/projects/%s", name);
}

int
apic_worker_save(struct apic *req, json_t *wk)
{
	assert(req);
	assert(wk);

	return create(req, wk, "api/v1/workers");
}

json_t *
apic_worker_list(struct apic *req)
{
	assert(req);

	return get(req, "api/v1/workers");
}

json_t *
apic_worker_find(struct apic *req, const char *name)
{
	assert(req);
	assert(name);

	return get(req, "api/v1/workers/%s", name);
}