view libsci/apic.c @ 78:26bbca3765dd

misc: update API timeout
author David Demelier <markand@malikania.fr>
date Tue, 31 Jan 2023 22:01:36 +0100
parents 308aa1086702
children 71cd8447e3a4
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 context {
	/* Output */
	FILE *fp;
	char *out;
	size_t outsz;

	/* HTTP requester. */
	char url[1024];
	char keyhdr[128];
	CURL *curl;
	CURLcode code;
	struct curl_slist *headers;

	/* JSON stuff. */
	json_t *doc;
	json_error_t error;
};

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 void
init_url(struct context *ctx, const char *fmt, va_list ap)
{
	char page[128];

	vsnprintf(page, sizeof (page), fmt, ap);
	snprintf(ctx->url, sizeof (ctx->url), "%s/%s", apiconf.baseurl, page);
}

static inline void
init_output(struct context *ctx)
{
	ctx->fp = util_open_memstream(&ctx->out, &ctx->outsz);
}

static void
init_curl(struct context *ctx, const char *body)
{
	/* Create API key string. */
	snprintf(ctx->keyhdr, sizeof (ctx->keyhdr), "X-Api-Key: %s", apiconf.key);

	ctx->headers = curl_slist_append(ctx->headers, "Content-Type: application/json");
	ctx->headers = curl_slist_append(ctx->headers, ctx->keyhdr);

	ctx->curl = curl_easy_init();
	curl_easy_setopt(ctx->curl, CURLOPT_HTTPHEADER, ctx->headers);
	curl_easy_setopt(ctx->curl, CURLOPT_URL, ctx->url);
	curl_easy_setopt(ctx->curl, CURLOPT_FOLLOWLOCATION, 1L);
	curl_easy_setopt(ctx->curl, CURLOPT_TIMEOUT, 30L);
	curl_easy_setopt(ctx->curl, CURLOPT_WRITEFUNCTION, writer);
	curl_easy_setopt(ctx->curl, CURLOPT_WRITEDATA, ctx->fp);
	curl_easy_setopt(ctx->curl, CURLOPT_NOSIGNAL, 1L);

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

static void
invoke(struct apic *req, struct context *ctx)
{
	ctx->code = curl_easy_perform(ctx->curl);

	/* Close file descriptor to get its output. */
	fclose(ctx->fp);
	ctx->fp = NULL;

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

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

static void
finish(struct context *ctx)
{
	if (ctx->fp)
		fclose(ctx->fp);

	free(ctx->out);

	if (ctx->curl)
		curl_easy_cleanup(ctx->curl);
	if (ctx->headers)
		curl_slist_free_all(ctx->headers);
}

static json_t *
perform(struct apic *req, const char *body, const char *fmt, va_list ap)
{
	struct context ctx = {0};

	init_url(&ctx, fmt, ap);
	init_output(&ctx);
	init_curl(&ctx, body);

	/* Perform that request now to obtain data. */
	invoke(req, &ctx);
	finish(&ctx);

	return ctx.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);

	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 req->error[0] ? -1 : 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);
}