练习47:一个快速的URL路由

原文:Exercise 47: A Fast URL Router

译者:飞龙

我现在打算向你展示使用TSTree来创建服务器中的快速URL路由。它适用于应用中的简单的URL匹配,而不是在许多Web应用框架中的更复杂(一些情况下也不必要)的路由发现功能。

我打算编程一个小型命令行工具和路由交互,他叫做urlor,读取简单的路由文件,之后提示用户输入要检索的URL。

  1. #include <lcthw/tstree.h>
  2. #include <lcthw/bstrlib.h>
  3. TSTree *add_route_data(TSTree *routes, bstring line)
  4. {
  5. struct bstrList *data = bsplit(line, ' ');
  6. check(data->qty == 2, "Line '%s' does not have 2 columns",
  7. bdata(line));
  8. routes = TSTree_insert(routes,
  9. bdata(data->entry[0]), blength(data->entry[0]),
  10. bstrcpy(data->entry[1]));
  11. bstrListDestroy(data);
  12. return routes;
  13. error:
  14. return NULL;
  15. }
  16. TSTree *load_routes(const char *file)
  17. {
  18. TSTree *routes = NULL;
  19. bstring line = NULL;
  20. FILE *routes_map = NULL;
  21. routes_map = fopen(file, "r");
  22. check(routes_map != NULL, "Failed to open routes: %s", file);
  23. while((line = bgets((bNgetc)fgetc, routes_map, '\n')) != NULL) {
  24. check(btrimws(line) == BSTR_OK, "Failed to trim line.");
  25. routes = add_route_data(routes, line);
  26. check(routes != NULL, "Failed to add route.");
  27. bdestroy(line);
  28. }
  29. fclose(routes_map);
  30. return routes;
  31. error:
  32. if(routes_map) fclose(routes_map);
  33. if(line) bdestroy(line);
  34. return NULL;
  35. }
  36. bstring match_url(TSTree *routes, bstring url)
  37. {
  38. bstring route = TSTree_search(routes, bdata(url), blength(url));
  39. if(route == NULL) {
  40. printf("No exact match found, trying prefix.\n");
  41. route = TSTree_search_prefix(routes, bdata(url), blength(url));
  42. }
  43. return route;
  44. }
  45. bstring read_line(const char *prompt)
  46. {
  47. printf("%s", prompt);
  48. bstring result = bgets((bNgetc)fgetc, stdin, '\n');
  49. check_debug(result != NULL, "stdin closed.");
  50. check(btrimws(result) == BSTR_OK, "Failed to trim.");
  51. return result;
  52. error:
  53. return NULL;
  54. }
  55. void bdestroy_cb(void *value, void *ignored)
  56. {
  57. (void)ignored;
  58. bdestroy((bstring)value);
  59. }
  60. void destroy_routes(TSTree *routes)
  61. {
  62. TSTree_traverse(routes, bdestroy_cb, NULL);
  63. TSTree_destroy(routes);
  64. }
  65. int main(int argc, char *argv[])
  66. {
  67. bstring url = NULL;
  68. bstring route = NULL;
  69. check(argc == 2, "USAGE: urlor <urlfile>");
  70. TSTree *routes = load_routes(argv[1]);
  71. check(routes != NULL, "Your route file has an error.");
  72. while(1) {
  73. url = read_line("URL> ");
  74. check_debug(url != NULL, "goodbye.");
  75. route = match_url(routes, url);
  76. if(route) {
  77. printf("MATCH: %s == %s\n", bdata(url), bdata(route));
  78. } else {
  79. printf("FAIL: %s\n", bdata(url));
  80. }
  81. bdestroy(url);
  82. }
  83. destroy_routes(routes);
  84. return 0;
  85. error:
  86. destroy_routes(routes);
  87. return 1;
  88. }

之后我创建了一个简单的文件,含有一些用于交互的伪造的路由:

  1. / MainApp /hello Hello /hello/ Hello /signup Signup /logout Logout /album/ Album

你会看到什么

一旦你使urlor工作,并且创建了路由文件,你可以尝试这样:

  1. $ ./bin/urlor urls.txt
  2. URL> /
  3. MATCH: / == MainApp
  4. URL> /hello
  5. MATCH: /hello == Hello
  6. URL> /hello/zed
  7. No exact match found, trying prefix.
  8. MATCH: /hello/zed == Hello
  9. URL> /album
  10. No exact match found, trying prefix.
  11. MATCH: /album == Album
  12. URL> /album/12345
  13. No exact match found, trying prefix.
  14. MATCH: /album/12345 == Album
  15. URL> asdfasfdasfd
  16. No exact match found, trying prefix.
  17. FAIL: asdfasfdasfd
  18. URL> /asdfasdfasf
  19. No exact match found, trying prefix.
  20. MATCH: /asdfasdfasf == MainApp
  21. URL>
  22. $

你可以看到路由系统首先尝试精确匹配,之后如果找不到的话则会尝试前缀匹配。这主要是尝试这二者的不同。根据你的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编写了动态回调处理器系统的全部。