Path arguments are request arguments that are extracted from a URL’s path rather than its query string.
Warning! Path arguments interfere with multicast as well as locality-aware routing.
In the typical case, endpoints of a microservice have fixed URLs at which they are reachable. Consider the following service.yaml
specification.
general:
host: calculator.example
functions:
- signature: Add(x int, y int) (sum int)
description: Add adds two integers and returns their sum.
The Add
endpoint is reachable at the internal Microbus
URL of https://calculator.example/add
or the external URL of https://localhost:8080/calculator.example/add
assuming that the ingress proxy is listening at localhost:8080
. The arguments x
and y
of the function are unmarshaled from the request query argument or from the body of the request.
GET /add?x=5&y=5 HTTP/1.1
Host: calculator.example
POST /add?x=5&y=5 HTTP/1.1
Host: calculator.example
{"x":5,"y":5}
A fixed path is consistent with the RPC over JSON style of API but is insufficient for implementing a RESTful style of API where it is common to expect input arguments in the path of the request. This is where path arguments come into play.
Consider the following service.yaml
specification that defines a path argument {id}
and a corresponding function argument id int
:
general:
host: articles.example
functions:
- signature: Load(id int) (article *Article)
description: Load looks for an article by its ID.
path: //article/{id}
method: GET
The Load
endpoint is now reachable at the internal Microbus
wildcard URL of https://article/{id}
where {id}
is expected to be an int
. The argument id
of the function is unmarshaled from the second part of the path because it shares the same name as the path argument.
GET /1 HTTP/1.1
Host: article
A typical path argument only captures the data in one part of the path, i.e. between two /
s in the path or after the last /
. The values of these arguments therefore cannot contain a /
. However, multiple such path arguments may be defined in the path.
functions:
- signature: LoadComment(articleID int, commentID int) (comment *Comment)
description: LoadComment looks for a comment of an article by its ID.
path: //article/{articleID}/comment/{commentID}
method: GET
A greedy path argument on the other hand captures the remainder of the path and can span multiple parts of the path and include /
s. A greedy path argument must be the last element in the path specification. Greedy path arguments are denoted using a +
in their definition, e.g. {greedy+}
.
functions:
- signature: LoadFile(category int, filePath string) (data []byte)
description: LoadFile looks for a file by its path.
path: //file/{category}/{filePath+}
method: GET
Path arguments that are left unnamed are automatically given the names path1
, path2
etc. in order of their appearance. In the following example, the three unnamed path arguments are named path1
, path2
and path3
. It is recommended to name path arguments and avoid this ambiguity.
functions:
- signature: UnnamedPathArguments(path1 int, path2 int, path3 string) (ok bool)
description: UnnamedPathArguments demonstrates unnamed arguments.
path: /foo/{}/bar/{}/greedy/{+}
method: GET
Path arguments are a form of wildcard subscription and might overlap if not crafted carefully. In the following case, requests to /hello/world
will alternate between the two handlers and will result in unpredictable behavior.
functions:
- signature: CatchAll(suffix string) (ok bool)
description: CatchAll catches all requests.
path: /{suffix+}
method: GET
- signature: Hello(name string) (n int)
description: Hello's subscription is a subset of CatchAll's.
path: /hello/{name}
method: GET
Path arguments work also for web handlers but they must be parsed manually from the request’s path. Consider the following example of a web handler:
web:
- signature: AvatarImage()
description: AvatarImage serves the avatar image of the user.
path: /avatar/{uid}/{size}/{name+}
method: GET
func (svc *Service) AvatarImage(w http.ResponseWriter, r *http.Request) (err error) {
// Path arguments must be manually extracted from the path
parts := strings.Split(r.URL.Path, "/") // ["", "avatar", "{uid}", "{size}", "{name}", "..."]
uid = parts[2]
size = parts[3]
name = strings.Join(parts[4:], "/")
return serveImage(uid, size)
}
Path arguments are not recommended for pervasive (multicast) endpoints, events or sinks. Using path arguments in these cases will result in significantly slower response times because they interfere with an optimization that relies on a fixed URL pattern.
functions:
- signature: NotAGoodIdea(id int) (ok bool)
description: NotAGoodIdea mixes a path argument with pervasive routing.
path: /data/{id}
queue: none
In addition, the variable nature of the path also interferes with locality-aware routing and relevant requests will route randomly instead.
Given these constraints, it is advised to use path arguments only for external-facing endpoints and use fixed paths for internal service-to-service communications.