-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
388 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# API | ||
|
||
## `curl` (async) | ||
|
||
With `curl`, you can interact with network resources directly. | ||
|
||
```ts | ||
async function curl(url: string, args: CurlArgs) => CurlResponse | ||
|
||
type CurlArgs = { | ||
method?: "GET" | "POST" | "DELETE" | "HEAD" | "OPTIONS" | "PUT" | "PATCH", | ||
headers?: object, | ||
data?: string, // ignored if `json` exists | ||
json?: JSON, | ||
binary?: bool, | ||
timeout?: uint64 // milliseconds | ||
} | ||
|
||
type CurlResponse = { | ||
status: number, | ||
data: string, | ||
} | ||
``` | ||
- If `args.binary` is `true`, then `response.data` will be a base64-encoded representation of the original data. | ||
- The precision of `args.timeout` is 50ms. | ||
**Example** POST w/ JSON: | ||
```js | ||
# Basic usage | ||
curl("https://httpbin.org/post", { | ||
json: {arr: [1,2,3]} | ||
}).then(r => { | ||
console.log(r.status, r.status==200) | ||
return JSON.parse(r.data) | ||
}).then(j => console.log(j.data)) | ||
|
||
# OpenAI | ||
curl("https://api.openai.com/v1/chat/completions", { | ||
headers: { "Authorization": "Bearer $OPENAI_API_KEY" }, | ||
json: { | ||
"model": "gpt-4o-mini", | ||
"messages": [{ | ||
"role": "system", | ||
"content": "You are a poetic assistant, skilled in explaining complex programming concepts with creative flair." | ||
}, { | ||
"role": "user", | ||
"content": "Compose a poem that explains the concept of recursion in programming." | ||
}] | ||
} | ||
}).then(r => JSON.parse(r.data)) | ||
.then(j => console.log(j)) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#pragma once | ||
|
||
#include <curl/curl.h> | ||
#include <functional> | ||
#include <shared_mutex> | ||
#include <thread> | ||
|
||
class CurlMultiManager { | ||
public: | ||
using Callback = std::function<void(CURLcode, CURL *, const std::string &)>; | ||
using HandleData = std::pair<CURL *, Callback>; | ||
|
||
static CurlMultiManager &shared(); | ||
CurlMultiManager(); | ||
~CurlMultiManager(); | ||
void add(CURL *easy, CurlMultiManager::Callback cb); | ||
|
||
private: | ||
CURLM *multi; | ||
std::thread worker_thread; | ||
int controlfd[2]; | ||
std::shared_mutex m; | ||
std::unordered_map<CURL *, std::string> buf; | ||
std::unordered_map<CURL *, Callback> cb; | ||
|
||
void run(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
add_executable(preview preview.cpp) | ||
target_link_libraries(preview WebviewCandidateWindow) | ||
target_include_directories(preview PRIVATE "${PROJECT_SOURCE_DIR}/include") | ||
|
||
if(APPLE) | ||
add_executable(preview MACOSX_BUNDLE preview.cpp) | ||
target_compile_options(preview PRIVATE "-Wno-auto-var-id" "-ObjC++") | ||
else() | ||
add_executable(preview preview.cpp) | ||
endif() | ||
|
||
target_link_libraries(preview WebviewCandidateWindow) | ||
target_include_directories(preview PRIVATE "${PROJECT_SOURCE_DIR}/include") | ||
|
||
add_dependencies(preview GenerateHTML) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
#include "curl.hpp" | ||
#include <atomic> | ||
#include <cassert> | ||
#include <errno.h> | ||
#include <mutex> | ||
#include <stdexcept> | ||
#include <thread> | ||
#include <unistd.h> | ||
|
||
static size_t _on_data_cb(char *data, size_t size, size_t nmemb, | ||
std::string *outbuf); | ||
static bool write_char_strong(int fd, char c); | ||
static bool read_char_strong(int fd, char *c); | ||
std::mutex m; | ||
std::atomic<bool> running; | ||
|
||
CurlMultiManager &CurlMultiManager::shared() { | ||
static CurlMultiManager instance; | ||
return instance; | ||
} | ||
|
||
CurlMultiManager::CurlMultiManager() { | ||
bool expected = false; | ||
if (!running.compare_exchange_strong(expected, true)) { | ||
throw std::runtime_error("should only run one curl manager"); | ||
} | ||
if (pipe(controlfd) < 0) { | ||
throw std::runtime_error("failed to create curl control pipe"); | ||
} | ||
curl_global_init(CURL_GLOBAL_ALL); | ||
multi = curl_multi_init(); | ||
worker_thread = std::thread(&CurlMultiManager::run, this); | ||
} | ||
|
||
CurlMultiManager::~CurlMultiManager() { | ||
write_char_strong(controlfd[1], 'q'); | ||
if (worker_thread.joinable()) { | ||
worker_thread.join(); | ||
} | ||
curl_multi_cleanup(multi); | ||
curl_global_cleanup(); | ||
close(controlfd[0]); | ||
close(controlfd[1]); | ||
running.store(false); | ||
} | ||
|
||
void CurlMultiManager::add(CURL *easy, CurlMultiManager::Callback callback) { | ||
{ | ||
std::unique_lock g(m); | ||
buf[easy] = ""; | ||
cb[easy] = callback; | ||
} | ||
curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, _on_data_cb); | ||
curl_easy_setopt(easy, CURLOPT_WRITEDATA, &buf[easy]); | ||
curl_multi_add_handle(multi, easy); | ||
std::atomic_thread_fence(std::memory_order_release); | ||
write_char_strong(controlfd[1], 'a'); | ||
} | ||
|
||
void CurlMultiManager::run() { | ||
while (true) { | ||
int still_running = 0; | ||
int ret; | ||
ret = curl_multi_perform(multi, &still_running); | ||
int numfds; | ||
struct curl_waitfd wfd; | ||
wfd.fd = controlfd[0]; | ||
wfd.events = CURL_WAIT_POLLIN; | ||
wfd.revents = 0; | ||
// NOTE: poll does not return when any of the easy handle's | ||
// timeout expires. By setting poll's timeout to 50ms, we | ||
// effectively set the timeout precision to 50ms. | ||
curl_multi_poll(multi, &wfd, 1, 50, &numfds); | ||
std::atomic_thread_fence(std::memory_order_acquire); | ||
if (wfd.revents) { | ||
char cmd; | ||
read_char_strong(controlfd[0], &cmd); | ||
switch (cmd) { | ||
case 'q': // quit | ||
return; | ||
case 'a': // added a new handle | ||
break; | ||
default: | ||
assert(false && "unreachable"); | ||
} | ||
} | ||
CURLMsg *msg; | ||
int msgs_left; | ||
while ((msg = curl_multi_info_read(multi, &msgs_left))) { | ||
if (msg->msg == CURLMSG_DONE) { | ||
CURL *easy = msg->easy_handle; | ||
CURLcode res = msg->data.result; | ||
{ | ||
// do this because the cb might be slow | ||
std::shared_lock g(m); | ||
try { | ||
cb[easy](res, easy, buf[easy]); | ||
} catch (...) { | ||
assert(false && "curl callback must not throw!"); | ||
} | ||
} | ||
curl_multi_remove_handle(multi, easy); | ||
curl_easy_cleanup(easy); | ||
{ | ||
std::unique_lock g(m); | ||
cb.erase(easy); | ||
buf.erase(easy); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
size_t _on_data_cb(char *data, size_t size, size_t nmemb, std::string *outbuf) { | ||
size_t realsize = size * nmemb; | ||
outbuf->append(data, realsize); | ||
return realsize; | ||
} | ||
|
||
static bool write_char_strong(int fd, char c) { | ||
ssize_t ret; | ||
do { | ||
ret = write(fd, &c, 1); | ||
} while (ret == -1 && errno == EINTR); | ||
return ret == 1; | ||
} | ||
|
||
static bool read_char_strong(int fd, char *c) { | ||
ssize_t ret; | ||
do { | ||
ret = read(fd, c, 1); | ||
} while (ret == -1 && errno == EINTR); | ||
return ret == 1; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.