Skip to content

Commit

Permalink
add function to send 103 http response code (VKCOM#560)
Browse files Browse the repository at this point in the history
New function `send_http_103_early_hints($headers)` sends HTTP 103 response with passed headers immediately without buffering
  • Loading branch information
astrophysik authored Aug 9, 2022
1 parent 56e7ea1 commit fbe570b
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 19 deletions.
1 change: 1 addition & 0 deletions builtin-functions/_functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ function ob_get_level () ::: int;

function header ($str ::: string, $replace ::: bool = true, $http_response_code ::: int = 0) ::: void;
function headers_list () ::: string[];
function send_http_103_early_hints($headers ::: string[]) ::: void;
function setcookie ($name ::: string, $value ::: string, $expire ::: int = 0, $path ::: string = '', $domain ::: string = '', $secure ::: bool = false, $http_only ::: bool = false) ::: void;
function setrawcookie ($name ::: string, $value ::: string, $expire ::: int = 0, $path ::: string = '', $domain ::: string = '', $secure ::: bool = false, $http_only ::: bool = false) ::: void;
function register_shutdown_function (callable():void $function) ::: void;
Expand Down
8 changes: 8 additions & 0 deletions runtime/interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,14 @@ array<string> f$headers_list() {
return result;
}

void f$send_http_103_early_hints(const array<string> & headers) {
string header("HTTP/1.1 103 Early Hints\r\n");
for (const auto & h : headers) {
header.append(h.get_value().c_str()).append("\r\n");
}
http_send_immediate_response(header.c_str(), header.size(), "\r\n", 2);
}

void f$setrawcookie(const string &name, const string &value, int64_t expire, const string &path, const string &domain, bool secure, bool http_only) {
string date = f$gmdate(HTTP_DATE, expire);

Expand Down
2 changes: 2 additions & 0 deletions runtime/interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ void f$header(const string &str, bool replace = true, int64_t http_response_code

array<string> f$headers_list();

void f$send_http_103_early_hints(const array<string> & headers);

void f$setcookie(const string &name, const string &value, int64_t expire = 0, const string &path = string(), const string &domain = string(), bool secure = false, bool http_only = false);

void f$setrawcookie(const string &name, const string &value, int64_t expire = 0, const string &path = string(), const string &domain = string(), bool secure = false, bool http_only = false);
Expand Down
15 changes: 15 additions & 0 deletions server/php-queries.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
#include "common/precise-time.h"
#include "common/wrappers/overloaded.h"

#include "net/net-buffers.h"
#include "net/net-connections.h"

#include "runtime/allocator.h"
#include "runtime/job-workers/processing-jobs.h"
#include "runtime/rpc.h"
Expand All @@ -24,6 +27,7 @@
#include "server/php-init-scripts.h"
#include "server/php-queries-stats.h"
#include "server/php-runner.h"
#include "server/php-worker.h"

#define MAX_NET_ERROR_LEN 128

Expand Down Expand Up @@ -953,6 +957,17 @@ void db_run_query(int host_num, const char *request, int request_len, int timeou
}
}

void http_send_immediate_response(const char *headers, int headers_len, const char *body, int body_len) {
php_assert(active_worker != nullptr);
if (active_worker->mode == http_worker) {
write_out(&active_worker->conn->Out, headers, headers_len);
write_out(&active_worker->conn->Out, body, body_len);
flush_connection_output(active_worker->conn);
} else {
php_warning("Early hints available only from HTTP worker");
}
}

slot_id_t rpc_send_query(int host_num, char *request, int request_size, int timeout_ms) {
net_query_t *query = create_net_query();
if (query == nullptr) {
Expand Down
1 change: 1 addition & 0 deletions server/php-queries.h
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ void job_set_result(int exit_code);

void script_error();
void finish_script(int exit_code);
void http_send_immediate_response(const char *headers, int headers_len, const char *body, int body_len);
int rpc_connect_to(const char *host_name, int port);
slot_id_t rpc_send_query(int host_num, char *request, int request_len, int timeout_ms);
void wait_net_events(int timeout_ms);
Expand Down
36 changes: 18 additions & 18 deletions tests/python/lib/http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@

from .colors import blue

class RawResponse:
def __init__(self, raw_bytes):
self.raw_bytes = raw_bytes
head, _, body = raw_bytes.partition(b"\r\n\r\n")

first_line, _, headers_data = head.partition(b"\r\n")
self.method, _, status = first_line.partition(b' ')
status_code, _, status_line = status.partition(b' ')
self.status_code = int(status_code)
self.reason = status_line
self.headers = {}
self.content = body

for i in headers_data.splitlines():
k, v = i.split(b': ')
self.headers[k.decode()] = v.decode()


def send_http_request(port, uri='/', method='GET', **kwargs):
session = requests.session()
Expand Down Expand Up @@ -45,21 +62,4 @@ def send_http_request_raw(port, request):
print(*response_bytes.splitlines(True), sep="\n")
print("=============================")

class _RawResponse:
def __init__(self, raw_bytes):
self.raw_bytes = raw_bytes
head, _, body = raw_bytes.partition(b"\r\n\r\n")

first_line, _, headers_data = head.partition(b"\r\n")
self.method, _, status = first_line.partition(b' ')
status_code, _, status_line = status.partition(b' ')
self.status_code = int(status_code)
self.reason = status_line
self.headers = {}
self.content = body

for i in headers_data.splitlines():
k, v = i.split(b': ')
self.headers[k.decode()] = v.decode()

return _RawResponse(response_bytes)
return RawResponse(response_bytes)
6 changes: 5 additions & 1 deletion tests/python/tests/http_server/php/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,9 @@ class A {
} else if ($_SERVER["PHP_SELF"] === "/pid") {
echo "pid=" . posix_getpid();
} else {
echo "Hello world!";
if ($_GET["hints"] === "yes") {
send_http_103_early_hints(["Content-Type: text/plain or application/json", "Link: </script.js>; rel=preload; as=script"]);
sleep(2);
}
echo "Hello world!";
}
32 changes: 32 additions & 0 deletions tests/python/tests/http_server/test_request.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import urllib
import socket

from python.lib.testcase import KphpServerAutoTestCase
from python.lib.http_client import RawResponse


class TestRequest(KphpServerAutoTestCase):
Expand All @@ -9,6 +11,36 @@ def test_send_simple_request_path(self):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Hello world!")

def test_send_request_with_early_hints(self):
def print_log(header, body):
print(header)
print("=============================")
print(*body.splitlines(True), sep="\n")
print("=============================")
msg = b"GET /?hints=yes HTTP/1.1\n\n"
print_log("\nSending Raw HTTP request", msg)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(('127.0.0.1', self.kphp_server.http_port))
s.send(msg)
s.settimeout(0.1)
early_hints = RawResponse(s.recv(118))
s.settimeout(1)
pause = False
try:
s.recv(4096)
except OSError:
pause = True
self.assertTrue(pause)
s.settimeout(None)
response = RawResponse(s.recv(4096))
print_log("\nGot Raw HTTP 103 hints", early_hints.raw_bytes)
print_log("\nGot Raw HTTP response", response.raw_bytes)
self.assertEqual(early_hints.status_code, 103)
self.assertEqual(early_hints.headers["Content-Type"], "text/plain or application/json")
self.assertEqual(early_hints.headers["Link"], "</script.js>; rel=preload; as=script")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Hello world!")

def test_send_request_with_query(self):
response = self.kphp_server.http_request_raw([b"GET /status?foo=bar&baz=gaz HTTP/1.1"])
self.assertEqual(response.status_code, 200)
Expand Down

0 comments on commit fbe570b

Please sign in to comment.