Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Http support #281

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gravity_visualstudio/exports.def
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ EXPORTS
gravity_list_from_array
gravity_list_free
gravity_list_append_list
gravity_list_append_value
gravity_list_blacken
gravity_list_size

Expand Down
2 changes: 2 additions & 0 deletions gravity_visualstudio/gravity.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<ClInclude Include="..\src\compiler\gravity_token.h" />
<ClInclude Include="..\src\compiler\gravity_visitor.h" />
<ClInclude Include="..\src\optionals\gravity_math.h" />
<ClInclude Include="..\src\optionals\gravity_http.h" />
<ClInclude Include="..\src\optionals\gravity_optionals.h" />
<ClInclude Include="..\src\runtime\gravity_core.h" />
<ClInclude Include="..\src\runtime\gravity_vm.h" />
Expand Down Expand Up @@ -63,6 +64,7 @@
<ClCompile Include="..\src\compiler\gravity_token.c" />
<ClCompile Include="..\src\compiler\gravity_visitor.c" />
<ClCompile Include="..\src\optionals\gravity_math.c" />
<ClCompile Include="..\src\optionals\gravity_http.c" />
<ClCompile Include="..\src\runtime\gravity_core.c" />
<ClCompile Include="..\src\runtime\gravity_vm.c" />
<ClCompile Include="..\src\shared\gravity_hash.c" />
Expand Down
6 changes: 6 additions & 0 deletions gravity_visualstudio/gravity.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@
<ClInclude Include="..\src\optionals\gravity_math.h">
<Filter>src\opt</Filter>
</ClInclude>
<ClInclude Include="..\src\optionals\gravity_http.h">
<Filter>src\opt</Filter>
</ClInclude>
<ClInclude Include="..\src\optionals\gravity_optionals.h">
<Filter>src\opt</Filter>
</ClInclude>
Expand Down Expand Up @@ -185,6 +188,9 @@
<ClCompile Include="..\src\optionals\gravity_math.c">
<Filter>src\opt</Filter>
</ClCompile>
<ClCompile Include="..\src\optionals\gravity_http.c">
<Filter>src\opt</Filter>
</ClCompile>
<ClCompile Include="..\src\compiler\gravity_ast.c">
<Filter>src\compiler</Filter>
</ClCompile>
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/gravity_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -2516,6 +2516,11 @@ static void parser_register_optional_classes (gravity_parser_t *parser) {
gnode_array_push(decls, decl2);
#endif

#ifdef GRAVITY_INCLUDE_HTTP
gnode_t *decl3 = gnode_variable_create(NO_TOKEN, string_dup(GRAVITY_HTTP_NAME()), NULL, NULL, LAST_DECLARATION(), NULL);
gnode_array_push(decls, decl3);
#endif

// check if optional classes callback is registered
if (parser->delegate && parser->delegate->optional_classes) {
const char **list = parser->delegate->optional_classes();
Expand Down
313 changes: 313 additions & 0 deletions src/optionals/gravity_http.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
//
// gravity_http.c
// gravity
//
// Created by Marco Bambini on 14/08/2017.
// Copyright © 2017 CreoLabs. All rights reserved.
//
// Based on: https://www.w3schools.com/jsref/jsref_obj_http.asp

#include <time.h>
#include <inttypes.h>
/* Generic */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Network */
#include <netdb.h>
#include <sys/socket.h>
#include "gravity_vm.h"
#include "gravity_http.h"
#include "gravity_core.h"
#include "gravity_hash.h"
#include "gravity_utils.h"
#include "gravity_macros.h"
#include "gravity_vmmacros.h"

#define HTTP_CLASS_NAME "Http"

#if GRAVITY_ENABLE_DOUBLE
#define BUF_SIZE 1024
#else
#define BUF_SIZE 1024
#endif

static gravity_class_t *gravity_class_http = NULL;
static uint32_t refcount = 0;

// MARK: - Implementation -

// Get host information (used to establish_connection)
static struct addrinfo *get_host_info(char* host, char* port) {
int r;
struct addrinfo hints, *getaddrinfo_res;
// Setup hints
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if ((r = getaddrinfo(host, port, &hints, &getaddrinfo_res))) {
fprintf(stderr, "[get_host_info:getaddrinfo] %s\n", gai_strerror(r));
return NULL;
}

return getaddrinfo_res;
}

// Establish connection with host
static int establish_connection(struct addrinfo *info) {
if (info == NULL) return -1;

int fd;
for (;info != NULL; info = info->ai_next) {
if ((fd = socket(info->ai_family,
info->ai_socktype,
info->ai_protocol)) < 0) {
perror("[establish_connection:socket]");
continue;
}

if (connect(fd, info->ai_addr, info->ai_addrlen) < 0) {
close(fd);
perror("[establish_connection:connect]");
continue;
}

freeaddrinfo(info);
return fd;
}

freeaddrinfo(info);
return -1;
}

static ssize_t send_request(gravity_vm *vm, int fd, char *method, char *path, gravity_map_t *data) {
char *req = (char *)mem_alloc(NULL, 1000 * sizeof(char));
sprintf(req, "%s %s HTTP/1.1\r\n", method, path);
if (strcmp(method, "POST") == 0 && data != NULL) {
char content_length_header[40];
char *json = gravity_map_to_string(vm, data);
sprintf(content_length_header, "Content-Length: %llu\r\n", strlen(json) * (uint64_t)sizeof(char));
strcat(req, content_length_header);
strcat(req, "Content-Type: application/json\r\n");
strcat(req, "\r\n");
strcat(req, json);
strcat(req, "\r\n");
} else {
strcat(req, "\r\n");
}
ssize_t b = send(fd, req, strlen(req), 0);
mem_free(req);
return b;
}

static char *receive_request(int fd, char **buf) {
*buf = (char *)mem_alloc(NULL, BUF_SIZE * sizeof(char));
while (recv(fd, *buf, BUF_SIZE, MSG_WAITALL) > 0);
return *buf;
}

static bool validate_options(gravity_vm *vm, gravity_map_t **options, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
if(!VALUE_ISA_MAP(args[1])) {
RETURN_ERROR("Data must be a map.");
}
*options = VALUE_AS_MAP(args[1]);
return true;
}

static bool validate_request_args(gravity_vm *vm, gravity_map_t **options, uint32_t rindex) {
// validate host
// - required
// - must be a string
gravity_value_t *host = gravity_hash_lookup_cstring((*options)->hash, "host");
if (host == NULL)
RETURN_ERROR("Host must be specified.");
if (!VALUE_ISA_STRING(((gravity_value_t)(*host))))
RETURN_ERROR("Host must be a string.");

// validate path
// - not required, defaults to '/'
// - must be a string
gravity_value_t *path = gravity_hash_lookup_cstring((*options)->hash, "path");
if (path == NULL)
gravity_map_insert(vm, *options, gravity_string_to_value(vm, "path", 4), gravity_string_to_value(vm, "/", 1));
else if (!VALUE_ISA_STRING(((gravity_value_t)(*path))))
RETURN_ERROR("Path must be a string.");

// validate port
// - not required, defaults to "80"
// - must be a string
gravity_value_t *port = gravity_hash_lookup_cstring((*options)->hash, "port");
if (port == NULL)
gravity_map_insert(vm, *options, gravity_string_to_value(vm, "port", 4), gravity_string_to_value(vm, "80", 2));
else if (!VALUE_ISA_STRING(((gravity_value_t)(*port))))
RETURN_ERROR("Port must be a string.");

// validate method
// - not required, defaults to "GET"
// - must be a string
gravity_value_t *method = gravity_hash_lookup_cstring((*options)->hash, "method");
if (method == NULL)
gravity_map_insert(vm, *options, gravity_string_to_value(vm, "method", 6), gravity_string_to_value(vm, "GET", 3));
else if (!VALUE_ISA_STRING(((gravity_value_t)(*method))))
RETURN_ERROR("Method must be a string.");

// validate data
// - not required, defaults to empty map
// - must be a map
gravity_value_t *data = gravity_hash_lookup_cstring((*options)->hash, "data");
if (data == NULL)
gravity_map_insert(vm, *options, gravity_string_to_value(vm, "data", 4), gravity_map_to_value(vm, gravity_map_new(vm, 32)));
else if (!VALUE_ISA_MAP(((gravity_value_t)(*data))))
RETURN_ERROR("Data must be a map.");

return true;
}

static bool http_request (gravity_vm *vm, gravity_map_t *options, uint32_t rindex) {
bool valid_args = validate_request_args(vm, &options, rindex);
if (!valid_args)
return valid_args;

int fd;
char *hostname = VALUE_AS_CSTRING(*gravity_hash_lookup_cstring(options->hash, "host"));
char *path = VALUE_AS_CSTRING(*gravity_hash_lookup_cstring(options->hash, "path"));
char *port = VALUE_AS_CSTRING(*gravity_hash_lookup_cstring(options->hash, "port"));
char *method = VALUE_AS_CSTRING(*gravity_hash_lookup_cstring(options->hash, "method"));
gravity_map_t *data = VALUE_AS_MAP(*gravity_hash_lookup_cstring(options->hash, "data"));

fd = establish_connection(get_host_info(hostname, port));
if (fd == -1) {
RETURN_ERROR("Failed to connect.");
}

char *buf;
send_request(vm, fd, method, path, data);
receive_request(fd, &buf);
close(fd);

gravity_map_t *response = gravity_map_new(vm, 32);
char *token = NULL;
token = strtok(buf, "\n");

// TODO: fix this ugliness
char content_length[10];
char http_version[5];
char status_code[5];
char status_message[31];
char content_type[50];
char charset[20];
char date[50];
// parse headers
char body[1000] = "";
bool parsing_body = false;
while (token) {
if (parsing_body) {
strcat(body, token);
}
else if (sscanf(token, "HTTP/%s %s %s\r\n", http_version, status_code, status_message) > 0) {
gravity_map_insert(vm, response,
gravity_string_to_value(vm, "http_version", strlen("http_version")),
gravity_string_to_value(vm, http_version, strlen(http_version)));
gravity_map_insert(vm, response,
gravity_string_to_value(vm, "status_code", strlen("status_code")),
gravity_string_to_value(vm, status_code, strlen(status_code)));
gravity_map_insert(vm, response,
gravity_string_to_value(vm, "status_message", strlen("status_message")),
gravity_string_to_value(vm, status_message, strlen(status_message)));
}
else if (sscanf(token, "Content-Type: %s %s\r\n", content_type, charset) > 0) {
gravity_map_insert(vm, response,
gravity_string_to_value(vm, "content_type", strlen("content_type")),
gravity_string_to_value(vm, content_type, strlen(content_type)));
gravity_map_insert(vm, response,
gravity_string_to_value(vm, "charset", strlen("charset")),
gravity_string_to_value(vm, charset, strlen(charset)));
}
else if (sscanf(token, "Content-Length: %s\r\n", content_length) > 0)
gravity_map_insert(vm, response,
gravity_string_to_value(vm, "content_length", strlen("content_length")),
gravity_string_to_value(vm, content_length, strlen(content_length)));
else if (sscanf(token, "Date: %29c\r\n", date) > 0)
gravity_map_insert(vm, response,
gravity_string_to_value(vm, "date", strlen("date")),
gravity_string_to_value(vm, date, strlen(date)));
else if (strncmp(token, "\r\n", strlen(token)) == 0) {
gravity_map_insert(vm, response,
gravity_string_to_value(vm, "body_reached", strlen("body_reached")),
gravity_string_to_value(vm, "true", strlen("true")));
parsing_body = true;
}
token = strtok(NULL, "\n");
}
gravity_map_insert(vm, response,
gravity_string_to_value(vm, "body", (uint16_t)strlen("body")),
gravity_string_to_value(vm, body, strlen(body)));
mem_free(buf);
RETURN_VALUE(VALUE_FROM_OBJECT(response), rindex);
}

static bool http_get (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
#pragma unused(vm, nargs)
gravity_map_t *options;
bool valid_options = validate_options(vm, &options, args, nargs, rindex);
if (!valid_options)
return valid_options;
gravity_map_insert(vm, options,
gravity_string_to_value(vm, "method", strlen("method")),
gravity_string_to_value(vm, "GET", strlen("GET")));
return http_request(vm, options, rindex);
}

static bool http_post (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
#pragma unused(vm, nargs)
gravity_map_t *options;
bool valid_options = validate_options(vm, &options, args, nargs, rindex);
if (!valid_options)
return valid_options;
gravity_map_insert(vm, options,
gravity_string_to_value(vm, "method", strlen("method")),
gravity_string_to_value(vm, "POST", strlen("POST")));
return http_request(vm, options, rindex);
}

// MARK: - Internals -

static void create_optional_class (void) {
gravity_class_http = gravity_class_new_pair(NULL, HTTP_CLASS_NAME, NULL, 0, 0);
gravity_class_t *meta = gravity_class_get_meta(gravity_class_http);
gravity_class_bind(meta, "get", NEW_CLOSURE_VALUE(http_get));
gravity_class_bind(meta, "post", NEW_CLOSURE_VALUE(http_post));
gravity_closure_t *closure = NULL;
SETMETA_INITED(gravity_class_http);
}

// MARK: - Commons -

bool gravity_ishttp_class (gravity_class_t *c) {
return (c == gravity_class_http);
}

const char *gravity_http_name (void) {
return HTTP_CLASS_NAME;
}

void gravity_http_register (gravity_vm *vm) {
if (!gravity_class_http) create_optional_class();
++refcount;

if (!vm || gravity_vm_ismini(vm)) return;
gravity_vm_setvalue(vm, HTTP_CLASS_NAME, VALUE_FROM_OBJECT(gravity_class_http));
}

void gravity_http_free (void) {
if (!gravity_class_http) return;
if (--refcount) return;

gravity_class_t *meta = gravity_class_get_meta(gravity_class_http);
gravity_class_free_core(NULL, meta);
gravity_class_free_core(NULL, gravity_class_http);

gravity_class_http = NULL;
}

19 changes: 19 additions & 0 deletions src/optionals/gravity_http.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// gravity_http.h
// gravity
//
// Created by Marco Bambini on 14/08/2017.
// Copyright © 2017 CreoLabs. All rights reserved.
//

#ifndef __GRAVITY_HTTP__
#define __GRAVITY_HTTP__

#include "gravity_value.h"

void gravity_http_register (gravity_vm *vm);
void gravity_http_free (void);
bool gravity_ishttp_class (gravity_class_t *c);
const char *gravity_http_name (void);

#endif
Loading