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 ec312d7722d9094e552f1c07b2a5baf6a2f4f366
parent b298892985d59c713a4593847c367d902232304f
Author: Erik Agsjö <erik.agsjo@gmail.com>
Date:   Sun, 14 Nov 2021 20:01:41 +0100

Default body reader, better free, et.c.

Diffstat:
Mnaett.c | 80++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mnaett.h | 81++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/naett_android.c | 5+++--
Msrc/naett_core.c | 49++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/naett_internal.h | 4+++-
Msrc/naett_osx.c | 21++++++++++++++++-----
6 files changed, 213 insertions(+), 27 deletions(-)

diff --git a/naett.c b/naett.c @@ -42,6 +42,7 @@ typedef struct Buffer { void* data; int size; int capacity; + int position; } Buffer; typedef struct { @@ -69,6 +70,7 @@ typedef struct { typedef struct { InternalRequest* request; int code; + int complete; KVLink* headers; Buffer body; #if __APPLE__ @@ -80,7 +82,7 @@ typedef struct { #endif } InternalResponse; -void naettPlatformInit(void* initThing); +void naettPlatformInit(naettInitData initData); int naettPlatformInitRequest(InternalRequest* req); void naettPlatformMakeRequest(InternalResponse* res); void naettPlatformFreeRequest(InternalRequest* req); @@ -151,6 +153,18 @@ static void kvSetter(InternalParamPtr param, InternalRequest* req) { *kvField = newNode; } +static int defaultBodyReader(void* dest, int bufferSize, void* userData) { + Buffer* buffer = (Buffer*) userData; + int bytesToRead = buffer->size - buffer->position; + if (bytesToRead > bufferSize) { + bytesToRead = bufferSize; + } + + memcpy(dest, buffer->data + buffer->position, bytesToRead); + buffer->position += bytesToRead; + return bytesToRead; +} + static int defaultBodyWriter(const void* source, int bytes, void* userData) { Buffer* buffer = (Buffer*) userData; int newCapacity = buffer->capacity; @@ -185,8 +199,8 @@ static void applyOptionParams(InternalRequest* req, InternalOption* option) { // Public API -void naettInit(void* initThing) { - naettPlatformInit(initThing); +void naettInit(naettInitData initData) { + naettPlatformInit(initData); } naettOption* naettMethod(const char* method) { @@ -324,6 +338,13 @@ naettRes* naettMake(naettReq* request) { InternalRequest* req = (InternalRequest*)request; naettAlloc(InternalResponse, res); res->request = req; + if (req->options.bodyReader == NULL) { + req->options.bodyReader = defaultBodyReader; + req->options.bodyReaderData = (void*) &req->options.body; + } + if (req->options.bodyReader == defaultBodyReader) { + req->options.body.position = 0; + } if (req->options.bodyWriter == NULL) { req->options.bodyWriter = defaultBodyWriter; req->options.bodyWriterData = (void*) &res->body; @@ -352,7 +373,7 @@ const char* naettGetHeader(naettRes* response, const char* name) { int naettComplete(const naettRes* response) { InternalResponse* res = (InternalResponse*)response; - return res->code != 0; + return res->complete; } int naettGetStatus(const naettRes* response) { @@ -360,9 +381,28 @@ int naettGetStatus(const naettRes* response) { return res->code; } +static void freeKVList(KVLink* node) { + while (node != NULL) { + free(node->key); + free(node->value); + KVLink* next = node->next; + free(node); + node = next; + } +} + void naettFree(naettReq* request) { InternalRequest* req = (InternalRequest*)request; naettPlatformFreeRequest(req); + if (req->options.body.data != NULL) { + free(req->options.body.data); + } + KVLink* node = req->options.headers; + freeKVList(node); + if (req->options.body.data != NULL) { + free(req->options.body.data); + } + free(req->url); free(request); } @@ -370,6 +410,11 @@ void naettClose(naettRes* response) { InternalResponse* res = (InternalResponse*)response; res->request = NULL; naettPlatformCloseResponse(res); + if (res->body.data != NULL) { + free(res->body.data); + } + KVLink* node = res->headers; + freeKVList(node); free(response); } // End of inlined naett_core.c // @@ -445,6 +490,9 @@ void naettClose(naettRes* response) { #include <stdlib.h> #include <string.h> +void naettPlatformInit(naettInitData initData) { +} + int 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); @@ -469,16 +517,23 @@ int naettPlatformInitRequest(InternalRequest* req) { header = header->next; } - if (req->options.body.data) { - Buffer* body = &req->options.body; + const int bufSize = 10240; + char byteBuffer[bufSize]; + int bytesRead = 0; + + if (req->options.bodyReader != NULL) { + id bodyData = objc_msgSend_t(id, NSUInteger)(class("NSMutableData"), sel("dataWithCapacity"), bufSize); + do { + bytesRead = req->options.bodyReader(byteBuffer, bufSize, req->options.bodyReaderData); + objc_msgSend_t(void, const void*, NSUInteger)(bodyData, sel("appendBytes:length:"), byteBuffer, bytesRead); + } while (bytesRead > 0); - 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; + return 1; } void didReceiveData(id self, SEL _sel, id session, id dataTask, id data) { @@ -531,7 +586,9 @@ static id createDelegate() { return delegate; } -void naettPlatformMakeRequest(InternalRequest* req, InternalResponse* res) { +void naettPlatformMakeRequest(InternalResponse* res) { + InternalRequest* req = res->request; + id config = objc_msgSend_id(class("NSURLSessionConfiguration"), sel("ephemeralSessionConfiguration")); id delegate = createDelegate(); @@ -668,8 +725,8 @@ static jint intCall(JNIEnv* env, jobject instance, const char* method, const cha return result; } -void naettPlatformInit(void* initThing) { - globalVM = (JavaVM*)initThing; +void naettPlatformInit(naettInitData initData) { + globalVM = initData.vm; } int naettPlatformInitRequest(InternalRequest* req) { @@ -810,6 +867,7 @@ static void* processRequest(void* data) { voidCall(env, inputStream, "close", "()V"); res->code = statusCode; + res->complete = 1; finally: (*env)->PopLocalFrame(env, NULL); diff --git a/naett.h b/naett.h @@ -5,7 +5,21 @@ extern "C" { #endif -void naettInit(void* initThing); +#if __ANDROID__ +#include <jni.h> +#endif + +#if __ANDROID__ +typedef JavaVM* naettInitData; +#else +typedef void* naettInitData; +#endif + +/** + * @brief Global init method. + * Call to initialize the library. + */ +void naettInit(naettInitData initThing); typedef struct naettReq naettReq; typedef struct naettRes naettRes; @@ -13,30 +27,87 @@ typedef struct naettOption naettOption; typedef int (*naettReadFunc)(void* dest, int bufferSize, void* userData); typedef int (*naettWriteFunc)(const void* source, int bytes, void* userData); +// Sets request method. Defaults to "GET". naettOption* naettMethod(const char* method); +// Adds a request header. naettOption* naettHeader(const char* name, const char* value); +// Sets the request body. Ignored if a body reader is configured. naettOption* naettBody(const char* body, int size); +// Sets a request body reader. naettOption* naettBodyReader(naettReadFunc reader, void* userData); +// Sets a response body writer. naettOption* naettBodyWriter(naettWriteFunc writer, void* userData); +// Sets timeout, used both for connection and read. naettOption* naettTimeout(int milliSeconds); +/** + * @brief Creates a new request to the specified url. + * Use varargs options to configure the connection and following request. + */ #define naettRequest(url, ...) naettRequest_va(url, countOptions(__VA_ARGS__), ##__VA_ARGS__) + +/** + * @brief Creates a new request to the specified url. + * Uses an array of options rather that varargs. + */ naettReq* naettRequestWithOptions(const char* url, int numOptions, const naettOption** options); -void naettFree(naettReq* request); + +/** + * @brief Makes a request and returns a response object. + * The actual request is processed asynchronously, use `naettComplete` + * to check if the response is completed. + * + * A request object can be reused multiple times to make requests, but + * there can be only one active request using the same request object. + */ naettRes* naettMake(naettReq* request); +/** + * @brief Frees a previously allocated request object. + * The request must not have any pending responses. + */ +void naettFree(naettReq* request); + +/** + * @brief Checks if a response is complete, with a result + * or with an error. + * Use `naettGetStatus` to get the status. + */ int naettComplete(const naettRes* response); -enum { + +enum naettStatus { naettConnectionError = -1, naettProtocolError = -2, naettReadError = -3, - naettConnecting = 0, + naettProcessing = 0, }; + +/** + * @brief Returns the status of a response. + * Status codes > 0 are HTTP status codes as returned by the server. + * Status codes < 0 are processing errors according to the `naettStatus` + * enum values. + */ int naettGetStatus(const naettRes* response); -const void* naettGetBody(naettRes* response, int* size); + +/** + * @brief Returns the response body. + * The body returned by this method is always empty when a custom + * body reader has been set up using the `naettBodyReader` option. + */ +const void* naettGetBody(naettRes* response, int* outSize); + +/** + * @brief Returns the HTTP header value for the specified header name. + */ const char* naettGetHeader(naettRes* response, const char* name); + +/** + * @brief Closes a response object. + */ void naettClose(naettRes* response); +// Varargs glue naettReq* naettRequest_va(const char* url, int numOptions, ...); #define countOptions(...) ((sizeof((void*[]){ __VA_ARGS__ }) / sizeof(void*))) diff --git a/src/naett_android.c b/src/naett_android.c @@ -83,8 +83,8 @@ static jint intCall(JNIEnv* env, jobject instance, const char* method, const cha return result; } -void naettPlatformInit(void* initThing) { - globalVM = (JavaVM*)initThing; +void naettPlatformInit(naettInitData initData) { + globalVM = initData.vm; } int naettPlatformInitRequest(InternalRequest* req) { @@ -225,6 +225,7 @@ static void* processRequest(void* data) { voidCall(env, inputStream, "close", "()V"); res->code = statusCode; + res->complete = 1; finally: (*env)->PopLocalFrame(env, NULL); diff --git a/src/naett_core.c b/src/naett_core.c @@ -61,6 +61,18 @@ static void kvSetter(InternalParamPtr param, InternalRequest* req) { *kvField = newNode; } +static int defaultBodyReader(void* dest, int bufferSize, void* userData) { + Buffer* buffer = (Buffer*) userData; + int bytesToRead = buffer->size - buffer->position; + if (bytesToRead > bufferSize) { + bytesToRead = bufferSize; + } + + memcpy(dest, buffer->data + buffer->position, bytesToRead); + buffer->position += bytesToRead; + return bytesToRead; +} + static int defaultBodyWriter(const void* source, int bytes, void* userData) { Buffer* buffer = (Buffer*) userData; int newCapacity = buffer->capacity; @@ -95,8 +107,8 @@ static void applyOptionParams(InternalRequest* req, InternalOption* option) { // Public API -void naettInit(void* initThing) { - naettPlatformInit(initThing); +void naettInit(naettInitData initData) { + naettPlatformInit(initData); } naettOption* naettMethod(const char* method) { @@ -234,6 +246,13 @@ naettRes* naettMake(naettReq* request) { InternalRequest* req = (InternalRequest*)request; naettAlloc(InternalResponse, res); res->request = req; + if (req->options.bodyReader == NULL) { + req->options.bodyReader = defaultBodyReader; + req->options.bodyReaderData = (void*) &req->options.body; + } + if (req->options.bodyReader == defaultBodyReader) { + req->options.body.position = 0; + } if (req->options.bodyWriter == NULL) { req->options.bodyWriter = defaultBodyWriter; req->options.bodyWriterData = (void*) &res->body; @@ -262,7 +281,7 @@ const char* naettGetHeader(naettRes* response, const char* name) { int naettComplete(const naettRes* response) { InternalResponse* res = (InternalResponse*)response; - return res->code != 0; + return res->complete; } int naettGetStatus(const naettRes* response) { @@ -270,9 +289,28 @@ int naettGetStatus(const naettRes* response) { return res->code; } +static void freeKVList(KVLink* node) { + while (node != NULL) { + free(node->key); + free(node->value); + KVLink* next = node->next; + free(node); + node = next; + } +} + void naettFree(naettReq* request) { InternalRequest* req = (InternalRequest*)request; naettPlatformFreeRequest(req); + if (req->options.body.data != NULL) { + free(req->options.body.data); + } + KVLink* node = req->options.headers; + freeKVList(node); + if (req->options.body.data != NULL) { + free(req->options.body.data); + } + free(req->url); free(request); } @@ -280,5 +318,10 @@ void naettClose(naettRes* response) { InternalResponse* res = (InternalResponse*)response; res->request = NULL; naettPlatformCloseResponse(res); + if (res->body.data != NULL) { + free(res->body.data); + } + KVLink* node = res->headers; + freeKVList(node); free(response); } diff --git a/src/naett_internal.h b/src/naett_internal.h @@ -38,6 +38,7 @@ typedef struct Buffer { void* data; int size; int capacity; + int position; } Buffer; typedef struct { @@ -65,6 +66,7 @@ typedef struct { typedef struct { InternalRequest* request; int code; + int complete; KVLink* headers; Buffer body; #if __APPLE__ @@ -76,7 +78,7 @@ typedef struct { #endif } InternalResponse; -void naettPlatformInit(void* initThing); +void naettPlatformInit(naettInitData initData); int naettPlatformInitRequest(InternalRequest* req); void naettPlatformMakeRequest(InternalResponse* res); void naettPlatformFreeRequest(InternalRequest* req); diff --git a/src/naett_osx.c b/src/naett_osx.c @@ -6,6 +6,9 @@ #include <stdlib.h> #include <string.h> +void naettPlatformInit(naettInitData initData) { +} + int 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); @@ -30,11 +33,17 @@ int naettPlatformInitRequest(InternalRequest* req) { header = header->next; } - if (req->options.body.data) { - Buffer* body = &req->options.body; + const int bufSize = 10240; + char byteBuffer[bufSize]; + int bytesRead = 0; + + if (req->options.bodyReader != NULL) { + id bodyData = objc_msgSend_t(id, NSUInteger)(class("NSMutableData"), sel("dataWithCapacity"), bufSize); + do { + bytesRead = req->options.bodyReader(byteBuffer, bufSize, req->options.bodyReaderData); + objc_msgSend_t(void, const void*, NSUInteger)(bodyData, sel("appendBytes:length:"), byteBuffer, bytesRead); + } while (bytesRead > 0); - 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); } @@ -93,7 +102,9 @@ static id createDelegate() { return delegate; } -void naettPlatformMakeRequest(InternalRequest* req, InternalResponse* res) { +void naettPlatformMakeRequest(InternalResponse* res) { + InternalRequest* req = res->request; + id config = objc_msgSend_id(class("NSURLSessionConfiguration"), sel("ephemeralSessionConfiguration")); id delegate = createDelegate();