Skip to content

Commit

Permalink
Merge pull request #77 from unum-cloud/Feature-REST
Browse files Browse the repository at this point in the history
REST-ful API Support
  • Loading branch information
ashvardanian authored Aug 29, 2023
2 parents 5461009 + 240d2f3 commit 5e676c3
Show file tree
Hide file tree
Showing 28 changed files with 1,518 additions and 602 deletions.
24 changes: 24 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,29 @@
],
},
},
{
"name": "C++: Test POSIX Rest Server",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build_debug/bin/ucall_example_rest_posix",
"cwd": "${workspaceFolder}",
"showDisplayString": true,
"stopAtEntry": false,
"externalConsole": false,
"preLaunchTask": "Build Debug",
"linux": {
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb"
},
"osx": {
"MIMode": "lldb",
"environment": [
{
"name": "CPATH",
"value": "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/"
}
],
},
},
]
}
64 changes: 34 additions & 30 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ if(MSVC)
${PICOTLSVS_DIR}/x64/Release/picotls-openssl.lib
)

include_directories(${PICOTLS_DIR}/include/)
include_directories(${PICOTLS_DIR}/include/ ${PICOTLSVS_DIR}/picotls)
set(URING_LIBS uring_internal)
else()
FetchContent_Declare(
Expand Down Expand Up @@ -219,29 +219,39 @@ endif()

find_package(Threads REQUIRED)


add_library(ucall_server_posix src/engine_posix.cpp)
target_link_libraries(ucall_server_posix simdjson::simdjson Threads::Threads ${tls_LIBS})

add_executable(ucall_example_login_posix examples/login/ucall_server.cpp)
target_link_libraries(ucall_example_login_posix ucall_server_posix cxxopts)
target_compile_options(ucall_example_login_posix PUBLIC -DCXXOPTS_NO_EXCEPTIONS=ON)
set(BACKENDS ucall_server_posix)

if(LINUX)
add_library(ucall_server_epoll src/engine_epoll.cpp)
target_link_libraries(ucall_server_epoll simdjson::simdjson Threads::Threads ${tls_LIBS})

add_executable(ucall_example_login_epoll examples/login/ucall_server.cpp)
target_link_libraries(ucall_example_login_epoll ucall_server_epoll cxxopts)
target_compile_options(ucall_example_login_epoll PUBLIC -DCXXOPTS_NO_EXCEPTIONS=ON)

add_library(ucall_server_uring src/engine_uring.cpp)
target_link_libraries(ucall_server_uring simdjson::simdjson Threads::Threads ${URING_LIBS} ${tls_LIBS})

add_executable(ucall_example_login_uring examples/login/ucall_server.cpp)
target_link_libraries(ucall_example_login_uring ucall_server_uring cxxopts)
target_compile_options(ucall_example_login_uring PUBLIC -DCXXOPTS_NO_EXCEPTIONS=ON)
set(BACKENDS ${BACKENDS} ucall_server_epoll ucall_server_uring)
endif()

foreach(backend IN LISTS BACKENDS)
string(FIND "${backend}" "_" last_underscore REVERSE)
math(EXPR substring_length "${last_underscore} + 1")
string(SUBSTRING "${backend}" ${substring_length} -1 backend_name)

set(jsonrpc_example_name "ucall_example_login_${backend_name}")
set(rest_example_name "ucall_example_rest_${backend_name}")

add_executable(${jsonrpc_example_name} examples/login/ucall_server.cpp)
target_link_libraries(${jsonrpc_example_name} ${backend} cxxopts)
target_compile_options(${jsonrpc_example_name} PUBLIC -DCXXOPTS_NO_EXCEPTIONS=ON)

add_executable(${rest_example_name} examples/login/ucall_server_rest.cpp)
target_link_libraries(${rest_example_name} ${backend} cxxopts)
target_compile_options(${rest_example_name} PUBLIC -DCXXOPTS_NO_EXCEPTIONS=ON)
endforeach()


if(UCALL_BUILD_EXAMPLES)
add_executable(ucall_example_redis examples/redis/ucall_server.cpp)
target_link_libraries(ucall_example_redis ucall_server_posix)
Expand All @@ -255,22 +265,16 @@ endif()
find_package(Python3 REQUIRED Development.Module)
include_directories(${Python_INCLUDE_DIRS})

if(LINUX)
Python3_add_library(py_ucall_uring src/python.c)
target_include_directories(py_ucall_uring PUBLIC src/ include/)
target_link_libraries(py_ucall_uring PRIVATE ucall_server_uring base64)
set_target_properties(py_ucall_uring PROPERTIES OUTPUT_NAME uring)
target_compile_definitions(py_ucall_uring PRIVATE UCALL_PYTHON_MODULE_NAME=uring)

Python3_add_library(py_ucall_epoll src/python.c)
target_include_directories(py_ucall_epoll PUBLIC src/ include/)
target_link_libraries(py_ucall_epoll PRIVATE ucall_server_epoll base64)
set_target_properties(py_ucall_epoll PROPERTIES OUTPUT_NAME epoll)
target_compile_definitions(py_ucall_epoll PRIVATE UCALL_PYTHON_MODULE_NAME=epoll)
endif()

Python3_add_library(py_ucall_posix src/python.c)
target_include_directories(py_ucall_posix PUBLIC src/ include/)
target_link_libraries(py_ucall_posix PRIVATE ucall_server_posix base64)
set_target_properties(py_ucall_posix PROPERTIES OUTPUT_NAME posix)
target_compile_definitions(py_ucall_posix PRIVATE UCALL_PYTHON_MODULE_NAME=posix)
foreach(backend IN LISTS BACKENDS)
string(FIND "${backend}" "_" last_underscore REVERSE)
math(EXPR substring_length "${last_underscore} + 1")
string(SUBSTRING "${backend}" ${substring_length} -1 backend_name)

set(py_lib_name "py_ucall_${backend_name}")
Python3_add_library(${py_lib_name} src/python.c)
target_include_directories(${py_lib_name} PUBLIC src/ include/)
target_link_libraries(${py_lib_name} PRIVATE ${backend} base64)
set_target_properties(${py_lib_name} PROPERTIES OUTPUT_NAME ${backend_name})
target_compile_definitions(${py_lib_name} PRIVATE UCALL_PYTHON_MODULE_NAME=${backend_name})
endforeach()
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ int main(int argc, char** argv) {
ucall_config_t config{};

ucall_init(&config, &server);
ucall_add_procedure(server, "sum", &sum, NULL);
ucall_add_procedure(server, "sum", &sum);
ucall_take_calls(server, 0);
ucall_free(server);
return 0;
Expand Down
100 changes: 100 additions & 0 deletions examples/login/rest_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import os
import json
import errno
import socket
import base64
import random
import string
from typing import Optional, List

import requests
from ucall.client import Client, ClientTLS


HTTP_HEADERS = '%s HTTP/1.1\r\nHost: 127.0.0.1:8540\r\nUser-Agent: python-requests/2.27.1\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nContent-Length: %i\r\nContent-Type: application/json\r\n\r\n'

# The ID of the current running process, used as a default
# identifier for requests originating from here.
PROCESS_ID = os.getpid()


def make_tcp_socket(ip: str, port: int):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
return sock


def socket_is_closed(sock: socket.socket) -> bool:
"""
Returns True if the remote side did close the connection
"""
if sock is None:
return True
try:
buf = sock.recv(1, socket.MSG_PEEK | socket.MSG_DONTWAIT)
if buf == b'':
return True
except BlockingIOError as exc:
if exc.errno != errno.EAGAIN:
# Raise on unknown exception
raise
return False


def recvall(sock, buffer_size=4096):
data = b""
while True:
chunk = sock.recv(buffer_size)
if not chunk:
break
data += chunk
return data


def parse_response(response: bytes) -> object:
if len(response) == 0:
raise requests.Timeout()
# return json.JSONDecoder().raw_decode(response.decode())[0]
return json.loads(response.decode())


class CaseValidateUser:
"""REST Client that operates directly over TCP/IPv4 stack, with HTTP"""

REQUEST_PATTERN = '{"user_id":%i,"text":"%s"}'

def __init__(self, uri: str = '127.0.0.1', port: int = 8545, identity: int = PROCESS_ID) -> None:
self.identity = identity
self.expected = -1
self.uri = uri
self.port = port
self.sock = None
self.payload = ''.join(random.choices(
string.ascii_uppercase, k=80))

def __call__(self, **kwargs) -> int:
self.send(**kwargs)
return self.recv()

def send(self, *, user_id: Optional[int] = None, session_id: Optional[int] = None) -> int:
user_id = random.randint(1, 1000) if user_id is None else user_id
session_id = random.randint(
1, 1000) if session_id is None else session_id
jsonrpc = self.REQUEST_PATTERN % (user_id, self.payload)
path = 'GET /validate_session/' + str(session_id)
headers = HTTP_HEADERS % (path, len(jsonrpc))
self.expected = (user_id ^ session_id) % 23 == 0
self.sock = make_tcp_socket(self.uri, self.port) if socket_is_closed(
self.sock) else self.sock
self.sock.send((headers + jsonrpc).encode())

def recv(self) -> int:
# self.sock.settimeout(0.01)
response_bytes = self.sock.recv(4096).decode()
self.sock.settimeout(None)
response = json.loads(
response_bytes[response_bytes.index("\r\n\r\n"):])
assert 'error' not in response, response['error']
received = response['response']
assert self.expected == received, 'Wrong Answer'
return received
2 changes: 1 addition & 1 deletion examples/login/ucall_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ int main(int argc, char** argv) {
std::printf("- silent\n");

// Add all the callbacks we need
ucall_add_procedure(server, "validate_session", &validate_session, nullptr);
ucall_add_procedure(server, "validate_session", &validate_session, request_type_t::post_k, nullptr);

if (config.max_threads > 1) {
std::vector<std::thread> threads;
Expand Down
Loading

0 comments on commit 5e676c3

Please sign in to comment.