Jenkins : Hyperlinks in HTML

This article was prompted by JENKINS-16368.

Because there are all sorts of ways Jenkins is deployed in the real world, Jenkins core and plugins need to follow a specific set of guidelines when it comes to putting hyperlinks inside HTML and elsewhere. Here are those rules:

Hyperlinks inside HTML should either use relative URLs (such as "../foo/bar" or "build/5/changes") or what I call app relative URL (relative URLs that starts with '/', such as "/job/foo/5" and "/manage"), but no absolute URL (such as "http://jenkins/foo/1")

This rule is because web applications have no way of knowing what URL the client is using to access the server. For example, with a reverse proxy in between, the user might be accessing https://jenkins.example.com/job/foo/5 which then get routed to http://sca14-1250.corp.example.com:8080/job/foo/5. On the other hand, the way the reverse proxying works ensures that the path component (the "/job/foo/5" portion in the aboe example) will remain intact. Therefore, it is best to use relative URLs or app relative URLs, then leave the browser to absolutize it with the current absolute URL it is using to access the server.

reverse proxy and path component

Technically speaking, it is Jenkins that's making a claim that we'll require the path component remains the same even when a reverse proxy is used.

In Java EE, the path component is further separated into two portions. The first is the context path, followed by the path info. For example, if the URL is http://server.example.com/jenkins/job/foo/5, then "/jenkins" is the context path and "/job/foo/5" is the path info. As a Jenkins developer, we control the latter, but the user controls the former. Therefore, to properly generate app relative URLs, you need to keep this in mind. Many people runs Jenkins with empty context path, so failing to prepend the context path is a common mistake.

  • Method name getUrl() is used throughout in Jenkins to return the path info portion of the URL of domain objects. For example, Build.getUrl() would return "job/foo/32/" (there's some inconsistency in the way trailing '/' is handled for historical reasons, but there's always no leading '/')
  • Method name getAbsoluteUrl() is used throught in Jenkins to return the absolute URL of domain objects. As the rule #1 indicates, this method must not be used to render hyperlinks
  • In Groovy/Jelly view as well as in JavaScript, a variable named "rootURL" is defined to represent the context path. Therefore, to generate app relative hyperlink, you write <a href="${rootURL}/${it.url}">...</a> (in Jelly) or a(href:"${rootURL}/${my.url}", ...) (for Groovy) in your views.

Rule #2: Static resource URL

Images, JavaScripts, stylesheets, and other static resources that do not change while Jenkins is running should be accessed from with a static resource URL prefix /static/XXXXXXXX, such as /static/907dbcb0/plugin/foobar/abc.png. The XXXXXXXX section is a random string generated by Jenkins for the duration of a running Jenkins session.

The routing of the request will be the same with or without the static prefix, but with the prefix, requests will be served with long Expires header, enabling the browser to cache them (and use them without contacting the server again with the "If-Modified-Since" header). The random string ensures that the browser never uses a stale file when plugins/core are updated.

To use these static resource URLs from Java code, use Functions.getResourcePath() as a prefix to your URL (see example). For Jelly views, use resURL variable (see example)

Rule #3: Generate absolute URLs in e-mails, IMs, etc.

If you are serving data outside HTTP response, such as an e-mail, then you do need to generate an absolute URL to Jenkins. To do this, use Jenkins.getInstance().getRootUrl(). This returns an absolute URL that the administrator has configured (with a fall back to some guessing), which is the only way to get the absolute URL of a Jenkins instance accurately. (Also see Jenkins says my reverse proxy setup is broken.)

Rule #4: ConsoleNote must be path-info only

When you embed hyperlinks into console output via HyperlinkNote, use the URL that starts with '/' to create a portable hyperlink. If the URL starts with '/', it's treated as relative to the context path. In this way, even when the URL of Jenkins has changed since the console note is created, the link will still render correctly.

For example,

void foo(TaskListener listener) {
  listener.getLogger().println(
    HyperlinkNote.encodeTo("/configure","Please configure your Jenkins"));
}

Also see ModelHyperlinkNote for more easily creating hyperlinks to various objects in Jenkins.

Is the transport secure?

In the presence of the reverse proxy that terminates HTTPS, Jenkins cannot trust HttpServletRequest#isSecure() to check if the transport is secure. For this purpose, there's Jenkins.isRootUrlSecure(), which checks if the admin-configured Jenkins URL is HTTPS.