You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1656 lines
51 KiB

  1. // Copyright 2012 The Gorilla Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package mux
  5. import (
  6. "bufio"
  7. "bytes"
  8. "errors"
  9. "fmt"
  10. "net/http"
  11. "strings"
  12. "testing"
  13. )
  14. func (r *Route) GoString() string {
  15. matchers := make([]string, len(r.matchers))
  16. for i, m := range r.matchers {
  17. matchers[i] = fmt.Sprintf("%#v", m)
  18. }
  19. return fmt.Sprintf("&Route{matchers:[]matcher{%s}}", strings.Join(matchers, ", "))
  20. }
  21. func (r *routeRegexp) GoString() string {
  22. return fmt.Sprintf("&routeRegexp{template: %q, matchHost: %t, matchQuery: %t, strictSlash: %t, regexp: regexp.MustCompile(%q), reverse: %q, varsN: %v, varsR: %v", r.template, r.matchHost, r.matchQuery, r.strictSlash, r.regexp.String(), r.reverse, r.varsN, r.varsR)
  23. }
  24. type routeTest struct {
  25. title string // title of the test
  26. route *Route // the route being tested
  27. request *http.Request // a request to test the route
  28. vars map[string]string // the expected vars of the match
  29. host string // the expected host of the match
  30. path string // the expected path of the match
  31. pathTemplate string // the expected path template to match
  32. hostTemplate string // the expected host template to match
  33. shouldMatch bool // whether the request is expected to match the route at all
  34. shouldRedirect bool // whether the request should result in a redirect
  35. }
  36. func TestHost(t *testing.T) {
  37. // newRequestHost a new request with a method, url, and host header
  38. newRequestHost := func(method, url, host string) *http.Request {
  39. req, err := http.NewRequest(method, url, nil)
  40. if err != nil {
  41. panic(err)
  42. }
  43. req.Host = host
  44. return req
  45. }
  46. tests := []routeTest{
  47. {
  48. title: "Host route match",
  49. route: new(Route).Host("aaa.bbb.ccc"),
  50. request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
  51. vars: map[string]string{},
  52. host: "aaa.bbb.ccc",
  53. path: "",
  54. shouldMatch: true,
  55. },
  56. {
  57. title: "Host route, wrong host in request URL",
  58. route: new(Route).Host("aaa.bbb.ccc"),
  59. request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
  60. vars: map[string]string{},
  61. host: "aaa.bbb.ccc",
  62. path: "",
  63. shouldMatch: false,
  64. },
  65. {
  66. title: "Host route with port, match",
  67. route: new(Route).Host("aaa.bbb.ccc:1234"),
  68. request: newRequest("GET", "http://aaa.bbb.ccc:1234/111/222/333"),
  69. vars: map[string]string{},
  70. host: "aaa.bbb.ccc:1234",
  71. path: "",
  72. shouldMatch: true,
  73. },
  74. {
  75. title: "Host route with port, wrong port in request URL",
  76. route: new(Route).Host("aaa.bbb.ccc:1234"),
  77. request: newRequest("GET", "http://aaa.bbb.ccc:9999/111/222/333"),
  78. vars: map[string]string{},
  79. host: "aaa.bbb.ccc:1234",
  80. path: "",
  81. shouldMatch: false,
  82. },
  83. {
  84. title: "Host route, match with host in request header",
  85. route: new(Route).Host("aaa.bbb.ccc"),
  86. request: newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc"),
  87. vars: map[string]string{},
  88. host: "aaa.bbb.ccc",
  89. path: "",
  90. shouldMatch: true,
  91. },
  92. {
  93. title: "Host route, wrong host in request header",
  94. route: new(Route).Host("aaa.bbb.ccc"),
  95. request: newRequestHost("GET", "/111/222/333", "aaa.222.ccc"),
  96. vars: map[string]string{},
  97. host: "aaa.bbb.ccc",
  98. path: "",
  99. shouldMatch: false,
  100. },
  101. // BUG {new(Route).Host("aaa.bbb.ccc:1234"), newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:1234"), map[string]string{}, "aaa.bbb.ccc:1234", "", true},
  102. {
  103. title: "Host route with port, wrong host in request header",
  104. route: new(Route).Host("aaa.bbb.ccc:1234"),
  105. request: newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:9999"),
  106. vars: map[string]string{},
  107. host: "aaa.bbb.ccc:1234",
  108. path: "",
  109. shouldMatch: false,
  110. },
  111. {
  112. title: "Host route with pattern, match",
  113. route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
  114. request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
  115. vars: map[string]string{"v1": "bbb"},
  116. host: "aaa.bbb.ccc",
  117. path: "",
  118. hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
  119. shouldMatch: true,
  120. },
  121. {
  122. title: "Host route with pattern, additional capturing group, match",
  123. route: new(Route).Host("aaa.{v1:[a-z]{2}(?:b|c)}.ccc"),
  124. request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
  125. vars: map[string]string{"v1": "bbb"},
  126. host: "aaa.bbb.ccc",
  127. path: "",
  128. hostTemplate: `aaa.{v1:[a-z]{2}(?:b|c)}.ccc`,
  129. shouldMatch: true,
  130. },
  131. {
  132. title: "Host route with pattern, wrong host in request URL",
  133. route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
  134. request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
  135. vars: map[string]string{"v1": "bbb"},
  136. host: "aaa.bbb.ccc",
  137. path: "",
  138. hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
  139. shouldMatch: false,
  140. },
  141. {
  142. title: "Host route with multiple patterns, match",
  143. route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
  144. request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
  145. vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
  146. host: "aaa.bbb.ccc",
  147. path: "",
  148. hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
  149. shouldMatch: true,
  150. },
  151. {
  152. title: "Host route with multiple patterns, wrong host in request URL",
  153. route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
  154. request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
  155. vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
  156. host: "aaa.bbb.ccc",
  157. path: "",
  158. hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
  159. shouldMatch: false,
  160. },
  161. {
  162. title: "Host route with hyphenated name and pattern, match",
  163. route: new(Route).Host("aaa.{v-1:[a-z]{3}}.ccc"),
  164. request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
  165. vars: map[string]string{"v-1": "bbb"},
  166. host: "aaa.bbb.ccc",
  167. path: "",
  168. hostTemplate: `aaa.{v-1:[a-z]{3}}.ccc`,
  169. shouldMatch: true,
  170. },
  171. {
  172. title: "Host route with hyphenated name and pattern, additional capturing group, match",
  173. route: new(Route).Host("aaa.{v-1:[a-z]{2}(?:b|c)}.ccc"),
  174. request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
  175. vars: map[string]string{"v-1": "bbb"},
  176. host: "aaa.bbb.ccc",
  177. path: "",
  178. hostTemplate: `aaa.{v-1:[a-z]{2}(?:b|c)}.ccc`,
  179. shouldMatch: true,
  180. },
  181. {
  182. title: "Host route with multiple hyphenated names and patterns, match",
  183. route: new(Route).Host("{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}"),
  184. request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
  185. vars: map[string]string{"v-1": "aaa", "v-2": "bbb", "v-3": "ccc"},
  186. host: "aaa.bbb.ccc",
  187. path: "",
  188. hostTemplate: `{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}`,
  189. shouldMatch: true,
  190. },
  191. {
  192. title: "Path route with single pattern with pipe, match",
  193. route: new(Route).Path("/{category:a|b/c}"),
  194. request: newRequest("GET", "http://localhost/a"),
  195. vars: map[string]string{"category": "a"},
  196. host: "",
  197. path: "/a",
  198. pathTemplate: `/{category:a|b/c}`,
  199. shouldMatch: true,
  200. },
  201. {
  202. title: "Path route with single pattern with pipe, match",
  203. route: new(Route).Path("/{category:a|b/c}"),
  204. request: newRequest("GET", "http://localhost/b/c"),
  205. vars: map[string]string{"category": "b/c"},
  206. host: "",
  207. path: "/b/c",
  208. pathTemplate: `/{category:a|b/c}`,
  209. shouldMatch: true,
  210. },
  211. {
  212. title: "Path route with multiple patterns with pipe, match",
  213. route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
  214. request: newRequest("GET", "http://localhost/a/product_name/1"),
  215. vars: map[string]string{"category": "a", "product": "product_name", "id": "1"},
  216. host: "",
  217. path: "/a/product_name/1",
  218. pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
  219. shouldMatch: true,
  220. },
  221. {
  222. title: "Path route with multiple patterns with pipe, match",
  223. route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
  224. request: newRequest("GET", "http://localhost/b/c/product_name/1"),
  225. vars: map[string]string{"category": "b/c", "product": "product_name", "id": "1"},
  226. host: "",
  227. path: "/b/c/product_name/1",
  228. pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
  229. shouldMatch: true,
  230. },
  231. }
  232. for _, test := range tests {
  233. testRoute(t, test)
  234. testTemplate(t, test)
  235. }
  236. }
  237. func TestPath(t *testing.T) {
  238. tests := []routeTest{
  239. {
  240. title: "Path route, match",
  241. route: new(Route).Path("/111/222/333"),
  242. request: newRequest("GET", "http://localhost/111/222/333"),
  243. vars: map[string]string{},
  244. host: "",
  245. path: "/111/222/333",
  246. shouldMatch: true,
  247. },
  248. {
  249. title: "Path route, match with trailing slash in request and path",
  250. route: new(Route).Path("/111/"),
  251. request: newRequest("GET", "http://localhost/111/"),
  252. vars: map[string]string{},
  253. host: "",
  254. path: "/111/",
  255. shouldMatch: true,
  256. },
  257. {
  258. title: "Path route, do not match with trailing slash in path",
  259. route: new(Route).Path("/111/"),
  260. request: newRequest("GET", "http://localhost/111"),
  261. vars: map[string]string{},
  262. host: "",
  263. path: "/111",
  264. pathTemplate: `/111/`,
  265. shouldMatch: false,
  266. },
  267. {
  268. title: "Path route, do not match with trailing slash in request",
  269. route: new(Route).Path("/111"),
  270. request: newRequest("GET", "http://localhost/111/"),
  271. vars: map[string]string{},
  272. host: "",
  273. path: "/111/",
  274. pathTemplate: `/111`,
  275. shouldMatch: false,
  276. },
  277. {
  278. title: "Path route, match root with no host",
  279. route: new(Route).Path("/"),
  280. request: newRequest("GET", "/"),
  281. vars: map[string]string{},
  282. host: "",
  283. path: "/",
  284. pathTemplate: `/`,
  285. shouldMatch: true,
  286. },
  287. {
  288. title: "Path route, match root with no host, App Engine format",
  289. route: new(Route).Path("/"),
  290. request: func() *http.Request {
  291. r := newRequest("GET", "http://localhost/")
  292. r.RequestURI = "/"
  293. return r
  294. }(),
  295. vars: map[string]string{},
  296. host: "",
  297. path: "/",
  298. pathTemplate: `/`,
  299. shouldMatch: true,
  300. },
  301. {
  302. title: "Path route, wrong path in request in request URL",
  303. route: new(Route).Path("/111/222/333"),
  304. request: newRequest("GET", "http://localhost/1/2/3"),
  305. vars: map[string]string{},
  306. host: "",
  307. path: "/111/222/333",
  308. shouldMatch: false,
  309. },
  310. {
  311. title: "Path route with pattern, match",
  312. route: new(Route).Path("/111/{v1:[0-9]{3}}/333"),
  313. request: newRequest("GET", "http://localhost/111/222/333"),
  314. vars: map[string]string{"v1": "222"},
  315. host: "",
  316. path: "/111/222/333",
  317. pathTemplate: `/111/{v1:[0-9]{3}}/333`,
  318. shouldMatch: true,
  319. },
  320. {
  321. title: "Path route with pattern, URL in request does not match",
  322. route: new(Route).Path("/111/{v1:[0-9]{3}}/333"),
  323. request: newRequest("GET", "http://localhost/111/aaa/333"),
  324. vars: map[string]string{"v1": "222"},
  325. host: "",
  326. path: "/111/222/333",
  327. pathTemplate: `/111/{v1:[0-9]{3}}/333`,
  328. shouldMatch: false,
  329. },
  330. {
  331. title: "Path route with multiple patterns, match",
  332. route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
  333. request: newRequest("GET", "http://localhost/111/222/333"),
  334. vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"},
  335. host: "",
  336. path: "/111/222/333",
  337. pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}`,
  338. shouldMatch: true,
  339. },
  340. {
  341. title: "Path route with multiple patterns, URL in request does not match",
  342. route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
  343. request: newRequest("GET", "http://localhost/111/aaa/333"),
  344. vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"},
  345. host: "",
  346. path: "/111/222/333",
  347. pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}`,
  348. shouldMatch: false,
  349. },
  350. {
  351. title: "Path route with multiple patterns with pipe, match",
  352. route: new(Route).Path("/{category:a|(?:b/c)}/{product}/{id:[0-9]+}"),
  353. request: newRequest("GET", "http://localhost/a/product_name/1"),
  354. vars: map[string]string{"category": "a", "product": "product_name", "id": "1"},
  355. host: "",
  356. path: "/a/product_name/1",
  357. pathTemplate: `/{category:a|(?:b/c)}/{product}/{id:[0-9]+}`,
  358. shouldMatch: true,
  359. },
  360. {
  361. title: "Path route with hyphenated name and pattern, match",
  362. route: new(Route).Path("/111/{v-1:[0-9]{3}}/333"),
  363. request: newRequest("GET", "http://localhost/111/222/333"),
  364. vars: map[string]string{"v-1": "222"},
  365. host: "",
  366. path: "/111/222/333",
  367. pathTemplate: `/111/{v-1:[0-9]{3}}/333`,
  368. shouldMatch: true,
  369. },
  370. {
  371. title: "Path route with multiple hyphenated names and patterns, match",
  372. route: new(Route).Path("/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}"),
  373. request: newRequest("GET", "http://localhost/111/222/333"),
  374. vars: map[string]string{"v-1": "111", "v-2": "222", "v-3": "333"},
  375. host: "",
  376. path: "/111/222/333",
  377. pathTemplate: `/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}`,
  378. shouldMatch: true,
  379. },
  380. {
  381. title: "Path route with multiple hyphenated names and patterns with pipe, match",
  382. route: new(Route).Path("/{product-category:a|(?:b/c)}/{product-name}/{product-id:[0-9]+}"),
  383. request: newRequest("GET", "http://localhost/a/product_name/1"),
  384. vars: map[string]string{"product-category": "a", "product-name": "product_name", "product-id": "1"},
  385. host: "",
  386. path: "/a/product_name/1",
  387. pathTemplate: `/{product-category:a|(?:b/c)}/{product-name}/{product-id:[0-9]+}`,
  388. shouldMatch: true,
  389. },
  390. {
  391. title: "Path route with multiple hyphenated names and patterns with pipe and case insensitive, match",
  392. route: new(Route).Path("/{type:(?i:daily|mini|variety)}-{date:\\d{4,4}-\\d{2,2}-\\d{2,2}}"),
  393. request: newRequest("GET", "http://localhost/daily-2016-01-01"),
  394. vars: map[string]string{"type": "daily", "date": "2016-01-01"},
  395. host: "",
  396. path: "/daily-2016-01-01",
  397. pathTemplate: `/{type:(?i:daily|mini|variety)}-{date:\d{4,4}-\d{2,2}-\d{2,2}}`,
  398. shouldMatch: true,
  399. },
  400. {
  401. title: "Path route with empty match right after other match",
  402. route: new(Route).Path(`/{v1:[0-9]*}{v2:[a-z]*}/{v3:[0-9]*}`),
  403. request: newRequest("GET", "http://localhost/111/222"),
  404. vars: map[string]string{"v1": "111", "v2": "", "v3": "222"},
  405. host: "",
  406. path: "/111/222",
  407. pathTemplate: `/{v1:[0-9]*}{v2:[a-z]*}/{v3:[0-9]*}`,
  408. shouldMatch: true,
  409. },
  410. }
  411. for _, test := range tests {
  412. testRoute(t, test)
  413. testTemplate(t, test)
  414. testUseEscapedRoute(t, test)
  415. }
  416. }
  417. func TestPathPrefix(t *testing.T) {
  418. tests := []routeTest{
  419. {
  420. title: "PathPrefix route, match",
  421. route: new(Route).PathPrefix("/111"),
  422. request: newRequest("GET", "http://localhost/111/222/333"),
  423. vars: map[string]string{},
  424. host: "",
  425. path: "/111",
  426. shouldMatch: true,
  427. },
  428. {
  429. title: "PathPrefix route, match substring",
  430. route: new(Route).PathPrefix("/1"),
  431. request: newRequest("GET", "http://localhost/111/222/333"),
  432. vars: map[string]string{},
  433. host: "",
  434. path: "/1",
  435. shouldMatch: true,
  436. },
  437. {
  438. title: "PathPrefix route, URL prefix in request does not match",
  439. route: new(Route).PathPrefix("/111"),
  440. request: newRequest("GET", "http://localhost/1/2/3"),
  441. vars: map[string]string{},
  442. host: "",
  443. path: "/111",
  444. shouldMatch: false,
  445. },
  446. {
  447. title: "PathPrefix route with pattern, match",
  448. route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
  449. request: newRequest("GET", "http://localhost/111/222/333"),
  450. vars: map[string]string{"v1": "222"},
  451. host: "",
  452. path: "/111/222",
  453. pathTemplate: `/111/{v1:[0-9]{3}}`,
  454. shouldMatch: true,
  455. },
  456. {
  457. title: "PathPrefix route with pattern, URL prefix in request does not match",
  458. route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
  459. request: newRequest("GET", "http://localhost/111/aaa/333"),
  460. vars: map[string]string{"v1": "222"},
  461. host: "",
  462. path: "/111/222",
  463. pathTemplate: `/111/{v1:[0-9]{3}}`,
  464. shouldMatch: false,
  465. },
  466. {
  467. title: "PathPrefix route with multiple patterns, match",
  468. route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
  469. request: newRequest("GET", "http://localhost/111/222/333"),
  470. vars: map[string]string{"v1": "111", "v2": "222"},
  471. host: "",
  472. path: "/111/222",
  473. pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}`,
  474. shouldMatch: true,
  475. },
  476. {
  477. title: "PathPrefix route with multiple patterns, URL prefix in request does not match",
  478. route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
  479. request: newRequest("GET", "http://localhost/111/aaa/333"),
  480. vars: map[string]string{"v1": "111", "v2": "222"},
  481. host: "",
  482. path: "/111/222",
  483. pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}`,
  484. shouldMatch: false,
  485. },
  486. }
  487. for _, test := range tests {
  488. testRoute(t, test)
  489. testTemplate(t, test)
  490. testUseEscapedRoute(t, test)
  491. }
  492. }
  493. func TestHostPath(t *testing.T) {
  494. tests := []routeTest{
  495. {
  496. title: "Host and Path route, match",
  497. route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
  498. request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
  499. vars: map[string]string{},
  500. host: "",
  501. path: "",
  502. pathTemplate: `/111/222/333`,
  503. hostTemplate: `aaa.bbb.ccc`,
  504. shouldMatch: true,
  505. },
  506. {
  507. title: "Host and Path route, wrong host in request URL",
  508. route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
  509. request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
  510. vars: map[string]string{},
  511. host: "",
  512. path: "",
  513. pathTemplate: `/111/222/333`,
  514. hostTemplate: `aaa.bbb.ccc`,
  515. shouldMatch: false,
  516. },
  517. {
  518. title: "Host and Path route with pattern, match",
  519. route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
  520. request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
  521. vars: map[string]string{"v1": "bbb", "v2": "222"},
  522. host: "aaa.bbb.ccc",
  523. path: "/111/222/333",
  524. pathTemplate: `/111/{v2:[0-9]{3}}/333`,
  525. hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
  526. shouldMatch: true,
  527. },
  528. {
  529. title: "Host and Path route with pattern, URL in request does not match",
  530. route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
  531. request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
  532. vars: map[string]string{"v1": "bbb", "v2": "222"},
  533. host: "aaa.bbb.ccc",
  534. path: "/111/222/333",
  535. pathTemplate: `/111/{v2:[0-9]{3}}/333`,
  536. hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
  537. shouldMatch: false,
  538. },
  539. {
  540. title: "Host and Path route with multiple patterns, match",
  541. route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
  542. request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
  543. vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
  544. host: "aaa.bbb.ccc",
  545. path: "/111/222/333",
  546. pathTemplate: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,
  547. hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
  548. shouldMatch: true,
  549. },
  550. {
  551. title: "Host and Path route with multiple patterns, URL in request does not match",
  552. route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
  553. request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
  554. vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
  555. host: "aaa.bbb.ccc",
  556. path: "/111/222/333",
  557. pathTemplate: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,
  558. hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
  559. shouldMatch: false,
  560. },
  561. }
  562. for _, test := range tests {
  563. testRoute(t, test)
  564. testTemplate(t, test)
  565. testUseEscapedRoute(t, test)
  566. }
  567. }
  568. func TestHeaders(t *testing.T) {
  569. // newRequestHeaders creates a new request with a method, url, and headers
  570. newRequestHeaders := func(method, url string, headers map[string]string) *http.Request {
  571. req, err := http.NewRequest(method, url, nil)
  572. if err != nil {
  573. panic(err)
  574. }
  575. for k, v := range headers {
  576. req.Header.Add(k, v)
  577. }
  578. return req
  579. }
  580. tests := []routeTest{
  581. {
  582. title: "Headers route, match",
  583. route: new(Route).Headers("foo", "bar", "baz", "ding"),
  584. request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "ding"}),
  585. vars: map[string]string{},
  586. host: "",
  587. path: "",
  588. shouldMatch: true,
  589. },
  590. {
  591. title: "Headers route, bad header values",
  592. route: new(Route).Headers("foo", "bar", "baz", "ding"),
  593. request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "dong"}),
  594. vars: map[string]string{},
  595. host: "",
  596. path: "",
  597. shouldMatch: false,
  598. },
  599. {
  600. title: "Headers route, regex header values to match",
  601. route: new(Route).Headers("foo", "ba[zr]"),
  602. request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar"}),
  603. vars: map[string]string{},
  604. host: "",
  605. path: "",
  606. shouldMatch: false,
  607. },
  608. {
  609. title: "Headers route, regex header values to match",
  610. route: new(Route).HeadersRegexp("foo", "ba[zr]"),
  611. request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "baz"}),
  612. vars: map[string]string{},
  613. host: "",
  614. path: "",
  615. shouldMatch: true,
  616. },
  617. }
  618. for _, test := range tests {
  619. testRoute(t, test)
  620. testTemplate(t, test)
  621. }
  622. }
  623. func TestMethods(t *testing.T) {
  624. tests := []routeTest{
  625. {
  626. title: "Methods route, match GET",
  627. route: new(Route).Methods("GET", "POST"),
  628. request: newRequest("GET", "http://localhost"),
  629. vars: map[string]string{},
  630. host: "",
  631. path: "",
  632. shouldMatch: true,
  633. },
  634. {
  635. title: "Methods route, match POST",
  636. route: new(Route).Methods("GET", "POST"),
  637. request: newRequest("POST", "http://localhost"),
  638. vars: map[string]string{},
  639. host: "",
  640. path: "",
  641. shouldMatch: true,
  642. },
  643. {
  644. title: "Methods route, bad method",
  645. route: new(Route).Methods("GET", "POST"),
  646. request: newRequest("PUT", "http://localhost"),
  647. vars: map[string]string{},
  648. host: "",
  649. path: "",
  650. shouldMatch: false,
  651. },
  652. }
  653. for _, test := range tests {
  654. testRoute(t, test)
  655. testTemplate(t, test)
  656. }
  657. }
  658. func TestQueries(t *testing.T) {
  659. tests := []routeTest{
  660. {
  661. title: "Queries route, match",
  662. route: new(Route).Queries("foo", "bar", "baz", "ding"),
  663. request: newRequest("GET", "http://localhost?foo=bar&baz=ding"),
  664. vars: map[string]string{},
  665. host: "",
  666. path: "",
  667. shouldMatch: true,
  668. },
  669. {
  670. title: "Queries route, match with a query string",
  671. route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
  672. request: newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"),
  673. vars: map[string]string{},
  674. host: "",
  675. path: "",
  676. pathTemplate: `/api`,
  677. hostTemplate: `www.example.com`,
  678. shouldMatch: true,
  679. },
  680. {
  681. title: "Queries route, match with a query string out of order",
  682. route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
  683. request: newRequest("GET", "http://www.example.com/api?baz=ding&foo=bar"),
  684. vars: map[string]string{},
  685. host: "",
  686. path: "",
  687. pathTemplate: `/api`,
  688. hostTemplate: `www.example.com`,
  689. shouldMatch: true,
  690. },
  691. {
  692. title: "Queries route, bad query",
  693. route: new(Route).Queries("foo", "bar", "baz", "ding"),
  694. request: newRequest("GET", "http://localhost?foo=bar&baz=dong"),
  695. vars: map[string]string{},
  696. host: "",
  697. path: "",
  698. shouldMatch: false,
  699. },
  700. {
  701. title: "Queries route with pattern, match",
  702. route: new(Route).Queries("foo", "{v1}"),
  703. request: newRequest("GET", "http://localhost?foo=bar"),
  704. vars: map[string]string{"v1": "bar"},
  705. host: "",
  706. path: "",
  707. shouldMatch: true,
  708. },
  709. {
  710. title: "Queries route with multiple patterns, match",
  711. route: new(Route).Queries("foo", "{v1}", "baz", "{v2}"),
  712. request: newRequest("GET", "http://localhost?foo=bar&baz=ding"),
  713. vars: map[string]string{"v1": "bar", "v2": "ding"},
  714. host: "",
  715. path: "",
  716. shouldMatch: true,
  717. },
  718. {
  719. title: "Queries route with regexp pattern, match",
  720. route: new(Route).Queries("foo", "{v1:[0-9]+}"),
  721. request: newRequest("GET", "http://localhost?foo=10"),
  722. vars: map[string]string{"v1": "10"},
  723. host: "",
  724. path: "",
  725. shouldMatch: true,
  726. },
  727. {
  728. title: "Queries route with regexp pattern, regexp does not match",
  729. route: new(Route).Queries("foo", "{v1:[0-9]+}"),
  730. request: newRequest("GET", "http://localhost?foo=a"),
  731. vars: map[string]string{},
  732. host: "",
  733. path: "",
  734. shouldMatch: false,
  735. },
  736. {
  737. title: "Queries route with regexp pattern with quantifier, match",
  738. route: new(Route).Queries("foo", "{v1:[0-9]{1}}"),
  739. request: newRequest("GET", "http://localhost?foo=1"),
  740. vars: map[string]string{"v1": "1"},
  741. host: "",
  742. path: "",
  743. shouldMatch: true,
  744. },
  745. {
  746. title: "Queries route with regexp pattern with quantifier, additional variable in query string, match",
  747. route: new(Route).Queries("foo", "{v1:[0-9]{1}}"),
  748. request: newRequest("GET", "http://localhost?bar=2&foo=1"),
  749. vars: map[string]string{"v1": "1"},
  750. host: "",
  751. path: "",
  752. shouldMatch: true,
  753. },
  754. {
  755. title: "Queries route with regexp pattern with quantifier, regexp does not match",
  756. route: new(Route).Queries("foo", "{v1:[0-9]{1}}"),
  757. request: newRequest("GET", "http://localhost?foo=12"),
  758. vars: map[string]string{},
  759. host: "",
  760. path: "",
  761. shouldMatch: false,
  762. },
  763. {
  764. title: "Queries route with regexp pattern with quantifier, additional capturing group",
  765. route: new(Route).Queries("foo", "{v1:[0-9]{1}(?:a|b)}"),
  766. request: newRequest("GET", "http://localhost?foo=1a"),
  767. vars: map[string]string{"v1": "1a"},
  768. host: "",
  769. path: "",
  770. shouldMatch: true,
  771. },
  772. {
  773. title: "Queries route with regexp pattern with quantifier, additional variable in query string, regexp does not match",
  774. route: new(Route).Queries("foo", "{v1:[0-9]{1}}"),
  775. request: newRequest("GET", "http://localhost?foo=12"),
  776. vars: map[string]string{},
  777. host: "",
  778. path: "",
  779. shouldMatch: false,
  780. },
  781. {
  782. title: "Queries route with hyphenated name, match",
  783. route: new(Route).Queries("foo", "{v-1}"),
  784. request: newRequest("GET", "http://localhost?foo=bar"),
  785. vars: map[string]string{"v-1": "bar"},
  786. host: "",
  787. path: "",
  788. shouldMatch: true,
  789. },
  790. {
  791. title: "Queries route with multiple hyphenated names, match",
  792. route: new(Route).Queries("foo", "{v-1}", "baz", "{v-2}"),
  793. request: newRequest("GET", "http://localhost?foo=bar&baz=ding"),
  794. vars: map[string]string{"v-1": "bar", "v-2": "ding"},
  795. host: "",
  796. path: "",
  797. shouldMatch: true,
  798. },
  799. {
  800. title: "Queries route with hyphenate name and pattern, match",
  801. route: new(Route).Queries("foo", "{v-1:[0-9]+}"),
  802. request: newRequest("GET", "http://localhost?foo=10"),
  803. vars: map[string]string{"v-1": "10"},
  804. host: "",
  805. path: "",
  806. shouldMatch: true,
  807. },
  808. {
  809. title: "Queries route with hyphenated name and pattern with quantifier, additional capturing group",
  810. route: new(Route).Queries("foo", "{v-1:[0-9]{1}(?:a|b)}"),
  811. request: newRequest("GET", "http://localhost?foo=1a"),
  812. vars: map[string]string{"v-1": "1a"},
  813. host: "",
  814. path: "",
  815. shouldMatch: true,
  816. },
  817. {
  818. title: "Queries route with empty value, should match",
  819. route: new(Route).Queries("foo", ""),
  820. request: newRequest("GET", "http://localhost?foo=bar"),
  821. vars: map[string]string{},
  822. host: "",
  823. path: "",
  824. shouldMatch: true,
  825. },
  826. {
  827. title: "Queries route with empty value and no parameter in request, should not match",
  828. route: new(Route).Queries("foo", ""),
  829. request: newRequest("GET", "http://localhost"),
  830. vars: map[string]string{},
  831. host: "",
  832. path: "",
  833. shouldMatch: false,
  834. },
  835. {
  836. title: "Queries route with empty value and empty parameter in request, should match",
  837. route: new(Route).Queries("foo", ""),
  838. request: newRequest("GET", "http://localhost?foo="),
  839. vars: map[string]string{},
  840. host: "",
  841. path: "",
  842. shouldMatch: true,
  843. },
  844. {
  845. title: "Queries route with overlapping value, should not match",
  846. route: new(Route).Queries("foo", "bar"),
  847. request: newRequest("GET", "http://localhost?foo=barfoo"),
  848. vars: map[string]string{},
  849. host: "",
  850. path: "",
  851. shouldMatch: false,
  852. },
  853. {
  854. title: "Queries route with no parameter in request, should not match",
  855. route: new(Route).Queries("foo", "{bar}"),
  856. request: newRequest("GET", "http://localhost"),
  857. vars: map[string]string{},
  858. host: "",
  859. path: "",
  860. shouldMatch: false,
  861. },
  862. {
  863. title: "Queries route with empty parameter in request, should match",
  864. route: new(Route).Queries("foo", "{bar}"),
  865. request: newRequest("GET", "http://localhost?foo="),
  866. vars: map[string]string{"foo": ""},
  867. host: "",
  868. path: "",
  869. shouldMatch: true,
  870. },
  871. {
  872. title: "Queries route, bad submatch",
  873. route: new(Route).Queries("foo", "bar", "baz", "ding"),
  874. request: newRequest("GET", "http://localhost?fffoo=bar&baz=dingggg"),
  875. vars: map[string]string{},
  876. host: "",
  877. path: "",
  878. shouldMatch: false,
  879. },
  880. }
  881. for _, test := range tests {
  882. testRoute(t, test)
  883. testTemplate(t, test)
  884. testUseEscapedRoute(t, test)
  885. }
  886. }
  887. func TestSchemes(t *testing.T) {
  888. tests := []routeTest{
  889. // Schemes
  890. {
  891. title: "Schemes route, match https",
  892. route: new(Route).Schemes("https", "ftp"),
  893. request: newRequest("GET", "https://localhost"),
  894. vars: map[string]string{},
  895. host: "",
  896. path: "",
  897. shouldMatch: true,
  898. },
  899. {
  900. title: "Schemes route, match ftp",
  901. route: new(Route).Schemes("https", "ftp"),
  902. request: newRequest("GET", "ftp://localhost"),
  903. vars: map[string]string{},
  904. host: "",
  905. path: "",
  906. shouldMatch: true,
  907. },
  908. {
  909. title: "Schemes route, bad scheme",
  910. route: new(Route).Schemes("https", "ftp"),
  911. request: newRequest("GET", "http://localhost"),
  912. vars: map[string]string{},
  913. host: "",
  914. path: "",
  915. shouldMatch: false,
  916. },
  917. }
  918. for _, test := range tests {
  919. testRoute(t, test)
  920. testTemplate(t, test)
  921. }
  922. }
  923. func TestMatcherFunc(t *testing.T) {
  924. m := func(r *http.Request, m *RouteMatch) bool {
  925. if r.URL.Host == "aaa.bbb.ccc" {
  926. return true
  927. }
  928. return false
  929. }
  930. tests := []routeTest{
  931. {
  932. title: "MatchFunc route, match",
  933. route: new(Route).MatcherFunc(m),
  934. request: newRequest("GET", "http://aaa.bbb.ccc"),
  935. vars: map[string]string{},
  936. host: "",
  937. path: "",
  938. shouldMatch: true,
  939. },
  940. {
  941. title: "MatchFunc route, non-match",
  942. route: new(Route).MatcherFunc(m),
  943. request: newRequest("GET", "http://aaa.222.ccc"),
  944. vars: map[string]string{},
  945. host: "",
  946. path: "",
  947. shouldMatch: false,
  948. },
  949. }
  950. for _, test := range tests {
  951. testRoute(t, test)
  952. testTemplate(t, test)
  953. }
  954. }
  955. func TestBuildVarsFunc(t *testing.T) {
  956. tests := []routeTest{
  957. {
  958. title: "BuildVarsFunc set on route",
  959. route: new(Route).Path(`/111/{v1:\d}{v2:.*}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
  960. vars["v1"] = "3"
  961. vars["v2"] = "a"
  962. return vars
  963. }),
  964. request: newRequest("GET", "http://localhost/111/2"),
  965. path: "/111/3a",
  966. pathTemplate: `/111/{v1:\d}{v2:.*}`,
  967. shouldMatch: true,
  968. },
  969. {
  970. title: "BuildVarsFunc set on route and parent route",
  971. route: new(Route).PathPrefix(`/{v1:\d}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
  972. vars["v1"] = "2"
  973. return vars
  974. }).Subrouter().Path(`/{v2:\w}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
  975. vars["v2"] = "b"
  976. return vars
  977. }),
  978. request: newRequest("GET", "http://localhost/1/a"),
  979. path: "/2/b",
  980. pathTemplate: `/{v1:\d}/{v2:\w}`,
  981. shouldMatch: true,
  982. },
  983. }
  984. for _, test := range tests {
  985. testRoute(t, test)
  986. testTemplate(t, test)
  987. }
  988. }
  989. func TestSubRouter(t *testing.T) {
  990. subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter()
  991. subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter()
  992. subrouter3 := new(Route).PathPrefix("/foo").Subrouter()
  993. subrouter4 := new(Route).PathPrefix("/foo/bar").Subrouter()
  994. subrouter5 := new(Route).PathPrefix("/{category}").Subrouter()
  995. tests := []routeTest{
  996. {
  997. route: subrouter1.Path("/{v2:[a-z]+}"),
  998. request: newRequest("GET", "http://aaa.google.com/bbb"),
  999. vars: map[string]string{"v1": "aaa", "v2": "bbb"},
  1000. host: "aaa.google.com",
  1001. path: "/bbb",
  1002. pathTemplate: `/{v2:[a-z]+}`,
  1003. hostTemplate: `{v1:[a-z]+}.google.com`,
  1004. shouldMatch: true,
  1005. },
  1006. {
  1007. route: subrouter1.Path("/{v2:[a-z]+}"),
  1008. request: newRequest("GET", "http://111.google.com/111"),
  1009. vars: map[string]string{"v1": "aaa", "v2": "bbb"},
  1010. host: "aaa.google.com",
  1011. path: "/bbb",
  1012. pathTemplate: `/{v2:[a-z]+}`,
  1013. hostTemplate: `{v1:[a-z]+}.google.com`,
  1014. shouldMatch: false,
  1015. },
  1016. {
  1017. route: subrouter2.Path("/baz/{v2}"),
  1018. request: newRequest("GET", "http://localhost/foo/bar/baz/ding"),
  1019. vars: map[string]string{"v1": "bar", "v2": "ding"},
  1020. host: "",
  1021. path: "/foo/bar/baz/ding",
  1022. pathTemplate: `/foo/{v1}/baz/{v2}`,
  1023. shouldMatch: true,
  1024. },
  1025. {
  1026. route: subrouter2.Path("/baz/{v2}"),
  1027. request: newRequest("GET", "http://localhost/foo/bar"),
  1028. vars: map[string]string{"v1": "bar", "v2": "ding"},
  1029. host: "",
  1030. path: "/foo/bar/baz/ding",
  1031. pathTemplate: `/foo/{v1}/baz/{v2}`,
  1032. shouldMatch: false,
  1033. },
  1034. {
  1035. route: subrouter3.Path("/"),
  1036. request: newRequest("GET", "http://localhost/foo/"),
  1037. vars: map[string]string{},
  1038. host: "",
  1039. path: "/foo/",
  1040. pathTemplate: `/foo/`,
  1041. shouldMatch: true,
  1042. },
  1043. {
  1044. route: subrouter3.Path(""),
  1045. request: newRequest("GET", "http://localhost/foo"),
  1046. vars: map[string]string{},
  1047. host: "",
  1048. path: "/foo",
  1049. pathTemplate: `/foo`,
  1050. shouldMatch: true,
  1051. },
  1052. {
  1053. route: subrouter4.Path("/"),
  1054. request: newRequest("GET", "http://localhost/foo/bar/"),
  1055. vars: map[string]string{},
  1056. host: "",
  1057. path: "/foo/bar/",
  1058. pathTemplate: `/foo/bar/`,
  1059. shouldMatch: true,
  1060. },
  1061. {
  1062. route: subrouter4.Path(""),
  1063. request: newRequest("GET", "http://localhost/foo/bar"),
  1064. vars: map[string]string{},
  1065. host: "",
  1066. path: "/foo/bar",
  1067. pathTemplate: `/foo/bar`,
  1068. shouldMatch: true,
  1069. },
  1070. {
  1071. route: subrouter5.Path("/"),
  1072. request: newRequest("GET", "http://localhost/baz/"),
  1073. vars: map[string]string{"category": "baz"},
  1074. host: "",
  1075. path: "/baz/",
  1076. pathTemplate: `/{category}/`,
  1077. shouldMatch: true,
  1078. },
  1079. {
  1080. route: subrouter5.Path(""),
  1081. request: newRequest("GET", "http://localhost/baz"),
  1082. vars: map[string]string{"category": "baz"},
  1083. host: "",
  1084. path: "/baz",
  1085. pathTemplate: `/{category}`,
  1086. shouldMatch: true,
  1087. },
  1088. }
  1089. for _, test := range tests {
  1090. testRoute(t, test)
  1091. testTemplate(t, test)
  1092. testUseEscapedRoute(t, test)
  1093. }
  1094. }
  1095. func TestNamedRoutes(t *testing.T) {
  1096. r1 := NewRouter()
  1097. r1.NewRoute().Name("a")
  1098. r1.NewRoute().Name("b")
  1099. r1.NewRoute().Name("c")
  1100. r2 := r1.NewRoute().Subrouter()
  1101. r2.NewRoute().Name("d")
  1102. r2.NewRoute().Name("e")
  1103. r2.NewRoute().Name("f")
  1104. r3 := r2.NewRoute().Subrouter()
  1105. r3.NewRoute().Name("g")
  1106. r3.NewRoute().Name("h")
  1107. r3.NewRoute().Name("i")
  1108. if r1.namedRoutes == nil || len(r1.namedRoutes) != 9 {
  1109. t.Errorf("Expected 9 named routes, got %v", r1.namedRoutes)
  1110. } else if r1.Get("i") == nil {
  1111. t.Errorf("Subroute name not registered")
  1112. }
  1113. }
  1114. func TestStrictSlash(t *testing.T) {
  1115. r := NewRouter()
  1116. r.StrictSlash(true)
  1117. tests := []routeTest{
  1118. {
  1119. title: "Redirect path without slash",
  1120. route: r.NewRoute().Path("/111/"),
  1121. request: newRequest("GET", "http://localhost/111"),
  1122. vars: map[string]string{},
  1123. host: "",
  1124. path: "/111/",
  1125. shouldMatch: true,
  1126. shouldRedirect: true,
  1127. },
  1128. {
  1129. title: "Do not redirect path with slash",
  1130. route: r.NewRoute().Path("/111/"),
  1131. request: newRequest("GET", "http://localhost/111/"),
  1132. vars: map[string]string{},
  1133. host: "",
  1134. path: "/111/",
  1135. shouldMatch: true,
  1136. shouldRedirect: false,
  1137. },
  1138. {
  1139. title: "Redirect path with slash",
  1140. route: r.NewRoute().Path("/111"),
  1141. request: newRequest("GET", "http://localhost/111/"),
  1142. vars: map[string]string{},
  1143. host: "",
  1144. path: "/111",
  1145. shouldMatch: true,
  1146. shouldRedirect: true,
  1147. },
  1148. {
  1149. title: "Do not redirect path without slash",
  1150. route: r.NewRoute().Path("/111"),
  1151. request: newRequest("GET", "http://localhost/111"),
  1152. vars: map[string]string{},
  1153. host: "",
  1154. path: "/111",
  1155. shouldMatch: true,
  1156. shouldRedirect: false,
  1157. },
  1158. {
  1159. title: "Propagate StrictSlash to subrouters",
  1160. route: r.NewRoute().PathPrefix("/static/").Subrouter().Path("/images/"),
  1161. request: newRequest("GET", "http://localhost/static/images"),
  1162. vars: map[string]string{},
  1163. host: "",
  1164. path: "/static/images/",
  1165. shouldMatch: true,
  1166. shouldRedirect: true,
  1167. },
  1168. {
  1169. title: "Ignore StrictSlash for path prefix",
  1170. route: r.NewRoute().PathPrefix("/static/"),
  1171. request: newRequest("GET", "http://localhost/static/logo.png"),
  1172. vars: map[string]string{},
  1173. host: "",
  1174. path: "/static/",
  1175. shouldMatch: true,
  1176. shouldRedirect: false,
  1177. },
  1178. }
  1179. for _, test := range tests {
  1180. testRoute(t, test)
  1181. testTemplate(t, test)
  1182. testUseEscapedRoute(t, test)
  1183. }
  1184. }
  1185. func TestUseEncodedPath(t *testing.T) {
  1186. r := NewRouter()
  1187. r.UseEncodedPath()
  1188. tests := []routeTest{
  1189. {
  1190. title: "Router with useEncodedPath, URL with encoded slash does match",
  1191. route: r.NewRoute().Path("/v1/{v1}/v2"),
  1192. request: newRequest("GET", "http://localhost/v1/1%2F2/v2"),
  1193. vars: map[string]string{"v1": "1%2F2"},
  1194. host: "",
  1195. path: "/v1/1%2F2/v2",
  1196. pathTemplate: `/v1/{v1}/v2`,
  1197. shouldMatch: true,
  1198. },
  1199. {
  1200. title: "Router with useEncodedPath, URL with encoded slash doesn't match",
  1201. route: r.NewRoute().Path("/v1/1/2/v2"),
  1202. request: newRequest("GET", "http://localhost/v1/1%2F2/v2"),
  1203. vars: map[string]string{"v1": "1%2F2"},
  1204. host: "",
  1205. path: "/v1/1%2F2/v2",
  1206. pathTemplate: `/v1/1/2/v2`,
  1207. shouldMatch: false,
  1208. },
  1209. }
  1210. for _, test := range tests {
  1211. testRoute(t, test)
  1212. testTemplate(t, test)
  1213. }
  1214. }
  1215. func TestWalkSingleDepth(t *testing.T) {
  1216. r0 := NewRouter()
  1217. r1 := NewRouter()
  1218. r2 := NewRouter()
  1219. r0.Path("/g")
  1220. r0.Path("/o")
  1221. r0.Path("/d").Handler(r1)
  1222. r0.Path("/r").Handler(r2)
  1223. r0.Path("/a")
  1224. r1.Path("/z")
  1225. r1.Path("/i")
  1226. r1.Path("/l")
  1227. r1.Path("/l")
  1228. r2.Path("/i")
  1229. r2.Path("/l")
  1230. r2.Path("/l")
  1231. paths := []string{"g", "o", "r", "i", "l", "l", "a"}
  1232. depths := []int{0, 0, 0, 1, 1, 1, 0}
  1233. i := 0
  1234. err := r0.Walk(func(route *Route, router *Router, ancestors []*Route) error {
  1235. matcher := route.matchers[0].(*routeRegexp)
  1236. if matcher.template == "/d" {
  1237. return SkipRouter
  1238. }
  1239. if len(ancestors) != depths[i] {
  1240. t.Errorf(`Expected depth of %d at i = %d; got "%d"`, depths[i], i, len(ancestors))
  1241. }
  1242. if matcher.template != "/"+paths[i] {
  1243. t.Errorf(`Expected "/%s" at i = %d; got "%s"`, paths[i], i, matcher.template)
  1244. }
  1245. i++
  1246. return nil
  1247. })
  1248. if err != nil {
  1249. panic(err)
  1250. }
  1251. if i != len(paths) {
  1252. t.Errorf("Expected %d routes, found %d", len(paths), i)
  1253. }
  1254. }
  1255. func TestWalkNested(t *testing.T) {
  1256. router := NewRouter()
  1257. g := router.Path("/g").Subrouter()
  1258. o := g.PathPrefix("/o").Subrouter()
  1259. r := o.PathPrefix("/r").Subrouter()
  1260. i := r.PathPrefix("/i").Subrouter()
  1261. l1 := i.PathPrefix("/l").Subrouter()
  1262. l2 := l1.PathPrefix("/l").Subrouter()
  1263. l2.Path("/a")
  1264. paths := []string{"/g", "/g/o", "/g/o/r", "/g/o/r/i", "/g/o/r/i/l", "/g/o/r/i/l/l", "/g/o/r/i/l/l/a"}
  1265. idx := 0
  1266. err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
  1267. path := paths[idx]
  1268. tpl := route.regexp.path.template
  1269. if tpl != path {
  1270. t.Errorf(`Expected %s got %s`, path, tpl)
  1271. }
  1272. idx++
  1273. return nil
  1274. })
  1275. if err != nil {
  1276. panic(err)
  1277. }
  1278. if idx != len(paths) {
  1279. t.Errorf("Expected %d routes, found %d", len(paths), idx)
  1280. }
  1281. }
  1282. func TestWalkErrorRoute(t *testing.T) {
  1283. router := NewRouter()
  1284. router.Path("/g")
  1285. expectedError := errors.New("error")
  1286. err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
  1287. return expectedError
  1288. })
  1289. if err != expectedError {
  1290. t.Errorf("Expected %v routes, found %v", expectedError, err)
  1291. }
  1292. }
  1293. func TestWalkErrorMatcher(t *testing.T) {
  1294. router := NewRouter()
  1295. expectedError := router.Path("/g").Subrouter().Path("").GetError()
  1296. err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
  1297. return route.GetError()
  1298. })
  1299. if err != expectedError {
  1300. t.Errorf("Expected %v routes, found %v", expectedError, err)
  1301. }
  1302. }
  1303. func TestWalkErrorHandler(t *testing.T) {
  1304. handler := NewRouter()
  1305. expectedError := handler.Path("/path").Subrouter().Path("").GetError()
  1306. router := NewRouter()
  1307. router.Path("/g").Handler(handler)
  1308. err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
  1309. return route.GetError()
  1310. })
  1311. if err != expectedError {
  1312. t.Errorf("Expected %v routes, found %v", expectedError, err)
  1313. }
  1314. }
  1315. func TestSubrouterErrorHandling(t *testing.T) {
  1316. superRouterCalled := false
  1317. subRouterCalled := false
  1318. router := NewRouter()
  1319. router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1320. superRouterCalled = true
  1321. })
  1322. subRouter := router.PathPrefix("/bign8").Subrouter()
  1323. subRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1324. subRouterCalled = true
  1325. })
  1326. req, _ := http.NewRequest("GET", "http://localhost/bign8/was/here", nil)
  1327. router.ServeHTTP(NewRecorder(), req)
  1328. if superRouterCalled {
  1329. t.Error("Super router 404 handler called when sub-router 404 handler is available.")
  1330. }
  1331. if !subRouterCalled {
  1332. t.Error("Sub-router 404 handler was not called.")
  1333. }
  1334. }
  1335. // See: https://github.com/gorilla/mux/issues/200
  1336. func TestPanicOnCapturingGroups(t *testing.T) {
  1337. defer func() {
  1338. if recover() == nil {
  1339. t.Errorf("(Test that capturing groups now fail fast) Expected panic, however test completed sucessfully.\n")
  1340. }
  1341. }()
  1342. NewRouter().NewRoute().Path("/{type:(promo|special)}/{promoId}.json")
  1343. }
  1344. // ----------------------------------------------------------------------------
  1345. // Helpers
  1346. // ----------------------------------------------------------------------------
  1347. func getRouteTemplate(route *Route) string {
  1348. host, err := route.GetHostTemplate()
  1349. if err != nil {
  1350. host = "none"
  1351. }
  1352. path, err := route.GetPathTemplate()
  1353. if err != nil {
  1354. path = "none"
  1355. }
  1356. return fmt.Sprintf("Host: %v, Path: %v", host, path)
  1357. }
  1358. func testRoute(t *testing.T, test routeTest) {
  1359. request := test.request
  1360. route := test.route
  1361. vars := test.vars
  1362. shouldMatch := test.shouldMatch
  1363. host := test.host
  1364. path := test.path
  1365. url := test.host + test.path
  1366. shouldRedirect := test.shouldRedirect
  1367. var match RouteMatch
  1368. ok := route.Match(request, &match)
  1369. if ok != shouldMatch {
  1370. msg := "Should match"
  1371. if !shouldMatch {
  1372. msg = "Should not match"
  1373. }
  1374. t.Errorf("(%v) %v:\nRoute: %#v\nRequest: %#v\nVars: %v\n", test.title, msg, route, request, vars)
  1375. return
  1376. }
  1377. if shouldMatch {
  1378. if test.vars != nil && !stringMapEqual(test.vars, match.Vars) {
  1379. t.Errorf("(%v) Vars not equal: expected %v, got %v", test.title, vars, match.Vars)
  1380. return
  1381. }
  1382. if host != "" {
  1383. u, _ := test.route.URLHost(mapToPairs(match.Vars)...)
  1384. if host != u.Host {
  1385. t.Errorf("(%v) URLHost not equal: expected %v, got %v -- %v", test.title, host, u.Host, getRouteTemplate(route))
  1386. return
  1387. }
  1388. }
  1389. if path != "" {
  1390. u, _ := route.URLPath(mapToPairs(match.Vars)...)
  1391. if path != u.Path {
  1392. t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, path, u.Path, getRouteTemplate(route))
  1393. return
  1394. }
  1395. }
  1396. if url != "" {
  1397. u, _ := route.URL(mapToPairs(match.Vars)...)
  1398. if url != u.Host+u.Path {
  1399. t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, url, u.Host+u.Path, getRouteTemplate(route))
  1400. return
  1401. }
  1402. }
  1403. if shouldRedirect && match.Handler == nil {
  1404. t.Errorf("(%v) Did not redirect", test.title)
  1405. return
  1406. }
  1407. if !shouldRedirect && match.Handler != nil {
  1408. t.Errorf("(%v) Unexpected redirect", test.title)
  1409. return
  1410. }
  1411. }
  1412. }
  1413. func testUseEscapedRoute(t *testing.T, test routeTest) {
  1414. test.route.useEncodedPath = true
  1415. testRoute(t, test)
  1416. }
  1417. func testTemplate(t *testing.T, test routeTest) {
  1418. route := test.route
  1419. pathTemplate := test.pathTemplate
  1420. if len(pathTemplate) == 0 {
  1421. pathTemplate = test.path
  1422. }
  1423. hostTemplate := test.hostTemplate
  1424. if len(hostTemplate) == 0 {
  1425. hostTemplate = test.host
  1426. }
  1427. routePathTemplate, pathErr := route.GetPathTemplate()
  1428. if pathErr == nil && routePathTemplate != pathTemplate {
  1429. t.Errorf("(%v) GetPathTemplate not equal: expected %v, got %v", test.title, pathTemplate, routePathTemplate)
  1430. }
  1431. routeHostTemplate, hostErr := route.GetHostTemplate()
  1432. if hostErr == nil && routeHostTemplate != hostTemplate {
  1433. t.Errorf("(%v) GetHostTemplate not equal: expected %v, got %v", test.title, hostTemplate, routeHostTemplate)
  1434. }
  1435. }
  1436. type TestA301ResponseWriter struct {
  1437. hh http.Header
  1438. status int
  1439. }
  1440. func (ho TestA301ResponseWriter) Header() http.Header {
  1441. return http.Header(ho.hh)
  1442. }
  1443. func (ho TestA301ResponseWriter) Write(b []byte) (int, error) {
  1444. return 0, nil
  1445. }
  1446. func (ho TestA301ResponseWriter) WriteHeader(code int) {
  1447. ho.status = code
  1448. }
  1449. func Test301Redirect(t *testing.T) {
  1450. m := make(http.Header)
  1451. func1 := func(w http.ResponseWriter, r *http.Request) {}
  1452. func2 := func(w http.ResponseWriter, r *http.Request) {}
  1453. r := NewRouter()
  1454. r.HandleFunc("/api/", func2).Name("func2")
  1455. r.HandleFunc("/", func1).Name("func1")
  1456. req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil)
  1457. res := TestA301ResponseWriter{
  1458. hh: m,
  1459. status: 0,
  1460. }
  1461. r.ServeHTTP(&res, req)
  1462. if "http://localhost/api/?abc=def" != res.hh["Location"][0] {
  1463. t.Errorf("Should have complete URL with query string")
  1464. }
  1465. }
  1466. func TestSkipClean(t *testing.T) {
  1467. func1 := func(w http.ResponseWriter, r *http.Request) {}
  1468. func2 := func(w http.ResponseWriter, r *http.Request) {}
  1469. r := NewRouter()
  1470. r.SkipClean(true)
  1471. r.HandleFunc("/api/", func2).Name("func2")
  1472. r.HandleFunc("/", func1).Name("func1")
  1473. req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil)
  1474. res := NewRecorder()
  1475. r.ServeHTTP(res, req)
  1476. if len(res.HeaderMap["Location"]) != 0 {
  1477. t.Errorf("Shouldn't redirect since skip clean is disabled")
  1478. }
  1479. }
  1480. // https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW
  1481. func TestSubrouterHeader(t *testing.T) {
  1482. expected := "func1 response"
  1483. func1 := func(w http.ResponseWriter, r *http.Request) {
  1484. fmt.Fprint(w, expected)
  1485. }
  1486. func2 := func(http.ResponseWriter, *http.Request) {}
  1487. r := NewRouter()
  1488. s := r.Headers("SomeSpecialHeader", "").Subrouter()
  1489. s.HandleFunc("/", func1).Name("func1")
  1490. r.HandleFunc("/", func2).Name("func2")
  1491. req, _ := http.NewRequest("GET", "http://localhost/", nil)
  1492. req.Header.Add("SomeSpecialHeader", "foo")
  1493. match := new(RouteMatch)
  1494. matched := r.Match(req, match)
  1495. if !matched {
  1496. t.Errorf("Should match request")
  1497. }
  1498. if match.Route.GetName() != "func1" {
  1499. t.Errorf("Expecting func1 handler, got %s", match.Route.GetName())
  1500. }
  1501. resp := NewRecorder()
  1502. match.Handler.ServeHTTP(resp, req)
  1503. if resp.Body.String() != expected {
  1504. t.Errorf("Expecting %q", expected)
  1505. }
  1506. }
  1507. // mapToPairs converts a string map to a slice of string pairs
  1508. func mapToPairs(m map[string]string) []string {
  1509. var i int
  1510. p := make([]string, len(m)*2)
  1511. for k, v := range m {
  1512. p[i] = k
  1513. p[i+1] = v
  1514. i += 2
  1515. }
  1516. return p
  1517. }
  1518. // stringMapEqual checks the equality of two string maps
  1519. func stringMapEqual(m1, m2 map[string]string) bool {
  1520. nil1 := m1 == nil
  1521. nil2 := m2 == nil
  1522. if nil1 != nil2 || len(m1) != len(m2) {
  1523. return false
  1524. }
  1525. for k, v := range m1 {
  1526. if v != m2[k] {
  1527. return false
  1528. }
  1529. }
  1530. return true
  1531. }
  1532. // newRequest is a helper function to create a new request with a method and url.
  1533. // The request returned is a 'server' request as opposed to a 'client' one through
  1534. // simulated write onto the wire and read off of the wire.
  1535. // The differences between requests are detailed in the net/http package.
  1536. func newRequest(method, url string) *http.Request {
  1537. req, err := http.NewRequest(method, url, nil)
  1538. if err != nil {
  1539. panic(err)
  1540. }
  1541. // extract the escaped original host+path from url
  1542. // http://localhost/path/here?v=1#frag -> //localhost/path/here
  1543. opaque := ""
  1544. if i := len(req.URL.Scheme); i > 0 {
  1545. opaque = url[i+1:]
  1546. }
  1547. if i := strings.LastIndex(opaque, "?"); i > -1 {
  1548. opaque = opaque[:i]
  1549. }
  1550. if i := strings.LastIndex(opaque, "#"); i > -1 {
  1551. opaque = opaque[:i]
  1552. }
  1553. // Escaped host+path workaround as detailed in https://golang.org/pkg/net/url/#URL
  1554. // for < 1.5 client side workaround
  1555. req.URL.Opaque = opaque
  1556. // Simulate writing to wire
  1557. var buff bytes.Buffer
  1558. req.Write(&buff)
  1559. ioreader := bufio.NewReader(&buff)
  1560. // Parse request off of 'wire'
  1561. req, err = http.ReadRequest(ioreader)
  1562. if err != nil {
  1563. panic(err)
  1564. }
  1565. return req
  1566. }