This is an automated email from the ASF dual-hosted git repository.
serverglen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git
The following commit(s) were added to refs/heads/master by this push:
new 7ceb8ed9 Support 100-continue of server and remove expect header of
request (#2499)
7ceb8ed9 is described below
commit 7ceb8ed9d97e870103aa5016212d6ac529534a44
Author: Bright Chen <[email protected]>
AuthorDate: Wed Jan 10 10:17:09 2024 +0800
Support 100-continue of server and remove expect header of request (#2499)
---
src/brpc/details/http_message.cpp | 5 +
src/brpc/policy/http_rpc_protocol.cpp | 26 +++++
src/brpc/policy/http_rpc_protocol.h | 2 +
test/brpc_http_rpc_protocol_unittest.cpp | 167 ++++++++++++++++++++++++++++++-
test/echo.proto | 1 +
5 files changed, 199 insertions(+), 2 deletions(-)
diff --git a/src/brpc/details/http_message.cpp
b/src/brpc/details/http_message.cpp
index 0a6b4076..252dc8cc 100644
--- a/src/brpc/details/http_message.cpp
+++ b/src/brpc/details/http_message.cpp
@@ -559,6 +559,11 @@ void MakeRawHttpRequest(butil::IOBuf* request,
os << "Content-Length: " << (content ? content->length() : 0)
<< BRPC_CRLF;
}
+ // `Expect: 100-continue' is not supported, remove it.
+ const std::string* expect = h->GetHeader("Expect");
+ if (expect && *expect == "100-continue") {
+ h->RemoveHeader("Expect");
+ }
//rfc 7230#section-5.4:
//A client MUST send a Host header field in all HTTP/1.1 request
//messages. If the authority component is missing or undefined for
diff --git a/src/brpc/policy/http_rpc_protocol.cpp
b/src/brpc/policy/http_rpc_protocol.cpp
index 979e3861..35aa9a1f 100644
--- a/src/brpc/policy/http_rpc_protocol.cpp
+++ b/src/brpc/policy/http_rpc_protocol.cpp
@@ -127,6 +127,9 @@ CommonStrings::CommonStrings()
, AUTHORIZATION("authorization")
, ACCEPT_ENCODING("accept-encoding")
, CONTENT_ENCODING("content-encoding")
+ , CONTENT_LENGTH("content_length")
+ , EXPECT("expect")
+ , CONTINUE_100("100-continue")
, GZIP("gzip")
, CONNECTION("connection")
, KEEP_ALIVE("keep-alive")
@@ -1168,6 +1171,29 @@ ParseResult ParseHttpMessage(butil::IOBuf *source,
Socket *socket,
}
return result;
} else if (http_imsg->stage() >= HTTP_ON_HEADERS_COMPLETE) {
+ // https://datatracker.ietf.org/doc/html/rfc7231#section-5.1.1
+ // A server that receives a 100-continue expectation in an HTTP/1.0
+ // request MUST ignore that expectation.
+ //
+ // A server MAY omit sending a 100 (Continue) response if it has
+ // already received some or all of the message body for the
+ // corresponding request, or if the framing indicates that there is
+ // no message body.
+ if (http_imsg->parser().type == HTTP_REQUEST &&
+ !http_imsg->header().before_http_1_1()) {
+ const std::string* expect =
http_imsg->header().GetHeader(common->EXPECT);
+ if (expect && *expect == common->CONTINUE_100) {
+ // Send 100-continue response back.
+ butil::IOBuf resp;
+ HttpHeader header;
+ header.set_status_code(HTTP_STATUS_CONTINUE);
+ MakeRawHttpResponse(&resp, &header, NULL);
+ Socket::WriteOptions wopt;
+ wopt.ignore_eovercrowded = true;
+ socket->Write(&resp, &wopt);
+ }
+ }
+
http_imsg->CheckProgressiveRead(arg, socket);
if (socket->is_read_progressive()) {
// header part of a progressively-read http message is
complete,
diff --git a/src/brpc/policy/http_rpc_protocol.h
b/src/brpc/policy/http_rpc_protocol.h
index 05c037b1..918e69d0 100644
--- a/src/brpc/policy/http_rpc_protocol.h
+++ b/src/brpc/policy/http_rpc_protocol.h
@@ -43,6 +43,8 @@ struct CommonStrings {
std::string ACCEPT_ENCODING;
std::string CONTENT_ENCODING;
std::string CONTENT_LENGTH;
+ std::string EXPECT;
+ std::string CONTINUE_100;
std::string GZIP;
std::string CONNECTION;
std::string KEEP_ALIVE;
diff --git a/test/brpc_http_rpc_protocol_unittest.cpp
b/test/brpc_http_rpc_protocol_unittest.cpp
index e0948399..b4e0547c 100644
--- a/test/brpc_http_rpc_protocol_unittest.cpp
+++ b/test/brpc_http_rpc_protocol_unittest.cpp
@@ -24,11 +24,17 @@
#include <string>
#include <sys/ioctl.h>
#include <sys/socket.h>
+#include <butil/build_config.h> // OS_MACOSX
+#if defined(OS_MACOSX)
+#include <sys/event.h>
+#endif
#include <gtest/gtest.h>
#include <gflags/gflags.h>
#include <google/protobuf/text_format.h>
#include <unistd.h>
#include <butil/strings/string_number_conversions.h>
+#include <brpc/policy/http_rpc_protocol.h>
+#include <butil/base64.h>
#include "brpc/http_method.h"
#include "butil/iobuf.h"
#include "butil/logging.h"
@@ -47,6 +53,7 @@
#include "json2pb/json_to_pb.h"
#include "brpc/details/method_status.h"
#include "brpc/rpc_dump.h"
+#include "bthread/unstable.h"
namespace brpc {
DECLARE_bool(rpc_dump);
@@ -1785,8 +1792,7 @@ class HttpServiceImpl : public ::test::HttpService {
::test::HttpResponse*,
::google::protobuf::Closure* done) override {
brpc::ClosureGuard done_guard(done);
- brpc::Controller* cntl =
- static_cast<brpc::Controller*>(cntl_base);
+ brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);
ASSERT_EQ(cntl->http_request().method(), brpc::HTTP_METHOD_HEAD);
const std::string* index =
cntl->http_request().GetHeader("x-db-index");
ASSERT_NE(nullptr, index);
@@ -1801,6 +1807,19 @@ class HttpServiceImpl : public ::test::HttpService {
EXP_RESPONSE_TRANSFER_ENCODING);
}
}
+
+ void Expect(::google::protobuf::RpcController* cntl_base,
+ const ::test::HttpRequest*,
+ ::test::HttpResponse*,
+ ::google::protobuf::Closure* done) override {
+ brpc::ClosureGuard done_guard(done);
+ brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);
+ const std::string* expect = cntl->http_request().GetHeader("Expect");
+ ASSERT_TRUE(expect != NULL);
+ ASSERT_EQ("100-continue", *expect);
+ ASSERT_EQ(cntl->http_request().method(), brpc::HTTP_METHOD_POST);
+ cntl->response_attachment().append("world");
+ }
};
TEST_F(HttpTest, http_head) {
@@ -1836,4 +1855,148 @@ TEST_F(HttpTest, http_head) {
}
}
+#define BRPC_CRLF "\r\n"
+
+void MakeHttpRequestHeaders(butil::IOBuf* out,
+ brpc::HttpHeader* h,
+ const butil::EndPoint& remote_side) {
+ butil::IOBufBuilder os;
+ os << HttpMethod2Str(h->method()) << ' ';
+ const brpc::URI& uri = h->uri();
+ uri.PrintWithoutHost(os); // host is sent by "Host" header.
+ os << " HTTP/" << h->major_version() << '.'
+ << h->minor_version() << BRPC_CRLF;
+ //rfc 7230#section-5.4:
+ //A client MUST send a Host header field in all HTTP/1.1 request
+ //messages. If the authority component is missing or undefined for
+ //the target URI, then a client MUST send a Host header field with an
+ //empty field-value.
+ //rfc 7231#sec4.3:
+ //the request-target consists of only the host name and port number of
+ //the tunnel destination, seperated by a colon. For example,
+ //Host: server.example.com:80
+ if (h->GetHeader("host") == NULL) {
+ os << "Host: ";
+ if (!uri.host().empty()) {
+ os << uri.host();
+ if (uri.port() >= 0) {
+ os << ':' << uri.port();
+ }
+ } else if (remote_side.port != 0) {
+ os << remote_side;
+ }
+ os << BRPC_CRLF;
+ }
+ if (!h->content_type().empty()) {
+ os << "Content-Type: " << h->content_type()
+ << BRPC_CRLF;
+ }
+ for (brpc::HttpHeader::HeaderIterator it = h->HeaderBegin();
+ it != h->HeaderEnd(); ++it) {
+ os << it->first << ": " << it->second << BRPC_CRLF;
+ }
+ if (h->GetHeader("Accept") == NULL) {
+ os << "Accept: */*" BRPC_CRLF;
+ }
+ // The fake "curl" user-agent may let servers return plain-text results.
+ if (h->GetHeader("User-Agent") == NULL) {
+ os << "User-Agent: brpc/1.0 curl/7.0" BRPC_CRLF;
+ }
+ const std::string& user_info = h->uri().user_info();
+ if (!user_info.empty() && h->GetHeader("Authorization") == NULL) {
+ // NOTE: just assume user_info is well formatted, namely
+ // "<user_name>:<password>". Users are very unlikely to add extra
+ // characters in this part and even if users did, most of them are
+ // invalid and rejected by http_parser_parse_url().
+ std::string encoded_user_info;
+ butil::Base64Encode(user_info, &encoded_user_info);
+ os << "Authorization: Basic " << encoded_user_info << BRPC_CRLF;
+ }
+ os << BRPC_CRLF; // CRLF before content
+ os.move_to(*out);
+}
+
+#undef BRPC_CRLF
+
+void ReadOneResponse(brpc::SocketUniquePtr& sock,
+ brpc::DestroyingPtr<brpc::policy::HttpContext>& imsg_guard) {
+#if defined(OS_LINUX)
+ ASSERT_EQ(0, bthread_fd_wait(sock->fd(), EPOLLIN));
+#elif defined(OS_MACOSX)
+ ASSERT_EQ(0, bthread_fd_wait(sock->fd(), EVFILT_READ));
+#endif
+
+ butil::IOPortal read_buf;
+ int64_t start_time = butil::gettimeofday_us();
+ while (true) {
+ const ssize_t nr = read_buf.append_from_file_descriptor(sock->fd(),
4096);
+ LOG(INFO) << "nr=" << nr;
+ LOG(INFO) << butil::ToPrintableString(read_buf);
+ ASSERT_TRUE(nr > 0 || (nr < 0 && errno == EAGAIN));
+ if (errno == EAGAIN) {
+ ASSERT_LT(butil::gettimeofday_us(), start_time + 1000000L) << "Too
long!";
+ bthread_usleep(1000);
+ continue;
+ }
+ brpc::ParseResult pr = brpc::policy::ParseHttpMessage(&read_buf,
sock.get(), false, NULL);
+ ASSERT_TRUE(pr.error() == brpc::PARSE_ERROR_NOT_ENOUGH_DATA ||
pr.is_ok());
+ if (pr.is_ok()) {
+
imsg_guard.reset(static_cast<brpc::policy::HttpContext*>(pr.message()));
+ break;
+ }
+ }
+ ASSERT_TRUE(read_buf.empty());
+}
+
+TEST_F(HttpTest, http_expect) {
+ const int port = 8923;
+ brpc::Server server;
+ HttpServiceImpl svc;
+ EXPECT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE));
+ EXPECT_EQ(0, server.Start(port, NULL));
+
+ butil::EndPoint ep;
+ ASSERT_EQ(0, butil::str2endpoint("127.0.0.1:8923", &ep));
+ brpc::SocketOptions options;
+ options.remote_side = ep;
+ brpc::SocketId id;
+ ASSERT_EQ(0, brpc::Socket::Create(options, &id));
+ brpc::SocketUniquePtr sock;
+ ASSERT_EQ(0, brpc::Socket::Address(id, &sock));
+
+ butil::IOBuf content;
+ content.append("hello");
+ brpc::HttpHeader header;
+ header.set_method(brpc::HTTP_METHOD_POST);
+ header.uri().set_path("/HttpService/Expect");
+ header.SetHeader("Expect", "100-continue");
+ header.SetHeader("Content-Length", std::to_string(content.size()));
+ butil::IOBuf header_buf;
+ MakeHttpRequestHeaders(&header_buf, &header, ep);
+ LOG(INFO) << butil::ToPrintableString(header_buf);
+ butil::IOBuf request_buf(header_buf);
+ request_buf.append(content);
+
+ ASSERT_EQ(0, sock->Write(&header_buf));
+ int64_t start_time = butil::gettimeofday_us();
+ while (sock->fd() < 0) {
+ bthread_usleep(1000);
+ ASSERT_LT(butil::gettimeofday_us(), start_time + 1000000L) << "Too
long!";
+ }
+ // 100 Continue
+ brpc::DestroyingPtr<brpc::policy::HttpContext> imsg_guard;
+ ReadOneResponse(sock, imsg_guard);
+ ASSERT_EQ(imsg_guard->header().status_code(), brpc::HTTP_STATUS_CONTINUE);
+
+ ASSERT_EQ(0, sock->Write(&content));
+ // 200 Ok
+ ReadOneResponse(sock, imsg_guard);
+ ASSERT_EQ(imsg_guard->header().status_code(), brpc::HTTP_STATUS_OK);
+
+ ASSERT_EQ(0, sock->Write(&request_buf));
+ // 200 Ok
+ ReadOneResponse(sock, imsg_guard);
+ ASSERT_EQ(imsg_guard->header().status_code(), brpc::HTTP_STATUS_OK);
+}
+
} //namespace
diff --git a/test/echo.proto b/test/echo.proto
index 6de4db6c..d7573fc6 100644
--- a/test/echo.proto
+++ b/test/echo.proto
@@ -90,6 +90,7 @@ service NacosNamingService {
service HttpService {
rpc Head(HttpRequest) returns (HttpResponse);
+ rpc Expect(HttpRequest) returns (HttpResponse);
}
enum State0 {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]