# 練習47:一個快速的URL路由
> 原文:[Exercise 47: A Fast URL Router](http://c.learncodethehardway.org/book/ex47.html)
> 譯者:[飛龍](https://github.com/wizardforcel)
我現在打算向你展示使用`TSTree`來創建服務器中的快速URL路由。它適用于應用中的簡單的URL匹配,而不是在許多Web應用框架中的更復雜(一些情況下也不必要)的路由發現功能。
我打算編程一個小型命令行工具和路由交互,他叫做`urlor`,讀取簡單的路由文件,之后提示用戶輸入要檢索的URL。
```c
#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`工作,并且創建了路由文件,你可以嘗試這樣:
```sh
$ ./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編寫了動態回調處理器系統的全部。
- 笨辦法學C 中文版
- 前言
- 導言:C的笛卡爾之夢
- 練習0:準備
- 練習1:啟用編譯器
- 練習2:用Make來代替Python
- 練習3:格式化輸出
- 練習4:Valgrind 介紹
- 練習5:一個C程序的結構
- 練習6:變量類型
- 練習7:更多變量和一些算術
- 練習8:大小和數組
- 練習9:數組和字符串
- 練習10:字符串數組和循環
- 練習11:While循環和布爾表達式
- 練習12:If,Else If,Else
- 練習13:Switch語句
- 練習14:編寫并使用函數
- 練習15:指針,可怕的指針
- 練習16:結構體和指向它們的指針
- 練習17:堆和棧的內存分配
- 練習18:函數指針
- 練習19:一個簡單的對象系統
- 練習20:Zed的強大的調試宏
- 練習21:高級數據類型和控制結構
- 練習22:棧、作用域和全局
- 練習23:認識達夫設備
- 練習24:輸入輸出和文件
- 練習25:變參函數
- 練習26:編寫第一個真正的程序
- 練習27:創造性和防御性編程
- 練習28:Makefile 進階
- 練習29:庫和鏈接
- 練習30:自動化測試
- 練習31:代碼調試
- 練習32:雙向鏈表
- 練習33:鏈表算法
- 練習34:動態數組
- 練習35:排序和搜索
- 練習36:更安全的字符串
- 練習37:哈希表
- 練習38:哈希算法
- 練習39:字符串算法
- 練習40:二叉搜索樹
- 練習41:將 Cachegrind 和 Callgrind 用于性能調優
- 練習42:棧和隊列
- 練習43:一個簡單的統計引擎
- 練習44:環形緩沖區
- 練習45:一個簡單的TCP/IP客戶端
- 練習46:三叉搜索樹
- 練習47:一個快速的URL路由
- 后記:“解構 K&R C” 已死