|
| 1 | +// trpc/server/http_sse/http_sse_server.cc |
| 2 | +// |
| 3 | +// SSE-specialized HTTP service based on HttpService::HandleTransportMessage. |
| 4 | +// Minimal SSE-specific modifications: detect SSE requests and enable SSE headers + streaming. |
| 5 | +// Other logic (filters, timeout, dispatch, error handling) copied from HttpService. |
| 6 | +// |
| 7 | + |
| 8 | +#include "trpc/server/http_sse/http_sse_service.h" |
| 9 | +#include "trpc/codec/http/http_protocol.h" |
| 10 | +#include "trpc/codec/http_sse/http_sse_proto_checker.h" // IsValidSseRequest |
| 11 | +#include "trpc/util/deferred.h" |
| 12 | +#include "trpc/util/log/logging.h" |
| 13 | +#include "trpc/util/time.h" |
| 14 | + |
| 15 | +namespace trpc { |
| 16 | + |
| 17 | +void HttpSseService::HandleTransportMessage(STransportReqMsg* recv, STransportRspMsg** send) noexcept { |
| 18 | + ServerContextPtr& context = recv->context; |
| 19 | + |
| 20 | + // request / response protocol objects (same as HttpService) |
| 21 | + http::RequestPtr& req = |
| 22 | + static_cast<HttpRequestProtocol*>(context->GetRequestMsg().get())->request; // NOLINT: safe unchecked cast |
| 23 | + http::Response& rsp = |
| 24 | + static_cast<HttpResponseProtocol*>(context->GetResponseMsg().get())->response; // NOLINT: safe unchecked cast |
| 25 | + rsp.SetHeaderOnly(req->GetMethodType() == http::HEAD); |
| 26 | + |
| 27 | + // attach request/response pointers to context for filters/handlers |
| 28 | + context->SetRequestData(req.get()); |
| 29 | + context->SetResponseData(&rsp); |
| 30 | + |
| 31 | + // set encode-type and function name, and compute real timeout |
| 32 | + context->SetReqEncodeType(MimeToEncodeType(req->GetHeader(http::kHeaderContentType))); |
| 33 | + context->SetFuncName(req->GetRouteUrl()); |
| 34 | + context->SetRealTimeout(); |
| 35 | + |
| 36 | + // configure request stream default deadline (for blocking reads) |
| 37 | + auto& req_stream = req->GetStream(); |
| 38 | + req_stream.SetDefaultDeadline(trpc::ReadSteadyClock() + std::chrono::milliseconds(context->GetTimeout())); |
| 39 | + |
| 40 | + // route lookup |
| 41 | + std::string uri_path = trpc::http::NormalizeUrl(req->GetRouteUrlView()); |
| 42 | + http::HandlerBase* handler = routes_.GetHandler(uri_path, req); |
| 43 | + |
| 44 | + // If the handler is a stream handler, check SSE specifics and handle accordingly |
| 45 | + if (handler && handler->IsStream()) { |
| 46 | + // detect SSE request: Accept contains "text/event-stream" and method is GET |
| 47 | + bool is_sse = IsValidSseRequest(req.get()); |
| 48 | + |
| 49 | + if (is_sse) { |
| 50 | + TRPC_LOG_INFO("HttpSseService: detected SSE request for path=" << uri_path); |
| 51 | + |
| 52 | + // Enable streaming on response |
| 53 | + rsp.EnableStream(context.get()); |
| 54 | + |
| 55 | + // Pre-set SSE headers on the response so handler sees them by default |
| 56 | + // These are the canonical SSE headers. Handler may override if needed. |
| 57 | + rsp.SetMimeType("text/event-stream"); |
| 58 | + rsp.SetHeader("Cache-Control", "no-cache"); |
| 59 | + rsp.SetHeader("Connection", "keep-alive"); |
| 60 | + // allow CORS by default (matches codec helper earlier) |
| 61 | + //rsp.SetHeader("Access-Control-Allow-Origin", "*"); |
| 62 | + //rsp.SetHeader("Access-Control-Allow-Headers", "Cache-Control"); |
| 63 | + // use chunked transfer encoding for streaming responses |
| 64 | + rsp.SetHeader(http::kHeaderTransferEncoding, http::kTransferEncodingChunked); |
| 65 | + |
| 66 | + // Call handler (streaming mode). Handler should write events via rsp.GetStream() or use your SseStreamWriter. |
| 67 | + Handle(uri_path, handler, context, req, rsp, send); |
| 68 | + |
| 69 | + // After handler returns, close stream and check possible stream-reset |
| 70 | + try { |
| 71 | + rsp.GetStream().Close(); |
| 72 | + } catch (...) { |
| 73 | + } |
| 74 | + |
| 75 | + if (context->GetStatus().StreamRst()) { |
| 76 | + TRPC_FMT_TRACE("{} {}, error: {}", req->GetMethod(), req->GetRouteUrlView(), context->GetStatus().ToString()); |
| 77 | + context->CloseConnection(); |
| 78 | + } |
| 79 | + |
| 80 | + // Release request stream |
| 81 | + req_stream.Close(); |
| 82 | + return; |
| 83 | + } |
| 84 | + // else: it's a stream handler but not SSE -> fall through to normal stream handling below |
| 85 | + } |
| 86 | + |
| 87 | + // Non-stream or non-SSE stream: follow original HttpService behavior |
| 88 | + if (!handler || !handler->IsStream()) { |
| 89 | + Status status = req_stream.AppendToRequest(req->GetMaxBodySize()); |
| 90 | + if (status.OK()) { |
| 91 | + Handle(uri_path, handler, context, req, rsp, send); |
| 92 | + } else { |
| 93 | + HandleError(context, req, rsp, status); |
| 94 | + } |
| 95 | + } else { // stream handler but not SSE |
| 96 | + rsp.EnableStream(context.get()); |
| 97 | + Handle(uri_path, handler, context, req, rsp, send); |
| 98 | + rsp.GetStream().Close(); |
| 99 | + if (context->GetStatus().StreamRst()) { |
| 100 | + TRPC_FMT_TRACE("{} {}, error: {}", req->GetMethod(), req->GetRouteUrlView(), context->GetStatus().ToString()); |
| 101 | + context->CloseConnection(); |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + // Release request stream |
| 106 | + req_stream.Close(); |
| 107 | +} |
| 108 | + |
| 109 | +Status HttpSseService::Dispatch(const std::string& path, http::HandlerBase* handler, ServerContextPtr& context, |
| 110 | + http::RequestPtr& req, http::Response& rsp) { |
| 111 | + return routes_.Handle(path, handler, context, req, rsp); |
| 112 | +} |
| 113 | + |
| 114 | +void HttpSseService::Handle(const std::string& path, http::HandlerBase* handler, ServerContextPtr& context, |
| 115 | + http::RequestPtr& req, http::Response& rsp, STransportRspMsg** send) { |
| 116 | + Deferred _([&context]() { |
| 117 | + context->SetRequestData(nullptr); |
| 118 | + context->SetResponseData(nullptr); |
| 119 | + }); |
| 120 | + |
| 121 | + // For some monitor plugins. |
| 122 | + auto msg_filter_status = GetFilterController().RunMessageServerFilters(FilterPoint::SERVER_POST_RECV_MSG, context); |
| 123 | + // For some tracing or log replay plugins. |
| 124 | + auto rpc_filter_status = GetFilterController().RunMessageServerFilters(FilterPoint::SERVER_PRE_RPC_INVOKE, context); |
| 125 | + |
| 126 | + // Rejected by filters. |
| 127 | + if (TRPC_UNLIKELY(msg_filter_status == FilterStatus::REJECT || rpc_filter_status == FilterStatus::REJECT)) { |
| 128 | + // For some tracing or log replay plugins. |
| 129 | + GetFilterController().RunMessageServerFilters(FilterPoint::SERVER_POST_RPC_INVOKE, context); |
| 130 | + // For some monitor plugins. |
| 131 | + GetFilterController().RunMessageServerFilters(FilterPoint::SERVER_PRE_SEND_MSG, context); |
| 132 | + |
| 133 | + auto& status = context->GetStatus(); |
| 134 | + status.SetFrameworkRetCode(GetDefaultServerRetCode(codec::ServerRetCode::INVOKE_UNKNOW_ERROR)); |
| 135 | + status.SetErrorMessage("filter reject"); |
| 136 | + http::Response reject_rsp; |
| 137 | + *send = trpc::object_pool::New<STransportRspMsg>(); |
| 138 | + (*send)->context = context; |
| 139 | + reject_rsp.GenerateExceptionReply(http::ResponseStatus::kForbidden, req->GetVersion(), "request reject"); |
| 140 | + std::move(reject_rsp).SerializeToString((*send)->buffer); |
| 141 | + return; |
| 142 | + } |
| 143 | + |
| 144 | + // Timeout. |
| 145 | + CheckTimeout(context); |
| 146 | + if (!context->GetStatus().OK()) { |
| 147 | + // For some tracing or log replay plugins. |
| 148 | + GetFilterController().RunMessageServerFilters(FilterPoint::SERVER_POST_RPC_INVOKE, context); |
| 149 | + // For some monitor plugins. |
| 150 | + GetFilterController().RunMessageServerFilters(FilterPoint::SERVER_PRE_SEND_MSG, context); |
| 151 | + |
| 152 | + TRPC_LOG_ERROR("CheckTimeout failed, ip: " << context->GetIp() |
| 153 | + << ", service queue_timeout: " << GetServiceAdapterOption().queue_timeout |
| 154 | + << ", client timeout:" << context->GetTimeout()); |
| 155 | + |
| 156 | + auto& status = context->GetStatus(); |
| 157 | + status.SetFrameworkRetCode(GetDefaultServerRetCode(codec::ServerRetCode::TIMEOUT_ERROR)); |
| 158 | + status.SetErrorMessage("client timeout"); |
| 159 | + http::Response timeout_rsp; |
| 160 | + static const std::string request_timeout_ex = http::JsonException(http::RequestTimeout()).ToJson(); |
| 161 | + timeout_rsp.GenerateExceptionReply(http::ResponseStatus::kGatewayTimeout, req->GetVersion(), request_timeout_ex); |
| 162 | + NoncontiguousBuffer buffer; |
| 163 | + std::move(timeout_rsp).SerializeToString(buffer); |
| 164 | + context->SendResponse(std::move(buffer)); |
| 165 | + context->CloseConnection(); |
| 166 | + return; |
| 167 | + } |
| 168 | + |
| 169 | + // Runs user handler. |
| 170 | + auto status = Dispatch(path, handler, context, req, rsp); |
| 171 | + if (context->IsResponse()) { |
| 172 | + context->SetStatus(std::move(status)); |
| 173 | + // For some tracing or log replay plugins. |
| 174 | + GetFilterController().RunMessageServerFilters(FilterPoint::SERVER_POST_RPC_INVOKE, context); |
| 175 | + // For some monitor plugins. |
| 176 | + GetFilterController().RunMessageServerFilters(FilterPoint::SERVER_PRE_SEND_MSG, context); |
| 177 | + |
| 178 | + *send = trpc::object_pool::New<STransportRspMsg>(); |
| 179 | + (*send)->context = context; |
| 180 | + |
| 181 | + if (context->CheckHandleTimeout()) { |
| 182 | + http::Response timeout_rsp; |
| 183 | + static const std::string request_handle_timeout_ex = |
| 184 | + http::JsonException(http::RequestTimeout("Request Handle Timeout")).ToJson(); |
| 185 | + timeout_rsp.GenerateExceptionReply(http::ResponseStatus::kGatewayTimeout, req->GetVersion(), |
| 186 | + request_handle_timeout_ex); |
| 187 | + std::move(timeout_rsp).SerializeToString((*send)->buffer); |
| 188 | + } else { |
| 189 | + std::move(rsp).SerializeToString((*send)->buffer); |
| 190 | + } |
| 191 | + } |
| 192 | +} |
| 193 | + |
| 194 | +void HttpSseService::HandleError(ServerContextPtr& context, http::RequestPtr& req, http::Response& rsp, |
| 195 | + const Status& status) { |
| 196 | + req->GetStream().Close(stream::HttpReadStream::ReadState::kErrorBit); |
| 197 | + if (status.GetFrameworkRetCode() == stream::kStreamStatusServerReadTimeout.GetFrameworkRetCode()) { |
| 198 | + rsp.GenerateExceptionReply(http::ResponseStatus::kRequestTimeout, req->GetVersion(), "request timeout"); |
| 199 | + } else if (status.GetFrameworkRetCode() == stream::kStreamStatusServerMessageExceedLimit.GetFrameworkRetCode()) { |
| 200 | + rsp.GenerateExceptionReply(http::ResponseStatus::kRequestEntityTooLarge, req->GetVersion(), |
| 201 | + "request entity too large"); |
| 202 | + } else { // unexpected error |
| 203 | + rsp.GenerateExceptionReply(http::ResponseStatus::kInternalServerError, req->GetVersion(), "unknown error"); |
| 204 | + } |
| 205 | + TRPC_LOG_DEBUG("HTTP read error, ip: " << context->GetIp() << ", status: " << status.ToString()); |
| 206 | + NoncontiguousBuffer buffer; |
| 207 | + std::move(rsp).SerializeToString(buffer); |
| 208 | + context->SendResponse(std::move(buffer)); |
| 209 | + context->SetRequestData(nullptr); |
| 210 | + context->SetResponseData(nullptr); |
| 211 | + context->CloseConnection(); |
| 212 | +} |
| 213 | + |
| 214 | +void HttpSseService::CheckTimeout(const ServerContextPtr& context) { |
| 215 | + uint64_t now_ms = static_cast<int64_t>(trpc::time::GetMilliSeconds()); |
| 216 | + uint64_t timeout = std::min(GetServiceAdapterOption().queue_timeout, context->GetTimeout()); |
| 217 | + |
| 218 | + if (context->GetRecvTimestamp() + timeout <= now_ms) { |
| 219 | + bool use_queue_timeout = GetServiceAdapterOption().queue_timeout < context->GetTimeout(); |
| 220 | + if (!use_queue_timeout && context->IsUseFullLinkTimeout()) { |
| 221 | + context->GetStatus().SetFrameworkRetCode( |
| 222 | + context->GetServerCodec()->GetProtocolRetCode(trpc::codec::ServerRetCode::FULL_LINK_TIMEOUT_ERROR)); |
| 223 | + context->GetStatus().SetErrorMessage("request full-link timeout."); |
| 224 | + } else { |
| 225 | + context->GetStatus().SetFrameworkRetCode( |
| 226 | + context->GetServerCodec()->GetProtocolRetCode(trpc::codec::ServerRetCode::TIMEOUT_ERROR)); |
| 227 | + context->GetStatus().SetErrorMessage("request timeout."); |
| 228 | + } |
| 229 | + } |
| 230 | +} |
| 231 | + |
| 232 | +} // namespace trpc |
| 233 | + |
0 commit comments