原文:Exercise 47: A Fast URL Routerhtml
譯者:飛龍git
我如今打算向你展現使用TSTree
來建立服務器中的快速URL路由。它適用於應用中的簡單的URL匹配,而不是在許多Web應用框架中的更復雜(一些狀況下也沒必要要)的路由發現功能。github
我打算編程一個小型命令行工具和路由交互,他叫作urlor
,讀取簡單的路由文件,以後提示用戶輸入要檢索的URL。web
#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, '\n')) != 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.\n"); 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, '\n'); 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\n", bdata(url), bdata(route)); } else { printf("FAIL: %s\n", 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編寫了動態回調處理器系統的所有。