Skip to content

Commit 76e032d

Browse files
authored
http client support uds (#461)
1 parent cd03fc0 commit 76e032d

File tree

4 files changed

+84
-11
lines changed

4 files changed

+84
-11
lines changed

fs/test/CMakeLists.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ add_executable(test-filecopy test_filecopy.cpp)
1212
target_link_libraries(test-filecopy PRIVATE photon_shared)
1313
add_test(NAME test-filecopy COMMAND $<TARGET_FILE:test-filecopy>)
1414

15-
add_executable(test-throttled test_throttledfile.cpp)
16-
target_link_libraries(test-throttled PRIVATE photon_shared)
17-
add_test(NAME test-throttled COMMAND $<TARGET_FILE:test-throttled>)
15+
add_executable(test-throttle-file test_throttledfile.cpp)
16+
target_link_libraries(test-throttle-file PRIVATE photon_shared)
17+
add_test(NAME test-throttle-file COMMAND $<TARGET_FILE:test-throttle-file>)

net/http/client.cpp

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class PooledDialer {
3838
bool tls_ctx_ownership;
3939
std::unique_ptr<ISocketClient> tcpsock;
4040
std::unique_ptr<ISocketClient> tlssock;
41+
std::unique_ptr<ISocketClient> udssock;
4142
std::unique_ptr<Resolver> resolver;
4243

4344
//etsocket seems not support multi thread very well, use tcp_socket now. need to find out why
@@ -49,6 +50,7 @@ class PooledDialer {
4950
auto tls_cli = new_tls_client(tls_ctx, new_tcp_socket_client(), true);
5051
tcpsock.reset(new_tcp_socket_pool(tcp_cli, -1, true));
5152
tlssock.reset(new_tcp_socket_pool(tls_cli, -1, true));
53+
udssock.reset(new_uds_client());
5254
}
5355

5456
~PooledDialer() {
@@ -63,6 +65,8 @@ class PooledDialer {
6365
ISocketStream* dial(const T& x, uint64_t timeout = -1UL) {
6466
return dial(x.host_no_port(), x.port(), x.secure(), timeout);
6567
}
68+
69+
ISocketStream* dial(std::string_view uds_path, uint64_t timeout = -1UL);
6670
};
6771

6872
ISocketStream* PooledDialer::dial(std::string_view host, uint16_t port, bool secure, uint64_t timeout) {
@@ -96,6 +100,14 @@ ISocketStream* PooledDialer::dial(std::string_view host, uint16_t port, bool sec
96100
return nullptr;
97101
}
98102

103+
ISocketStream* PooledDialer::dial(std::string_view uds_path, uint64_t timeout) {
104+
udssock->timeout(timeout);
105+
auto stream = udssock->connect(uds_path.data());
106+
if (!stream)
107+
LOG_ERRNO_RETURN(0, nullptr, "failed to dial to unix socket `", uds_path);
108+
return stream;
109+
}
110+
99111
constexpr uint64_t code3xx() { return 0; }
100112
template<typename...Ts>
101113
constexpr uint64_t code3xx(uint64_t x, Ts...xs)
@@ -164,9 +176,13 @@ class ClientImpl : public Client {
164176
if (tmo.timeout() == 0)
165177
LOG_ERROR_RETURN(ETIMEDOUT, ROUNDTRIP_FAILED, "connection timedout");
166178
auto &req = op->req;
167-
auto s = (m_proxy && !m_proxy_url.empty())
168-
? m_dialer.dial(m_proxy_url, tmo.timeout())
169-
: m_dialer.dial(req, tmo.timeout());
179+
ISocketStream* s;
180+
if (m_proxy && !m_proxy_url.empty())
181+
s = m_dialer.dial(m_proxy_url, tmo.timeout());
182+
else if (!op->uds_path.empty())
183+
s = m_dialer.dial(op->uds_path, tmo.timeout());
184+
else
185+
s = m_dialer.dial(req, tmo.timeout());
170186
if (!s) {
171187
if (errno == ECONNREFUSED || errno == ENOENT) {
172188
LOG_ERROR_RETURN(0, ROUNDTRIP_FAST_RETRY, "connection refused")

net/http/client.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ class Client : public Object {
4848
uint16_t retry = 5; // default retry: 5 at most
4949
Response resp; // response
5050
int status_code = -1; // status code in response
51-
bool enable_proxy;
51+
bool enable_proxy = false;
52+
std::string_view uds_path; // If set, Unix Domain Socket will be used instead of TCP.
53+
// URL should still be the format of http://localhost/xxx
5254
IStream* body_stream = nullptr; // use body_stream as body
5355
using BodyWriter = Delegate<ssize_t, Request*>; // or call body_writer if body_stream
5456
BodyWriter body_writer = {}; // is not set
@@ -68,6 +70,11 @@ class Client : public Object {
6870
if (!_client) return -1;
6971
return _client->call(this);
7072
}
73+
int call(std::string_view unix_socket_path) {
74+
if (!_client) return -1;
75+
uds_path = unix_socket_path;
76+
return _client->call(this);
77+
}
7178

7279
protected:
7380
Client* _client;
@@ -83,15 +90,15 @@ class Client : public Object {
8390
Operation(uint16_t buf_size) : req(_buf, buf_size) {}
8491
};
8592

86-
Operation* new_operation(Verb v, std::string_view url, uint16_t buf_size = 64 * 1024 - 1) {
93+
Operation* new_operation(Verb v, std::string_view url, uint16_t buf_size = UINT16_MAX) {
8794
return Operation::create(this, v, url, buf_size);
8895
}
8996

90-
Operation* new_operation(uint16_t buf_size = 64 * 1024 - 1) {
97+
Operation* new_operation(uint16_t buf_size = UINT16_MAX) {
9198
return Operation::create(this, buf_size);
9299
}
93100

94-
template<uint16_t BufferSize>
101+
template<uint16_t BufferSize = UINT16_MAX>
95102
class OperationOnStack : public Operation {
96103
char _buf[BufferSize];
97104
public:

net/http/test/client_function_test.cpp

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@ int timeout_writer(void *self, IStream* stream) {
6161
return 0;
6262
}
6363

64+
class SimpleHandler : public http::HTTPHandler {
65+
public:
66+
int handle_request(http::Request& req, http::Response& resp, std::string_view) {
67+
std::string url_path(req.target());
68+
resp.set_result(200);
69+
resp.headers.content_length(url_path.size());
70+
resp.headers.insert("Content-Type", "application/octet-stream");
71+
auto n = resp.write(url_path.data(), url_path.size());
72+
if (n != (ssize_t) url_path.size()) {
73+
LOG_ERRNO_RETURN(0, -1, "send body failed");
74+
}
75+
return 0;
76+
}
77+
};
78+
6479
TEST(http_client, get) {
6580
system("mkdir -p /tmp/ease_ut/http_test/");
6681
system("echo \"this is a http_client request body text for socket stream\" > /tmp/ease_ut/http_test/ease-httpclient-gettestfile");
@@ -523,6 +538,41 @@ TEST(DISABLED_http_client, ipv6) { // make sure runing in a ipv6-ready environm
523538
EXPECT_EQ(200, op->resp.status_code());
524539
}
525540

541+
TEST(http_client, unix_socket) {
542+
const char* uds_path = "test-http-client.sock";
543+
544+
auto http_server = new_http_server();
545+
DEFER(delete http_server);
546+
http_server->add_handler(new SimpleHandler, true, "/simple-api");
547+
548+
auto socket_server = new_uds_server(true);
549+
DEFER(delete socket_server);
550+
551+
socket_server->set_handler(http_server->get_connection_handler());
552+
ASSERT_EQ(0, socket_server->bind(uds_path));
553+
ASSERT_EQ(0, socket_server->listen());
554+
ASSERT_EQ(0, socket_server->start_loop(false));
555+
556+
auto client = new_http_client();
557+
DEFER(delete client);
558+
559+
Client::OperationOnStack<> op(client, Verb::GET, "http://localhost/simple-api");
560+
int ret = op.call(uds_path);
561+
ASSERT_EQ(0, ret);
562+
ASSERT_EQ(200, op.resp.status_code());
563+
564+
char buf[UINT16_MAX];
565+
ssize_t n = op.resp.read(buf, op.resp.body_size());
566+
ASSERT_EQ(n, (ssize_t) op.resp.body_size());
567+
LOG_INFO(buf);
568+
569+
// A wrong hostname or HTTPS doesn't have effect on unix socket
570+
Client::OperationOnStack<> op2(client, Verb::GET, "https://www.wrong.hostname/simple-api");
571+
ret = op2.call(uds_path);
572+
ASSERT_EQ(0, ret);
573+
ASSERT_EQ(200, op2.resp.status_code());
574+
}
575+
526576
TEST(url, url_escape_unescape) {
527577
EXPECT_EQ(
528578
url_escape("?a=x:b&b=cd&c= feg&d=2/1[+]@alibaba.com&e='!bad';"),
@@ -582,5 +632,5 @@ int main(int argc, char** arg) {
582632
#endif
583633
set_log_output_level(ALOG_INFO);
584634
::testing::InitGoogleTest(&argc, arg);
585-
LOG_DEBUG("test result:`", RUN_ALL_TESTS());
635+
return RUN_ALL_TESTS();
586636
}

0 commit comments

Comments
 (0)