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 539143df7a74c3b27ca5a5646dc00876b37f2bfb
parent c4b61b00173bb29c7a6d90e695dd3ebf7b66c0f8
Author: Erik Agsjö <erik.agsjo@gmail.com>
Date:   Wed, 10 Nov 2021 21:32:51 +0100

First Naive OSX implementation works!

Diffstat:
Mmain.c | 10++++++++--
Mnaett.c | 255++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mnaett.h | 12+++++++-----
Mnaett_internal.h | 7+++++++
Mnaett_osx.c | 41++++++++++++++++++++++++++++++++---------
5 files changed, 235 insertions(+), 90 deletions(-)

diff --git a/main.c b/main.c @@ -1,10 +1,16 @@ #include "naett.h" #include <unistd.h> +#include <stdio.h> int main(int argc, char** argv) { - naettReq* req = naettRequest("http://www.dn.se", naettMethod("GET"), naettHeader("content-type", "application/json")); + naettReq* req = + naettRequest("http://www.dn.se", naettMethod("GET"), naettHeader("content-type", "application/json")); naettRes* res = naettMake(req); while (!naettComplete(res)) { - usleep(100*1000); + usleep(100 * 1000); } + + int bodyLength = 0; + const char* body = naettGetBody(res, &bodyLength); + printf("Got %d bytes of type %s\n", bodyLength, naettGetHeader(res, "content-type")); } diff --git a/naett.c b/naett.c @@ -4,122 +4,186 @@ #include <stddef.h> #include <string.h> -typedef struct InternalOption* InternalOptionPtr; -typedef void (*OptionSetter)(InternalOptionPtr option, InternalRequest* req); +typedef struct InternalParam* InternalParamPtr; +typedef void (*ParamSetter)(InternalParamPtr param, InternalRequest* req); + +typedef struct InternalParam { + ParamSetter setter; + int offset; + union { + int integer; + const char* string; + struct { + const char* key; + const char* value; + } kv; + void* ptr; + }; +} InternalParam; 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; - }; + #define maxParams 2 + int numParams; + InternalParam params[maxParams]; } InternalOption; -void stringSetter(InternalOptionPtr option, InternalRequest* req) { - char* stringCopy = strdup(option->string); - char* opaque = (char*) &req->options; - char** stringField = (char**) (opaque + option->offset); +static void stringSetter(InternalParamPtr param, InternalRequest* req) { + char* stringCopy = strdup(param->string); + char* opaque = (char*)&req->options; + char** stringField = (char**)(opaque + param->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; +static void intSetter(InternalParamPtr param, InternalRequest* req) { + char* opaque = (char*)&req->options; + int* intField = (int*)(opaque + param->offset); + *intField = param->integer; } -void ptrSetter(InternalOptionPtr option, InternalRequest* req) { - char* opaque = (char*) &req->options; - void** ptrField = (void**) (opaque + option->offset); - *ptrField = option->ptr; +static void ptrSetter(InternalParamPtr param, InternalRequest* req) { + char* opaque = (char*)&req->options; + void** ptrField = (void**)(opaque + param->offset); + *ptrField = param->ptr; } -void kvSetter(InternalOptionPtr option, InternalRequest* req) { - char* opaque = (char*) &req->options; - KVLink** kvField = (KVLink**) (opaque + option->offset); - +static void kvSetter(InternalParamPtr param, InternalRequest* req) { + char* opaque = (char*)&req->options; + KVLink** kvField = (KVLink**)(opaque + param->offset); + naettAlloc(KVLink, newNode); - newNode->key = strdup(option->kv.key); - newNode->value = strdup(option->kv.value); + newNode->key = strdup(param->kv.key); + newNode->value = strdup(param->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; +static int defaultBodyWriter(const void* source, int bytes, void* userData) { + Buffer* buffer = (Buffer*) userData; + int newCapacity = buffer->capacity; + if (newCapacity == 0) { + newCapacity = bytes; + } + while (newCapacity - buffer->size < bytes) { + newCapacity *= 2; + } + if (newCapacity != buffer->capacity) { + buffer->data = realloc(buffer->data, newCapacity); + buffer->capacity = newCapacity; + } + char* dest = ((char*)buffer->data) + buffer->size; + memcpy(dest, source, bytes); + buffer->size += bytes; + return bytes; } -void initRequest(InternalRequest* req, const char* url) { +static void initRequest(InternalRequest* req, const char* url) { req->options.method = strdup("GET"); req->url = strdup(url); } +static void applyOptionParams(InternalRequest* req, InternalOption* option) { + for (int j = 0; j < option->numParams; j++) { + InternalParam* param = option->params + j; + param->setter(param, req); + } +} + // Public API naettOption* naettMethod(const char* method) { naettAlloc(InternalOption, option); - option->string = method; - option->offset = offsetof(RequestOptions, method); - option->setter = stringSetter; - return (naettOption*) option; + option->numParams = 1; + InternalParam* param = &option->params[0]; + + param->string = method; + param->offset = offsetof(RequestOptions, method); + param->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; + option->numParams = 1; + InternalParam* param = &option->params[0]; + + param->kv.key = name; + param->kv.value = value; + param->offset = offsetof(RequestOptions, headers); + param->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; + option->numParams = 1; + InternalParam* param = &option->params[0]; + + param->integer = timeoutMS; + param->offset = offsetof(RequestOptions, timeoutMS); + param->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; + option->numParams = 2; + + InternalParam* bodyParam = &option->params[0]; + InternalParam* sizeParam = &option->params[1]; + + bodyParam->ptr = (void*)body; + bodyParam->offset = offsetof(RequestOptions, body) + offsetof(Buffer, data); + bodyParam->setter = ptrSetter; + + sizeParam->integer = size; + sizeParam->offset = offsetof(RequestOptions, body) + offsetof(Buffer, size); + sizeParam->setter = intSetter; + + return (naettOption*)option; } -naettOption* naettBodyReader(naettReadFunc reader) { +naettOption* naettBodyReader(naettReadFunc reader, void* userData) { naettAlloc(InternalOption, option); - option->ptr = reader; - option->offset = offsetof(RequestOptions, bodyReader); - option->setter = ptrSetter; - return (naettOption*) option; + option->numParams = 2; + + InternalParam* readerParam = &option->params[0]; + InternalParam* dataParam = &option->params[1]; + + readerParam->ptr = reader; + readerParam->offset = offsetof(RequestOptions, bodyReader); + readerParam->setter = ptrSetter; + + dataParam->ptr = reader; + dataParam->offset = offsetof(RequestOptions, bodyReaderData); + dataParam->setter = ptrSetter; + + return (naettOption*)option; } -naettOption* naettBodyWriter(naettWriteFunc writer) { +naettOption* naettBodyWriter(naettWriteFunc writer, void* userData) { naettAlloc(InternalOption, option); - option->ptr = writer; - option->offset = offsetof(RequestOptions, bodyWriter); - option->setter = ptrSetter; - return (naettOption*) option; -} + option->numParams = 2; + + InternalParam* writerParam = &option->params[0]; + InternalParam* dataParam = &option->params[1]; + writerParam->ptr = writer; + writerParam->offset = offsetof(RequestOptions, bodyWriter); + writerParam->setter = ptrSetter; + + dataParam->ptr = writer; + dataParam->offset = offsetof(RequestOptions, bodyWriterData); + dataParam->setter = ptrSetter; + + return (naettOption*)option; +} naettReq* naettRequest_va(const char* url, int numArgs, ...) { va_list args; InternalOption* option; @@ -129,13 +193,13 @@ naettReq* naettRequest_va(const char* url, int numArgs, ...) { va_start(args, numArgs); for (int i = 0; i < numArgs; i++) { option = va_arg(args, InternalOption*); - option->setter(option, req); + applyOptionParams(req, option); free(option); } va_end(args); naettPlatformInitRequest(req); - return (naettReq*) req; + return (naettReq*)req; } naettReq* naettRequestWithOptions(const char* url, int numOptions, const naettOption** options) { @@ -143,16 +207,59 @@ naettReq* naettRequestWithOptions(const char* url, int numOptions, const naettOp initRequest(req, url); for (int i = 0; i < numOptions; i++) { - InternalOption* option = (InternalOption*) options[i]; - option->setter(option, req); + InternalOption* option = (InternalOption*)options[i]; + applyOptionParams(req, option); free(option); } naettPlatformInitRequest(req); - return (naettReq*) req; + return (naettReq*)req; +} + +naettRes* naettMake(naettReq* request) { + InternalRequest* req = (InternalRequest*)request; + naettAlloc(InternalResponse, res); + res->request = req; + if (req->options.bodyWriter == NULL) { + req->options.bodyWriter = defaultBodyWriter; + req->options.bodyWriterData = (void*) &res->body; + } + naettPlatformMakeRequest(req, res); + return (naettRes*) res; +} + +const void* naettGetBody(naettRes* response, int* size) { + InternalResponse* res = (InternalResponse*)response; + *size = res->body.size; + return res->body.data; +} + +const char* naettGetHeader(naettRes* response, const char* name) { + InternalResponse* res = (InternalResponse*)response; + KVLink* node = res->headers; + while (node) { + if (strcasecmp(name, node->key) == 0) { + return node->value; + } + node = node->next; + } + return NULL; } int naettComplete(const naettRes* response) { - InternalResponse* res = (InternalResponse*) response; + InternalResponse* res = (InternalResponse*)response; return res->complete; } + +void naettFree(naettReq* request) { + InternalRequest* req = (InternalRequest*)request; + naettPlatformFreeRequest(req); + free(request); +} + +void naettClose(naettRes* response) { + InternalResponse* res = (InternalResponse*)response; + res->request = NULL; + naettPlatformCloseResponse(res); + free(response); +} diff --git a/naett.h b/naett.h @@ -4,22 +4,24 @@ 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); +typedef int (*naettReadFunc)(void* dest, int bufferSize, void* userData); +typedef int (*naettWriteFunc)(const void* source, int bytes, void* userData); 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* naettBodyReader(naettReadFunc reader, void* userData); +naettOption* naettBodyWriter(naettWriteFunc writer, void* userData); 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); +void naettFree(naettReq* request); naettRes* naettMake(naettReq* request); int naettComplete(const naettRes* response); +const void* naettGetBody(naettRes* response, int* size); +const char* naettGetHeader(naettRes* response, const char* name); void naettClose(naettRes* response); naettReq* naettRequest_va(const char* url, int numOptions, ...); diff --git a/naett_internal.h b/naett_internal.h @@ -34,13 +34,16 @@ typedef struct 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; @@ -57,9 +60,13 @@ typedef struct { InternalRequest* request; int complete; int code; + KVLink* headers; Buffer body; } 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_osx.c b/naett_osx.c @@ -4,6 +4,7 @@ #include "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); @@ -35,7 +36,30 @@ void naettPlatformInitRequest(InternalRequest* req) { void didReceiveData(id self, SEL _sel, id session, id dataTask, id data) { InternalResponse* res = NULL; - object_getInstanceVariable(self, "response", (void**)&res); + 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); } void didComplete(id self, SEL _sel, id session, id dataTask, id error) { @@ -44,9 +68,7 @@ void didComplete(id self, SEL _sel, id session, id dataTask, id error) { res->complete = 1; } -naettRes* naettMake(naettReq* request) { - InternalRequest* req = (InternalRequest*)request; - +void naettPlatformMakeRequest(InternalRequest* req, InternalResponse* res) { Class TaskDelegateClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "TaskDelegate", 0); addMethod(TaskDelegateClass, "URLSession:dataTask:didReceiveData:", didReceiveData, "v@:@@@"); addMethod(TaskDelegateClass, "URLSession:task:didCompleteWithError:", didComplete, "v@:@@@"); @@ -61,13 +83,14 @@ naettRes* naettMake(naettReq* request) { 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); - + object_setInstanceVariable(delegate, "response", (void*)res); objc_msgSend_void(task, sel("resume")); +} + +void naettPlatformFreeRequest(InternalRequest* req) { +} - return (naettRes*) response; +void naettPlatformCloseResponse(InternalResponse* res) { } #endif // __MACOS__