naett.c

Tiny cross-platform HTTP / HTTPS client library in C.
git clone git://git.finwo.net/lib/naett.c
Log | Files | Refs | README | LICENSE

commit c4b61b00173bb29c7a6d90e695dd3ebf7b66c0f8
Author: Erik Agsjö <erik.agsjo@gmail.com>
Date:   Wed, 10 Nov 2021 09:29:05 +0100

First!

Diffstat:
AMakefile | 16++++++++++++++++
Amain.c | 10++++++++++
Anaett.c | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anaett.h | 28++++++++++++++++++++++++++++
Anaett_internal.h | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anaett_objc.h | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anaett_osx.c | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 410 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,16 @@ +CFLAGS = -g +#CFLAGS = -O2 + +ifeq ($(OS),Windows_NT) + +else + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Darwin) + LDFLAGS = -framework Cocoa + else ifeq ($(UNAME_S),Linux) + #LDFLAGS = -s -lGLU -lGL -lX11 + endif +endif + +naett: main.c naett.c naett_osx.c + gcc $^ -o $@ $(CFLAGS) $(LDFLAGS) diff --git a/main.c b/main.c @@ -0,0 +1,10 @@ +#include "naett.h" +#include <unistd.h> + +int main(int argc, char** argv) { + naettReq* req = naettRequest("http://www.dn.se", naettMethod("GET"), naettHeader("content-type", "application/json")); + naettRes* res = naettMake(req); + while (!naettComplete(res)) { + usleep(100*1000); + } +} diff --git a/naett.c b/naett.c @@ -0,0 +1,158 @@ +#include "naett_internal.h" +#include <stdarg.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> + +typedef struct InternalOption* InternalOptionPtr; +typedef void (*OptionSetter)(InternalOptionPtr option, InternalRequest* req); + +typedef struct InternalOption { + OptionSetter setter; + int offset; + union { + int integer; + const char* string; + struct { + const char* key; + const char* value; + } kv; + void* ptr; + Buffer buffer; + }; +} InternalOption; + +void stringSetter(InternalOptionPtr option, InternalRequest* req) { + char* stringCopy = strdup(option->string); + char* opaque = (char*) &req->options; + char** stringField = (char**) (opaque + option->offset); + if (*stringField) { + free(*stringField); + } + *stringField = stringCopy; +} + +void intSetter(InternalOptionPtr option, InternalRequest* req) { + char* opaque = (char*) &req->options; + int* intField = (int*) (opaque + option->offset); + *intField = option->integer; +} + +void ptrSetter(InternalOptionPtr option, InternalRequest* req) { + char* opaque = (char*) &req->options; + void** ptrField = (void**) (opaque + option->offset); + *ptrField = option->ptr; +} + +void kvSetter(InternalOptionPtr option, InternalRequest* req) { + char* opaque = (char*) &req->options; + KVLink** kvField = (KVLink**) (opaque + option->offset); + + naettAlloc(KVLink, newNode); + newNode->key = strdup(option->kv.key); + newNode->value = strdup(option->kv.value); + newNode->next = *kvField; + *kvField = newNode; +} + +void bufferSetter(InternalOptionPtr option, InternalRequest* req) { + char* opaque = (char*) &req->options; + Buffer* bufferField = (Buffer*) (opaque + option->offset); + + bufferField->data = option->buffer.data; + bufferField->size = option->buffer.size; +} + +void initRequest(InternalRequest* req, const char* url) { + req->options.method = strdup("GET"); + req->url = strdup(url); +} + +// Public API + +naettOption* naettMethod(const char* method) { + naettAlloc(InternalOption, option); + option->string = method; + option->offset = offsetof(RequestOptions, method); + option->setter = stringSetter; + return (naettOption*) option; +} + +naettOption* naettHeader(const char* name, const char* value) { + naettAlloc(InternalOption, option); + option->kv.key = name; + option->kv.value = value; + option->offset = offsetof(RequestOptions, headers); + option->setter = kvSetter; + return (naettOption*) option; +} + +naettOption* naettTimeout(int timeoutMS) { + naettAlloc(InternalOption, option); + option->integer = timeoutMS; + option->offset = offsetof(RequestOptions, timeoutMS); + option->setter = intSetter; + return (naettOption*) option; +} + +naettOption* naettBody(const char* body, int size) { + naettAlloc(InternalOption, option); + option->buffer.data = (void*) body; + option->buffer.size = size; + option->offset = offsetof(RequestOptions, body); + option->setter = bufferSetter; + return (naettOption*) option; +} + +naettOption* naettBodyReader(naettReadFunc reader) { + naettAlloc(InternalOption, option); + option->ptr = reader; + option->offset = offsetof(RequestOptions, bodyReader); + option->setter = ptrSetter; + return (naettOption*) option; +} + +naettOption* naettBodyWriter(naettWriteFunc writer) { + naettAlloc(InternalOption, option); + option->ptr = writer; + option->offset = offsetof(RequestOptions, bodyWriter); + option->setter = ptrSetter; + return (naettOption*) option; +} + +naettReq* naettRequest_va(const char* url, int numArgs, ...) { + va_list args; + InternalOption* option; + naettAlloc(InternalRequest, req); + initRequest(req, url); + + va_start(args, numArgs); + for (int i = 0; i < numArgs; i++) { + option = va_arg(args, InternalOption*); + option->setter(option, req); + free(option); + } + va_end(args); + + naettPlatformInitRequest(req); + return (naettReq*) req; +} + +naettReq* naettRequestWithOptions(const char* url, int numOptions, const naettOption** options) { + naettAlloc(InternalRequest, req); + initRequest(req, url); + + for (int i = 0; i < numOptions; i++) { + InternalOption* option = (InternalOption*) options[i]; + option->setter(option, req); + free(option); + } + + naettPlatformInitRequest(req); + return (naettReq*) req; +} + +int naettComplete(const naettRes* response) { + InternalResponse* res = (InternalResponse*) response; + return res->complete; +} diff --git a/naett.h b/naett.h @@ -0,0 +1,28 @@ +#ifndef LIBNAETT_H +#define LIBNAETT_H + +typedef struct naettReq naettReq; +typedef struct naettRes naettRes; +typedef struct naettOption naettOption; +typedef int (*naettReadFunc)(char* dest, int bufferSize); +typedef int (*naettWriteFunc)(const char* source, int bytes); + +naettOption* naettMethod(const char* method); +naettOption* naettHeader(const char* name, const char* value); +naettOption* naettBody(const char* body, int size); +naettOption* naettBodyReader(naettReadFunc reader); +naettOption* naettBodyWriter(naettWriteFunc writer); +naettOption* naettTimeout(int milliSeconds); + +#define naettRequest(url, ...) naettRequest_va(url, countOptions(__VA_ARGS__), ##__VA_ARGS__) +naettReq* naettRequestWithOptions(const char* url, int numOptions, const naettOption** options); +void naettFreeRequest(naettReq* request); +naettRes* naettMake(naettReq* request); + +int naettComplete(const naettRes* response); +void naettClose(naettRes* response); + +naettReq* naettRequest_va(const char* url, int numOptions, ...); +#define countOptions(...) ((sizeof((void*[]){ __VA_ARGS__ }) / sizeof(void*))) + +#endif // LIBNAETT_H diff --git a/naett_internal.h b/naett_internal.h @@ -0,0 +1,65 @@ +#ifndef NAETT_INTERNAL_H +#define NAETT_INTERNAL_H + +#include "naett.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#define __WINDOWS__ 1 +#endif + +#if __linux__ && !__ANDROID__ +#define __LINUX__ 1 +#endif + +#ifdef __APPLE__ +#include "TargetConditionals.h" +#include <objc/objc.h> +#if TARGET_OS_IPHONE +#define __IOS__ 1 +#else +#define __MACOS__ 1 +#endif +#endif + +#define naettAlloc(TYPE, VAR) TYPE* VAR = (TYPE*)calloc(1, sizeof(TYPE)) + +typedef struct KVLink { + const char* key; + const char* value; + struct KVLink* next; +} KVLink; + +typedef struct Buffer { + void* data; + int size; +} Buffer; + +typedef struct { + const char* method; + int timeoutMS; + naettReadFunc bodyReader; + naettWriteFunc bodyWriter; + KVLink* headers; + Buffer body; +} RequestOptions; + +typedef struct { + RequestOptions options; + const char* url; +#if __APPLE__ + id urlRequest; +#endif +} InternalRequest; + +typedef struct { + InternalRequest* request; + int complete; + int code; + Buffer body; +} InternalResponse; + +void naettPlatformInitRequest(InternalRequest* req); + +#endif // NAETT_INTERNAL_H diff --git a/naett_objc.h b/naett_objc.h @@ -0,0 +1,60 @@ +#ifndef NAETT_OBJC_H +#define NAETT_OBJC_H + +#if defined(__IOS__) || defined (__MACOS__) +#include <assert.h> +#include <math.h> + +#include <objc/NSObjCRuntime.h> +#include <objc/message.h> +#include <objc/objc.h> +#include <objc/runtime.h> + +#if defined(__OBJC__) && __has_feature(objc_arc) +#error "ARC is not supported" +#endif + +// ABI is a bit different between platforms +#ifdef __arm64__ +#define abi_objc_msgSend_stret objc_msgSend +#else +#define abi_objc_msgSend_stret objc_msgSend_stret +#endif +#ifdef __i386__ +#define abi_objc_msgSend_fpret objc_msgSend_fpret +#else +#define abi_objc_msgSend_fpret objc_msgSend +#endif + +#define objc_msgSendSuper_t(RET, ...) ((RET(*)(struct objc_super*, SEL, ##__VA_ARGS__))objc_msgSendSuper) +#define objc_msgSend_t(RET, ...) ((RET(*)(id, SEL, ##__VA_ARGS__))objc_msgSend) +#define objc_msgSend_stret_t(RET, ...) ((RET(*)(id, SEL, ##__VA_ARGS__))abi_objc_msgSend_stret) +#define objc_msgSend_id objc_msgSend_t(id) +#define objc_msgSend_void objc_msgSend_t(void) +#define objc_msgSend_void_id objc_msgSend_t(void, id) +#define objc_msgSend_void_bool objc_msgSend_t(void, bool) + +#define sel(NAME) sel_registerName(NAME) +#define class(NAME) ((id)objc_getClass(NAME)) +#define makeClass(NAME, SUPER) \ + objc_allocateClassPair((Class)objc_getClass(SUPER), NAME, 0) + +// Check here to get the signature right: https://nshipster.com/type-encodings/ +#define addMethod(CLASS, NAME, IMPL, SIGNATURE) \ + if (!class_addMethod(CLASS, sel(NAME), (IMP) (IMPL), (SIGNATURE))) assert(false) + +#define addIvar(CLASS, NAME, SIZE, SIGNATURE) \ + if (!class_addIvar(CLASS, NAME, SIZE, rint(log2(SIZE)), SIGNATURE)) assert(false) + +#define objc_alloc(CLASS) objc_msgSend_id(class(CLASS), sel("alloc")) + +#if __LP64__ || NS_BUILD_32_LIKE_64 +#define NSIntegerEncoding "q" +#define NSUIntegerEncoding "L" +#else +#define NSIntegerEncoding "i" +#define NSUIntegerEncoding "I" +#endif + +#endif // defined(__IOS__) || defined (__MACOS__) +#endif // NAETT_OBJC_H diff --git a/naett_osx.c b/naett_osx.c @@ -0,0 +1,73 @@ +#include "naett_internal.h" + +#ifdef __MACOS__ + +#include "naett_objc.h" +#include <stdlib.h> + +void naettPlatformInitRequest(InternalRequest* req) { + id urlString = objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), req->url); + id url = objc_msgSend_t(id, id)(class("NSURL"), sel("URLWithString:"), urlString); + id request = objc_msgSend_t(id, id)(class("NSMutableURLRequest"), sel("requestWithURL:"), url); + objc_msgSend_t(void, double)(request, sel("setTimeoutInterval:"), (double)(req->options.timeoutMS) / 1000.0); + id methodString = + objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), req->options.method); + objc_msgSend_t(void, id)(request, sel("setHTTPMethod:"), methodString); + + KVLink* header = req->options.headers; + while (header != NULL) { + id name = objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), header->key); + id value = objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), header->value); + objc_msgSend_t(void, id, id)(request, sel("setValue:forHTTPHeaderField:"), name, value); + header = header->next; + } + + if (req->options.body.data) { + Buffer* body = &req->options.body; + + id bodyData = objc_msgSend_t(id, void*, NSUInteger, BOOL)( + class("NSData"), sel("dataWithBytesNoCopy:length:freeWhenDone:"), body->data, body->size, NO); + objc_msgSend_t(void, id)(request, sel("setHTTPBody:"), bodyData); + } + + req->urlRequest = request; +} + +void didReceiveData(id self, SEL _sel, id session, id dataTask, id data) { + InternalResponse* res = NULL; + object_getInstanceVariable(self, "response", (void**)&res); +} + +void didComplete(id self, SEL _sel, id session, id dataTask, id error) { + InternalResponse* res = NULL; + object_getInstanceVariable(self, "response", (void**)&res); + res->complete = 1; +} + +naettRes* naettMake(naettReq* request) { + InternalRequest* req = (InternalRequest*)request; + + Class TaskDelegateClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "TaskDelegate", 0); + addMethod(TaskDelegateClass, "URLSession:dataTask:didReceiveData:", didReceiveData, "v@:@@@"); + addMethod(TaskDelegateClass, "URLSession:task:didCompleteWithError:", didComplete, "v@:@@@"); + addIvar(TaskDelegateClass, "response", sizeof(void*), "^v"); + + id config = objc_msgSend_id(class("NSURLSessionConfiguration"), sel("ephemeralSessionConfiguration")); + + id delegate = objc_msgSend_id((id)TaskDelegateClass, sel("alloc")); + delegate = objc_msgSend_id(delegate, sel("init")); + + id session = objc_msgSend_t(id, id, id, id)( + class("NSURLSession"), sel("sessionWithConfiguration:delegate:delegateQueue:"), config, delegate, nil); + id task = objc_msgSend_t(id, id)(session, sel("dataTaskWithRequest:"), req->urlRequest); + + naettAlloc(InternalResponse, response); + response->request = req; + object_setInstanceVariable(delegate, "response", (void*)response); + + objc_msgSend_void(task, sel("resume")); + + return (naettRes*) response; +} + +#endif // __MACOS__