練習47:一個快速的URL路由
原文:Exercise 47: A Fast URL Router
譯者:飛龍
我現在打算向你展示使用TSTree
來建立伺服器中的快速URL路由。它適用於應用中的簡單的URL匹配,而不是在許多Web應用框架中的更復雜(一些情況下也不必要)的路由發現功能。
我打算程式設計一個小型命令列工具和路由互動,他叫做urlor
,讀取簡單的路由檔案,之後提示使用者輸入要檢索的URL。
#include <lcthw/tstree.h>
#include <lcthw/bstrlib.h>
TSTree *add_route_data(TSTree *routes, bstring line)
{
struct bstrList *data = bsplit(line, ` `);
check(data->qty == 2, "Line `%s` does not have 2 columns",
bdata(line));
routes = TSTree_insert(routes,
bdata(data->entry[0]), blength(data->entry[0]),
bstrcpy(data->entry[1]));
bstrListDestroy(data);
return routes;
error:
return NULL;
}
TSTree *load_routes(const char *file)
{
TSTree *routes = NULL;
bstring line = NULL;
FILE *routes_map = NULL;
routes_map = fopen(file, "r");
check(routes_map != NULL, "Failed to open routes: %s", file);
while((line = bgets((bNgetc)fgetc, routes_map, `
`)) != NULL) {
check(btrimws(line) == BSTR_OK, "Failed to trim line.");
routes = add_route_data(routes, line);
check(routes != NULL, "Failed to add route.");
bdestroy(line);
}
fclose(routes_map);
return routes;
error:
if(routes_map) fclose(routes_map);
if(line) bdestroy(line);
return NULL;
}
bstring match_url(TSTree *routes, bstring url)
{
bstring route = TSTree_search(routes, bdata(url), blength(url));
if(route == NULL) {
printf("No exact match found, trying prefix.
");
route = TSTree_search_prefix(routes, bdata(url), blength(url));
}
return route;
}
bstring read_line(const char *prompt)
{
printf("%s", prompt);
bstring result = bgets((bNgetc)fgetc, stdin, `
`);
check_debug(result != NULL, "stdin closed.");
check(btrimws(result) == BSTR_OK, "Failed to trim.");
return result;
error:
return NULL;
}
void bdestroy_cb(void *value, void *ignored)
{
(void)ignored;
bdestroy((bstring)value);
}
void destroy_routes(TSTree *routes)
{
TSTree_traverse(routes, bdestroy_cb, NULL);
TSTree_destroy(routes);
}
int main(int argc, char *argv[])
{
bstring url = NULL;
bstring route = NULL;
check(argc == 2, "USAGE: urlor <urlfile>");
TSTree *routes = load_routes(argv[1]);
check(routes != NULL, "Your route file has an error.");
while(1) {
url = read_line("URL> ");
check_debug(url != NULL, "goodbye.");
route = match_url(routes, url);
if(route) {
printf("MATCH: %s == %s
", bdata(url), bdata(route));
} else {
printf("FAIL: %s
", bdata(url));
}
bdestroy(url);
}
destroy_routes(routes);
return 0;
error:
destroy_routes(routes);
return 1;
}
之後我建立了一個簡單的檔案,含有一些用於互動的偽造的路由:
/ MainApp /hello Hello /hello/ Hello /signup Signup /logout Logout /album/ Album
你會看到什麼
一旦你使urlor
工作,並且建立了路由檔案,你可以嘗試這樣:
$ ./bin/urlor urls.txt
URL> /
MATCH: / == MainApp
URL> /hello
MATCH: /hello == Hello
URL> /hello/zed
No exact match found, trying prefix.
MATCH: /hello/zed == Hello
URL> /album
No exact match found, trying prefix.
MATCH: /album == Album
URL> /album/12345
No exact match found, trying prefix.
MATCH: /album/12345 == Album
URL> asdfasfdasfd
No exact match found, trying prefix.
FAIL: asdfasfdasfd
URL> /asdfasdfasf
No exact match found, trying prefix.
MATCH: /asdfasdfasf == MainApp
URL>
$
你可以看到路由系統首先嚐試精確匹配,之後如果找不到的話則會嘗試字首匹配。這主要是嘗試這二者的不同。根據你的URL的語義,你可能想要之中精確匹配,始終字首匹配,或者執行二者並選出“最好”的那個。
如何改進
URL非常古怪。因為人們想讓它們神奇地處理它們的web應用所具有的,所有瘋狂的事情,即使不是很合邏輯。在這個對如何將TSTree
用作路由的簡單演示中,它具有一些人們不想要的缺陷。比如,它會把/al
匹配到Album
,它是人們通常不想要的。它們想要/album/*
匹配到Album
以及/al
匹配到404錯誤。
這並不難以實現,因為你可以修改字首演算法來以你想要的任何方式匹配。如果你修改了匹配演算法,來尋找所有匹配的字首,之後選出“最好”的那個,你就可以輕易做到它。這種情況下,/al
回匹配MainApp
或者Album
。獲得這些結果之後,就可以執行一些邏輯來決定哪個“最好”。
另一件你能在真正的路由系統裡做的事情,就是使用TSTree
來尋找所有可能的匹配,但是這些匹配是需要檢查的一些模式串。在許多web應用中,有一個正規表示式的列表,用於和每個請求的URL進行匹配。匹配所有這些正規表示式非常花時間,所以你可以使用TSTree
來通過它們的字首尋找所有可能的結果。於是你就可以縮小模式串的範圍,更快速地做嘗試。
使用這種方式,你的URL會精確匹配,因為你實際上執行了正規表示式,它們匹配起來更快,因為你通過可能的字首來查詢它們。
這種演算法也可用於所有需要使用者視覺化的靈活路由機制。域名、IP地址、包註冊器和目錄,檔案或者URL。
附加題
-
建立一個實際的引擎,使用
Handler
結構儲存應用,而不是僅僅儲存應用的字串。這個結構儲存它所繫結的URL,名稱和任何需要構建實際路由系統的東西。 -
將URL對映到
.so
檔案而不是任意的名字,並且使用dlopen
系統動態載入處理器,並執行它們所包含的回撥。將這些回撥放進你的Handler
結構體中,之後你就用C編寫了動態回撥處理器系統的全部。