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 4a29a444306dedb59eab2b0510a6bc2e66520c7b
parent ecca78319fea82a868e9ef745d8c13168b2e8a29
Author: Erik Agsjö <erik.agsjo@gmail.com>
Date:   Wed, 10 Nov 2021 23:42:30 +0100

Amalgamation

Diffstat:
DMakefile | 16----------------
Aexample/Makefile | 16++++++++++++++++
Rmain.c -> example/main.c | 0
Mnaett.c | 268++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Dnaett_internal.h | 75---------------------------------------------------------------------------
Asrc/Makefile | 4++++
Asrc/amalgam.h | 3+++
Asrc/bundle.sh | 37+++++++++++++++++++++++++++++++++++++
Cnaett.c -> src/naett_core.c | 0
Asrc/naett_internal.h | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rnaett_objc.h -> src/naett_objc.h | 0
Rnaett_osx.c -> src/naett_osx.c | 0
12 files changed, 400 insertions(+), 92 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,16 +0,0 @@ -CFLAGS = -g -Wall -pedantic -Wno-gnu-zero-variadic-macro-arguments -#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/example/Makefile b/example/Makefile @@ -0,0 +1,16 @@ +CFLAGS = -I.. -g -Wall -pedantic -Wno-gnu-zero-variadic-macro-arguments +#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 + gcc $^ -o $@ $(CFLAGS) $(LDFLAGS) diff --git a/main.c b/example/main.c diff --git a/naett.c b/naett.c @@ -1,4 +1,82 @@ -#include "naett_internal.h" +// Inlined amalgam.h: // +#include "naett.h" +// Inlined naett_core.c: // +// Inlined naett_internal.h: // +#ifndef NAETT_INTERNAL_H +#define NAETT_INTERNAL_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; + int capacity; +} Buffer; + +typedef struct { + const char* method; + int timeoutMS; + naettReadFunc bodyReader; + void* bodyReaderData; + naettWriteFunc bodyWriter; + void* bodyWriterData; + 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; + KVLink* headers; + Buffer body; +#if __APPLE__ + id session; +#endif +} InternalResponse; + +void naettPlatformInitRequest(InternalRequest* req); +void naettPlatformMakeRequest(InternalRequest* req, InternalResponse* res); +void naettPlatformFreeRequest(InternalRequest* req); +void naettPlatformCloseResponse(InternalResponse* res); + +#endif // NAETT_INTERNAL_H +// End of inlined naett_internal.h // + #include <stdarg.h> #include <stdlib.h> #include <stddef.h> @@ -263,3 +341,191 @@ void naettClose(naettRes* response) { naettPlatformCloseResponse(res); free(response); } +// End of inlined naett_core.c // + +// Inlined naett_osx.c: // +//#include "naett_internal.h" + +#ifdef __MACOS__ + +// Inlined naett_objc.h: // +#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")) +#define autorelease(OBJ) objc_msgSend_void(OBJ, sel("autorelease")) +#define retain(OBJ) objc_msgSend_void(OBJ, sel("retain")) +#define release(OBJ) objc_msgSend_void(OBJ, sel("release")) + +#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 +// End of inlined naett_objc.h // + +#include <stdlib.h> +#include <string.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); + release(urlString); + + id request = objc_msgSend_t(id, id)(class("NSMutableURLRequest"), sel("requestWithURL:"), url); + release(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); + release(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); + release(name); + release(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); + release(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); + + if (res->headers == NULL) { + id response = objc_msgSend_t(id)(dataTask, sel("response")); + id allHeaders = objc_msgSend_t(id)(response, sel("allHeaderFields")); + + NSUInteger headerCount = objc_msgSend_t(NSUInteger)(allHeaders, sel("count")); + id headerNames[headerCount]; + id headerValues[headerCount]; + + objc_msgSend_t(NSInteger, id*, id*, NSUInteger)( + allHeaders, sel("getObjects:andKeys:count:"), headerValues, headerNames, headerCount); + for (int i = 0; i < headerCount; i++) { + naettAlloc(KVLink, node); + node->key = strdup(objc_msgSend_t(const char*)(headerNames[i], sel("UTF8String"))); + node->value = strdup(objc_msgSend_t(const char*)(headerValues[i], sel("UTF8String"))); + node->next = res->headers; + res->headers = node; + } + } + + const void* bytes = objc_msgSend_t(const void*)(data, sel("bytes")); + NSUInteger length = objc_msgSend_t(NSUInteger)(data, sel("length")); + res->request->options.bodyWriter(bytes, length, res->request->options.bodyWriterData); +} + +static void didComplete(id self, SEL _sel, id session, id dataTask, id error) { + InternalResponse* res = NULL; + object_getInstanceVariable(self, "response", (void**)&res); + res->complete = 1; +} + +static id createDelegate() { + Class TaskDelegateClass = nil; + + if (!TaskDelegateClass) { + TaskDelegateClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "naettTaskDelegate", 0); + addMethod(TaskDelegateClass, "URLSession:dataTask:didReceiveData:", didReceiveData, "v@:@@@"); + addMethod(TaskDelegateClass, "URLSession:task:didCompleteWithError:", didComplete, "v@:@@@"); + addIvar(TaskDelegateClass, "response", sizeof(void*), "^v"); + } + + id delegate = objc_msgSend_id((id)TaskDelegateClass, sel("alloc")); + delegate = objc_msgSend_id(delegate, sel("init")); + + return delegate; +} + +void naettPlatformMakeRequest(InternalRequest* req, InternalResponse* res) { + id config = objc_msgSend_id(class("NSURLSessionConfiguration"), sel("ephemeralSessionConfiguration")); + id delegate = createDelegate(); + + id session = objc_msgSend_t(id, id, id, id)( + class("NSURLSession"), sel("sessionWithConfiguration:delegate:delegateQueue:"), config, delegate, nil); + + res->session = session; + release(delegate); + + id task = objc_msgSend_t(id, id)(session, sel("dataTaskWithRequest:"), req->urlRequest); + object_setInstanceVariable(delegate, "response", (void*)res); + objc_msgSend_void(task, sel("resume")); + release(task); +} + +void naettPlatformFreeRequest(InternalRequest* req) { + release(req->urlRequest); + req->urlRequest = nil; +} + +void naettPlatformCloseResponse(InternalResponse* res) { + release(res->session); +} + +#endif // __MACOS__ +// End of inlined naett_osx.c // + +// End of inlined amalgam.h // diff --git a/naett_internal.h b/naett_internal.h @@ -1,75 +0,0 @@ -#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; - int capacity; -} Buffer; - -typedef struct { - const char* method; - int timeoutMS; - naettReadFunc bodyReader; - void* bodyReaderData; - naettWriteFunc bodyWriter; - void* bodyWriterData; - 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; - KVLink* headers; - Buffer body; -#if __APPLE__ - id session; -#endif -} InternalResponse; - -void naettPlatformInitRequest(InternalRequest* req); -void naettPlatformMakeRequest(InternalRequest* req, InternalResponse* res); -void naettPlatformFreeRequest(InternalRequest* req); -void naettPlatformCloseResponse(InternalResponse* res); - -#endif // NAETT_INTERNAL_H diff --git a/src/Makefile b/src/Makefile @@ -0,0 +1,4 @@ +all: ../naett.c + +../naett.c: + ./bundle.sh $@ amalgam.h diff --git a/src/amalgam.h b/src/amalgam.h @@ -0,0 +1,3 @@ +#include "naett.h" +#include "naett_core.c" +#include "naett_osx.c" diff --git a/src/bundle.sh b/src/bundle.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +usage="Usage: $0 bundle.c amalgam.h" +target=${1:?$usage} +source=${2:?$usage} + +[[ -f $source ]] || (echo source missing && exit) + +declare -A seen + +cat /dev/null > "$target" + +bundle() { + local file=$1 + + [[ -z "${seen[$file]}" ]] || return 1 + seen[$file]=1 + + echo "// Inlined $file: //" >> "$target" + IFS="" + while read line; do + if [[ $line =~ ^\#include\ \"(.+)\" && -f ${BASH_REMATCH[1]} ]]; then + include=${BASH_REMATCH[1]} + if bundle $include; then + echo "$line" >> "$target" + else + echo "//$line" >> "$target" + fi + else + echo "$line" >> "$target" + fi + done < $file + echo "// End of inlined $file //" >> "$target" + return 0 +} + +bundle "$source" diff --git a/naett.c b/src/naett_core.c diff --git a/src/naett_internal.h b/src/naett_internal.h @@ -0,0 +1,73 @@ +#ifndef NAETT_INTERNAL_H +#define NAETT_INTERNAL_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; + int capacity; +} Buffer; + +typedef struct { + const char* method; + int timeoutMS; + naettReadFunc bodyReader; + void* bodyReaderData; + naettWriteFunc bodyWriter; + void* bodyWriterData; + 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; + KVLink* headers; + Buffer body; +#if __APPLE__ + id session; +#endif +} InternalResponse; + +void naettPlatformInitRequest(InternalRequest* req); +void naettPlatformMakeRequest(InternalRequest* req, InternalResponse* res); +void naettPlatformFreeRequest(InternalRequest* req); +void naettPlatformCloseResponse(InternalResponse* res); + +#endif // NAETT_INTERNAL_H diff --git a/naett_objc.h b/src/naett_objc.h diff --git a/naett_osx.c b/src/naett_osx.c