forked from Telodendria/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
|
[ ] TLS
|
||||||
[ ] SOCKS
|
[ ] SOCKS
|
||||||
[ ] Multi-output
|
[ ] Multi-output
|
||||||
[ ] HTTP Client API
|
[~] HTTP Client API
|
||||||
|
[ ] Document HttpParseHeaders()
|
||||||
|
[ ] HttpClient man page
|
||||||
|
[ ] Test on other platforms
|
||||||
[ ] Option to pretty-print Json
|
[ ] Option to pretty-print Json
|
||||||
|
|
||||||
[ ] Simple command line tool to make matrix requests
|
[ ] Simple command line tool to make matrix requests
|
||||||
|
|
104
src/Http.c
104
src/Http.c
|
@ -26,9 +26,11 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
#include <Memory.h>
|
#include <Memory.h>
|
||||||
#include <HashMap.h>
|
#include <HashMap.h>
|
||||||
|
#include <Util.h>
|
||||||
|
|
||||||
#ifndef TELODENDRIA_STRING_CHUNK
|
#ifndef TELODENDRIA_STRING_CHUNK
|
||||||
#define TELODENDRIA_STRING_CHUNK 64
|
#define TELODENDRIA_STRING_CHUNK 64
|
||||||
|
@ -532,3 +534,105 @@ HttpParamEncode(HashMap * params)
|
||||||
|
|
||||||
return out;
|
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;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
c->requestHeaders = HashMapCreate();
|
|
||||||
if (!c->requestHeaders)
|
|
||||||
{
|
|
||||||
Free(c);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
c->responseHeaders = HashMapCreate();
|
c->responseHeaders = HashMapCreate();
|
||||||
if (!c->responseHeaders)
|
if (!c->responseHeaders)
|
||||||
{
|
{
|
||||||
|
@ -515,6 +508,7 @@ HttpServerWorkerThread(void *args)
|
||||||
|
|
||||||
if (strcmp(requestProtocol, "HTTP/1.1") != 0 && strcmp(requestProtocol, "HTTP/1.0") != 0)
|
if (strcmp(requestProtocol, "HTTP/1.1") != 0 && strcmp(requestProtocol, "HTTP/1.0") != 0)
|
||||||
{
|
{
|
||||||
|
Free(requestPath);
|
||||||
goto bad_request;
|
goto bad_request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -533,65 +527,14 @@ HttpServerWorkerThread(void *args)
|
||||||
context = HttpServerContextCreate(requestMethod, requestPath, requestParams, fp);
|
context = HttpServerContextCreate(requestMethod, requestPath, requestParams, fp);
|
||||||
if (!context)
|
if (!context)
|
||||||
{
|
{
|
||||||
|
Free(requestPath);
|
||||||
goto internal_error;
|
goto internal_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) != -1)
|
context->requestHeaders = HttpParseHeaders(fp);
|
||||||
|
if (!context->requestHeaders)
|
||||||
{
|
{
|
||||||
char *headerKey;
|
goto internal_error;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
server->requestHandler(context, server->handlerArgs);
|
server->requestHandler(context, server->handlerArgs);
|
||||||
|
|
|
@ -125,4 +125,7 @@ extern HashMap *
|
||||||
extern char *
|
extern char *
|
||||||
HttpParamEncode(HashMap *);
|
HttpParamEncode(HashMap *);
|
||||||
|
|
||||||
|
extern HashMap *
|
||||||
|
HttpParseHeaders(FILE *);
|
||||||
|
|
||||||
#endif
|
#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