Add HttpClient API

This commit is contained in:
Jordan Bancino 2023-03-07 23:10:06 +00:00
parent fc8fbc9a70
commit 313f0e2e73
6 changed files with 412 additions and 63 deletions

View file

@ -15,7 +15,10 @@ Milestone: v0.3.0
[ ] TLS
[ ] SOCKS
[ ] Multi-output
[ ] HTTP Client API
[~] HTTP Client API
[ ] Document HttpParseHeaders()
[ ] HttpClient man page
[ ] Test on other platforms
[ ] Option to pretty-print Json
[ ] Simple command line tool to make matrix requests

View file

@ -26,9 +26,11 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <Memory.h>
#include <HashMap.h>
#include <Util.h>
#ifndef TELODENDRIA_STRING_CHUNK
#define TELODENDRIA_STRING_CHUNK 64
@ -532,3 +534,105 @@ HttpParamEncode(HashMap * params)
return out;
}
HashMap *
HttpParseHeaders(FILE * fp)
{
HashMap *headers;
char *line;
ssize_t lineLen;
size_t lineSize;
char *headerKey;
char *headerValue;
if (!fp)
{
return NULL;
}
headers = HashMapCreate();
if (!headers)
{
return NULL;
}
line = NULL;
lineLen = 0;
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) != -1)
{
char *headerPtr;
ssize_t i;
if (strcmp(line, "\r\n") == 0 || strcmp(line, "\n") == 0)
{
break;
}
for (i = 0; i < lineLen; i++)
{
if (line[i] == ':')
{
line[i] = '\0';
break;
}
line[i] = tolower((unsigned char) line[i]);
}
headerKey = Malloc((i + 1) * sizeof(char));
if (!headerKey)
{
goto error;
}
strcpy(headerKey, line);
headerPtr = line + i + 1;
while (isspace((unsigned char) *headerPtr))
{
headerPtr++;
}
for (i = lineLen - 1; i > (line + lineLen) - headerPtr; i--)
{
if (!isspace((unsigned char) line[i]))
{
break;
}
line[i] = '\0';
}
headerValue = Malloc(strlen(headerPtr) + 1);
if (!headerValue)
{
Free(headerKey);
goto error;
}
strcpy(headerValue, headerPtr);
HashMapSet(headers, headerKey, headerValue);
Free(headerKey);
}
Free(line);
return headers;
error:
Free(line);
while (HashMapIterate(headers, &headerKey, (void **) &headerValue))
{
Free(headerValue);
}
HashMapFree(headers);
return NULL;
}

241
src/HttpClient.c Normal file
View file

@ -0,0 +1,241 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <HttpClient.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <Http.h>
#include <Memory.h>
#include <Util.h>
struct HttpClientContext
{
HashMap *responseHeaders;
FILE *stream;
};
HttpClientContext *
HttpRequest(HttpRequestMethod method, int flags, char *host, char *path, char *port)
{
HttpClientContext *context;
int sd;
struct addrinfo hints, *res, *res0;
int error;
if (!method || !host || !path)
{
return NULL;
}
/* TODO: Not supported yet */
if (flags & HTTP_TLS)
{
return NULL;
}
context = Malloc(sizeof(HttpClientContext));
if (!context)
{
return NULL;
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(host, port, &hints, &res0);
if (error)
{
Free(context);
return NULL;
}
for (res = res0; res; res = res->ai_next)
{
sd = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
if (sd < 0)
{
continue;
}
if (connect(sd, res->ai_addr, res->ai_addrlen) < 0)
{
close(sd);
sd = -1;
continue;
}
break;
}
if (sd < 0)
{
Free(context);
return NULL;
}
freeaddrinfo(res0);
context->stream = fdopen(sd, "r+");
if (!context->stream)
{
Free(context);
close(sd);
return NULL;
}
fprintf(context->stream, "%s %s HTTP/1.0\r\n",
HttpRequestMethodToString(method), path);
HttpRequestHeader(context, "Connection", "close");
HttpRequestHeader(context, "User-Agent", "Telodendria/" TELODENDRIA_VERSION);
return context;
}
void
HttpRequestHeader(HttpClientContext * context, char *key, char *val)
{
if (!context || !key || !val)
{
return;
}
fprintf(context->stream, "%s: %s\r\n", key, val);
}
HttpStatus
HttpRequestSend(HttpClientContext * context)
{
HttpStatus status;
char *line = NULL;
ssize_t lineLen;
size_t lineSize = 0;
char *tmp;
if (!context)
{
return 0;
}
fprintf(context->stream, "\r\n");
fflush(context->stream);
lineLen = UtilGetLine(&line, &lineSize, context->stream);
/* Line must contain at least "HTTP/x.x xxx" */
if (lineLen < 12)
{
return 0;
}
if (!(strncmp(line, "HTTP/1.0", 8) == 0 ||
strncmp(line, "HTTP/1.1", 8) == 0))
{
return 0;
}
tmp = line + 9;
while (isspace(*tmp) && *tmp != '\0')
{
tmp++;
}
if (!*tmp)
{
return 0;
}
status = atoi(tmp);
if (!status)
{
return 0;
}
context->responseHeaders = HttpParseHeaders(context->stream);
if (!context->responseHeaders)
{
return 0;
}
return status;
}
HashMap *
HttpResponseHeaders(HttpClientContext * context)
{
if (!context)
{
return NULL;
}
return context->responseHeaders;
}
FILE *
HttpClientStream(HttpClientContext * context)
{
if (!context)
{
return NULL;
}
return context->stream;
}
void
HttpClientContextFree(HttpClientContext * context)
{
char *key;
void *val;
if (!context)
{
return;
}
while (HashMapIterate(context->responseHeaders, &key, &val))
{
Free(val);
}
HashMapFree(context->responseHeaders);
fclose(context->stream);
Free(context);
}

View file

@ -96,13 +96,6 @@ HttpServerContextCreate(HttpRequestMethod requestMethod,
return NULL;
}
c->requestHeaders = HashMapCreate();
if (!c->requestHeaders)
{
Free(c);
return NULL;
}
c->responseHeaders = HashMapCreate();
if (!c->responseHeaders)
{
@ -515,6 +508,7 @@ HttpServerWorkerThread(void *args)
if (strcmp(requestProtocol, "HTTP/1.1") != 0 && strcmp(requestProtocol, "HTTP/1.0") != 0)
{
Free(requestPath);
goto bad_request;
}
@ -533,65 +527,14 @@ HttpServerWorkerThread(void *args)
context = HttpServerContextCreate(requestMethod, requestPath, requestParams, fp);
if (!context)
{
Free(requestPath);
goto internal_error;
}
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) != -1)
context->requestHeaders = HttpParseHeaders(fp);
if (!context->requestHeaders)
{
char *headerKey;
char *headerValue;
char *headerPtr;
if (strcmp(line, "\r\n") == 0)
{
break;
}
for (i = 0; i < lineLen; i++)
{
if (line[i] == ':')
{
line[i] = '\0';
break;
}
line[i] = tolower((unsigned char) line[i]);
}
headerKey = Malloc((i + 1) * sizeof(char));
if (!headerKey)
{
goto internal_error;
}
strcpy(headerKey, line);
headerPtr = line + i + 1;
while (isspace((unsigned char) *headerPtr))
{
headerPtr++;
}
for (i = lineLen - 1; i > (line + lineLen) - headerPtr; i--)
{
if (!isspace((unsigned char) line[i]))
{
break;
}
line[i] = '\0';
}
headerValue = Malloc(strlen(headerPtr) + 1);
if (!headerValue)
{
goto internal_error;
}
strcpy(headerValue, headerPtr);
HashMapSet(context->requestHeaders, headerKey, headerValue);
Free(headerKey);
goto internal_error;
}
server->requestHandler(context, server->handlerArgs);

View file

@ -125,4 +125,7 @@ extern HashMap *
extern char *
HttpParamEncode(HashMap *);
extern HashMap *
HttpParseHeaders(FILE *);
#endif

55
src/include/HttpClient.h Normal file
View file

@ -0,0 +1,55 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef TELODENDRIA_HTTPCLIENT_H
#define TELODENDRIA_HTTPCLIENT_H
#include <stdio.h>
#include <HashMap.h>
#include <Http.h>
#define HTTP_NONE 0
#define HTTP_TLS (1 << 0)
typedef struct HttpClientContext HttpClientContext;
extern HttpClientContext *
HttpRequest(HttpRequestMethod, int, char *, char *, char *);
extern void
HttpRequestHeader(HttpClientContext *, char *, char *);
extern HttpStatus
HttpRequestSend(HttpClientContext *);
extern HashMap *
HttpResponseHeaders(HttpClientContext *);
extern FILE *
HttpClientStream(HttpClientContext *);
extern void
HttpClientContextFree(HttpClientContext *);
#endif /* TELODENDRIA_HTTPCLIENT_H */