笨辦法學C 練習47:一個快速的URL路由

飛龍發表於2019-05-09

練習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編寫了動態回撥處理器系統的全部。

相關文章