forked from lda/telodendria
Add HttpClient API
This commit is contained in:
parent
fc8fbc9a70
commit
313f0e2e73
6 changed files with 412 additions and 63 deletions
5
TODO.txt
5
TODO.txt
|
@ -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
|
||||
|
|
104
src/Http.c
104
src/Http.c
|
@ -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
241
src/HttpClient.c
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -125,4 +125,7 @@ extern HashMap *
|
|||
extern char *
|
||||
HttpParamEncode(HashMap *);
|
||||
|
||||
extern HashMap *
|
||||
HttpParseHeaders(FILE *);
|
||||
|
||||
#endif
|
||||
|
|
55
src/include/HttpClient.h
Normal file
55
src/include/HttpClient.h
Normal 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 */
|
Loading…
Reference in a new issue