Skip to content

Commit e4a380e

Browse files
committed
Add URL rewrite support for HTTP static file handler, GH-2732
1 parent 4954eaa commit e4a380e

File tree

10 files changed

+252
-14
lines changed

10 files changed

+252
-14
lines changed

‎examples/http/url_rewrite.php‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
/**
3+
* URL重写功能测试
4+
*/
5+
6+
$http = new Swoole\Http\Server("0.0.0.0", 9501, SWOOLE_BASE);
7+
8+
$http->set([
9+
'enable_static_handler' => true,
10+
'document_root' => realpath(__DIR__.'/../www/'),
11+
'http_autoindex' => true,
12+
// URL重写规则配置
13+
'url_rewrite_rules' => [
14+
// 普通路径重写: 将 /api 开头的请求重写到 /static/api 目录
15+
'/api' => '/static/api',
16+
17+
// 正则表达式重写: 将 /user/123 格式的请求重写到 /static/user.html?id=123
18+
'~^/user/(\\d+)$~' => '/static/user.html?id=$1',
19+
20+
// 正则表达式重写: 将 /article/title-slug 格式的请求重写到 /static/article.html?slug=title-slug
21+
'~^/article/([\\w\\-]+)$~' => '/static/article.html?slug=$1'
22+
]
23+
]);
24+
25+
$http->on('request', function ($request, $response) {
26+
$response->header('Content-Type', 'text/plain; charset=utf-8');
27+
$response->end("动态处理: " . $request->server['request_uri']);
28+
});
29+
30+
$http->on('start', function ($server) {
31+
echo "HTTP服务器已启动,监听 0.0.0.0:9501\n";
32+
echo "URL重写功能已启用\n";
33+
echo "测试示例:\n";
34+
echo "1. 普通重写: http://localhost:9501/api/test.txt -> /static/api/test.txt\n";
35+
echo "2. 正则重写: http://localhost:9501/user/123 -> /static/user.html?id=123\n";
36+
echo "3. 正则重写: http://localhost:9501/article/test-title -> /static/article.html?slug=test-title\n";
37+
});
38+
39+
$http->start();

‎ext-src/php_swoole_library.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,8 @@ static const char* swoole_library_source_core_constant =
568568
"\n"
569569
" public const OPTION_DOCUMENT_ROOT = 'document_root';\n"
570570
"\n"
571+
" public const OPTION_URL_REWRITE_RULES = 'url_rewrite_rules';\n"
572+
"\n"
571573
" public const OPTION_HTTP_AUTOINDEX = 'http_autoindex';\n"
572574
"\n"
573575
" public const OPTION_HTTP_INDEX_FILES = 'http_index_files';\n"
@@ -8560,6 +8562,7 @@ static const char* swoole_library_source_core_server_helper =
85608562
" 'upload_max_filesize' => true,\n"
85618563
" 'enable_static_handler' => true,\n"
85628564
" 'document_root' => true,\n"
8565+
" 'url_rewrite_rules' => true,\n"
85638566
" 'http_autoindex' => true,\n"
85648567
" 'http_index_files' => true,\n"
85658568
" 'http_compression_types' => true,\n"

‎ext-src/swoole_http2_server.cc‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ static bool http2_server_is_static_file(Server *serv, HttpContext *ctx) {
189189
zval *zrequest_uri = zend_hash_str_find(Z_ARR_P(zserver), ZEND_STRL("request_uri"));
190190
if (zrequest_uri && Z_TYPE_P(zrequest_uri) == IS_STRING) {
191191
StaticHandler handler(serv, Z_STRVAL_P(zrequest_uri), Z_STRLEN_P(zrequest_uri));
192-
if (!handler.hit()) {
192+
if (!handler.try_serve()) {
193193
return false;
194194
}
195195

@@ -204,7 +204,7 @@ static bool http2_server_is_static_file(Server *serv, HttpContext *ctx) {
204204
* if http_index_files is enabled, need to search the index file first.
205205
* if the index file is found, set filename to index filename.
206206
*/
207-
if (!handler.hit_index_file()) {
207+
if (!handler.try_serve_index_file()) {
208208
return false;
209209
}
210210

@@ -1177,9 +1177,9 @@ int swoole_http2_server_parse(const std::shared_ptr<Http2Session> &client, const
11771177

11781178
// Exceeded the max_body_size, or if the stream has ended, sends RST_STREAM frame, stops receiving data
11791179
if (buffer->length + length > client->max_body_size || ctx->end_) {
1180-
http2_server_send_rst_stream(ctx, 0);
1181-
client->remove_stream(stream_id);
1182-
break;
1180+
http2_server_send_rst_stream(ctx, 0);
1181+
client->remove_stream(stream_id);
1182+
break;
11831183
}
11841184

11851185
buffer->append(buf, length);

‎ext-src/swoole_server.cc‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2367,6 +2367,29 @@ static PHP_METHOD(swoole_server, set) {
23672367
RETURN_FALSE;
23682368
}
23692369
}
2370+
/**
2371+
* [url_rewrite] rules
2372+
*/
2373+
if (php_swoole_array_get_value(vht, "url_rewrite_rules", ztmp)) {
2374+
if (ZVAL_IS_ARRAY(ztmp)) {
2375+
zval *replacement;
2376+
zend_string *pattern;
2377+
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(ztmp), pattern, replacement) {
2378+
ZVAL_DEREF(replacement);
2379+
if (Z_TYPE_P(replacement) == IS_STRING) {
2380+
serv->add_rewrite_rule(std::string(ZSTR_VAL(pattern), ZSTR_LEN(pattern)),
2381+
std::string(Z_STRVAL_P(replacement), Z_STRLEN_P(replacement)));
2382+
} else {
2383+
php_swoole_fatal_error(E_ERROR, "The `replacement` must be string");
2384+
RETURN_FALSE;
2385+
}
2386+
}
2387+
ZEND_HASH_FOREACH_END();
2388+
} else {
2389+
php_swoole_fatal_error(E_ERROR, "The `url_rewrite_rules` must be array");
2390+
RETURN_FALSE;
2391+
}
2392+
}
23702393
/**
23712394
* buffer input size
23722395
*/

‎include/swoole_server.h‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ namespace swoole {
4949

5050
namespace http_server {
5151
struct Request;
52-
}
52+
struct RewriteRule;
53+
class StaticHandler;
54+
} // namespace http_server
5355

5456
class Server;
5557
struct Manager;
@@ -1143,7 +1145,9 @@ class Server {
11431145
void add_static_handler_location(const std::string &);
11441146
void add_static_handler_index_files(const std::string &);
11451147
bool select_static_handler(const http_server::Request *request, const Connection *conn);
1148+
bool apply_rewrite_rules(http_server::StaticHandler *handler);
11461149
void add_http_compression_type(const std::string &type);
1150+
void add_rewrite_rule(const std::string &pattern, const std::string &replacement);
11471151

11481152
int create();
11491153
bool create_worker_pipes();
@@ -1663,6 +1667,8 @@ class Server {
16631667
* http static file directory
16641668
*/
16651669
std::string document_root;
1670+
1671+
std::shared_ptr<std::vector<http_server::RewriteRule>> rewrite_rules;
16661672
std::mutex lock_;
16671673
uint32_t max_connection = 0;
16681674
TimerNode *enable_accept_timer = nullptr;

‎include/swoole_static_handler.h‎

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,18 @@
2525

2626
namespace swoole {
2727
namespace http_server {
28+
29+
struct RewriteRule {
30+
std::string pattern;
31+
std::string replacement;
32+
bool is_regex;
33+
};
34+
2835
class StaticHandler {
2936
private:
3037
Server *serv;
3138
std::string request_url;
39+
std::string original_url;
3240
std::string dir_path;
3341
std::set<std::string> dir_files;
3442
std::string index_file;
@@ -50,16 +58,17 @@ class StaticHandler {
5058

5159
public:
5260
int status_code = SW_HTTP_OK;
53-
StaticHandler(Server *_server, const char *url, size_t url_length) : request_url(url, url_length) {
61+
StaticHandler(Server *_server, const char *url, size_t url_length)
62+
: request_url(url, url_length), original_url(url, url_length) {
5463
serv = _server;
5564
}
5665

5766
/**
5867
* @return true: continue to execute backwards
5968
* @return false: break static handler
6069
*/
61-
bool hit();
62-
bool hit_index_file();
70+
bool try_serve();
71+
bool try_serve_index_file();
6372

6473
bool is_modified(const std::string &date_if_modified_since) const;
6574
bool is_modified_range(const std::string &date_range) const;
@@ -100,6 +109,14 @@ class StaticHandler {
100109
return filename;
101110
}
102111

112+
const std::string &get_request_url() const {
113+
return request_url;
114+
}
115+
116+
void set_request_url(const std::string &rewritten_url) {
117+
request_url = rewritten_url;
118+
}
119+
103120
const std::string &get_boundary() {
104121
if (boundary.empty()) {
105122
boundary = std::string(SW_HTTP_SERVER_BOUNDARY_PREKEY);
@@ -127,6 +144,10 @@ class StaticHandler {
127144

128145
bool get_absolute_path();
129146

147+
const std::string &get_original_url() const {
148+
return original_url;
149+
}
150+
130151
size_t get_filesize() const {
131152
return file_stat.st_size;
132153
}

‎include/swoole_util.h‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,13 @@ static inline bool starts_with(const char *haystack, size_t l_haystack, const ch
248248
return memcmp(haystack, needle, l_needle) == 0;
249249
}
250250

251+
static inline bool starts_with(const std::string &str, const std::string &prefix) {
252+
if (prefix.size() > str.size()) {
253+
return false;
254+
}
255+
return std::equal(prefix.begin(), prefix.end(), str.begin());
256+
}
257+
251258
static inline bool ends_with(const char *haystack, size_t l_haystack, const char *needle, size_t l_needle) {
252259
if (l_needle > l_haystack) {
253260
return false;

‎scripts/format-changed-files.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ if [ ! -z "$cpp_files" ]; then
1717
echo "Formatting C/C++ files..."
1818
for file in $cpp_files; do
1919
# 额外检查确保不处理 _arginfo.h 文件
20-
if [[ "$file" != *_arginfo.h ]]; then
20+
if [[ "$file" != *_arginfo.h && "$file" != "ext-src/php_swoole_library.h" ]]; then
2121
echo " - $file"
2222
clang-format -i "$file"
2323
fi

‎src/server/static_handler.cc‎

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <dirent.h>
2222
#include <algorithm>
2323
#include <sstream>
24+
#include <regex>
2425

2526
namespace swoole {
2627
namespace http_server {
@@ -101,7 +102,9 @@ bool StaticHandler::get_absolute_path() {
101102
return true;
102103
}
103104

104-
bool StaticHandler::hit() {
105+
bool StaticHandler::try_serve() {
106+
serv->apply_rewrite_rules(this);
107+
105108
char *p = filename;
106109
const char *url = request_url.c_str();
107110
size_t url_length = request_url.length();
@@ -189,7 +192,7 @@ bool StaticHandler::hit() {
189192
return true;
190193
}
191194

192-
bool StaticHandler::hit_index_file() {
195+
bool StaticHandler::try_serve_index_file() {
193196
if (serv->http_index_files && !serv->http_index_files->empty() && is_dir()) {
194197
if (!get_dir_files()) {
195198
return false;
@@ -441,6 +444,25 @@ void StaticHandler::parse_range(const char *range, const char *if_range) {
441444
}
442445
}
443446
} // namespace http_server
447+
448+
void Server::add_rewrite_rule(const std::string &pattern, const std::string &replacement) {
449+
if (rewrite_rules == nullptr) {
450+
rewrite_rules = std::make_shared<std::vector<http_server::RewriteRule>>();
451+
}
452+
453+
http_server::RewriteRule rule;
454+
rule.replacement = replacement;
455+
456+
if (pattern.length() >= 2 && pattern.at(0) == '~' && pattern.at(pattern.length() - 1) == '~') {
457+
rule.pattern = pattern.substr(1, pattern.length() - 2);
458+
rule.is_regex = true;
459+
} else {
460+
rule.pattern = pattern;
461+
rule.is_regex = false;
462+
}
463+
rewrite_rules->emplace_back(rule);
464+
}
465+
444466
void Server::add_static_handler_location(const std::string &location) {
445467
if (locations == nullptr) {
446468
locations = std::make_shared<std::unordered_set<std::string>>();
@@ -464,7 +486,7 @@ bool Server::select_static_handler(const http_server::Request *request, const Co
464486
size_t url_length = request->url_length_;
465487

466488
http_server::StaticHandler handler(this, url, url_length);
467-
if (!handler.hit()) {
489+
if (!handler.try_serve()) {
468490
return false;
469491
}
470492

@@ -515,7 +537,7 @@ bool Server::select_static_handler(const http_server::Request *request, const Co
515537
* if http_index_files is enabled, need to search the index file first.
516538
* if the index file is found, set filename to index filename.
517539
*/
518-
if (!handler.hit_index_file()) {
540+
if (!handler.try_serve_index_file()) {
519541
return false;
520542
}
521543

@@ -638,4 +660,42 @@ bool Server::select_static_handler(const http_server::Request *request, const Co
638660

639661
return true;
640662
}
663+
664+
bool Server::apply_rewrite_rules(http_server::StaticHandler *handler) {
665+
if (!rewrite_rules || rewrite_rules->empty()) {
666+
return false;
667+
}
668+
669+
bool rewritten = false;
670+
auto current_url = handler->get_request_url();
671+
672+
for (const auto &rule : *rewrite_rules) {
673+
if (rule.is_regex) {
674+
try {
675+
std::regex pattern(rule.pattern);
676+
std::string rewritten_url;
677+
678+
if (std::regex_search(current_url, pattern)) {
679+
rewritten_url = std::regex_replace(current_url, pattern, rule.replacement);
680+
if (rewritten_url != current_url) {
681+
handler->set_request_url(rewritten_url);
682+
rewritten = true;
683+
break;
684+
}
685+
}
686+
} catch (const std::regex_error &e) {
687+
continue;
688+
}
689+
} else {
690+
if (starts_with(current_url, rule.pattern)) {
691+
std::string rewritten_url = rule.replacement + current_url.substr(rule.pattern.length());
692+
handler->set_request_url(rewritten_url);
693+
rewritten = true;
694+
break;
695+
}
696+
}
697+
}
698+
699+
return rewritten;
700+
}
641701
} // namespace swoole

0 commit comments

Comments
 (0)