Subroutines

VCL services do not have a single entry point or main function, but rather a number of predefined subroutines that are called at various stages of the request lifecycle. See using VCL to learn more about how and when each subroutine is called. It is also possible to define your own custom subroutines and call them from the built-in ones.

Built-in (lifecycle) subroutines

The following subroutines are part of the VCL lifecycle:

Custom subroutines

VCL subroutines allow users to isolate code and avoid repetition. They are defined using the sub keyword:

sub compression_check {
if (req.http.Accept-Encoding ~ "gzip|br") {
set req.http.Compression-Accepted-By-Client = "yes";
}
}

Subroutines may optionally have a type, and return a value in accordance with their type:

sub supports_compression BOOL {
if (req.http.Accept-Encoding ~ "gzip|br") {
return true;
}
return false;
}

If no type is supplied, the type is implicitly VOID.

Calling a subroutine

IMPORTANT: The validity of a subroutine to the compiler depends on where it is called from. A subroutine called from vcl_recv may contain only code that would be valid within the vcl_recv subroutine.

VOID-typed custom subroutines (those that do not return a value) are called using the call statement:

sub vcl_recv {
# set the compression-accepted-by-client header if applicable
call compression_check;
}

Typed subroutines other than VOID are called by placing them to the right hand side of an assignment followed by parentheses ():

sub vcl_recv {
declare local var.compressed BOOL;
set var.compressed = supports_compression();
}

Returning a state

In the VCL lifecycle, built-in subroutines return a state indicating what Fastly should do next. Custom subroutines may also return a state as if they were the built-in subroutine that called them. For instance, vcl_recv might call a custom subroutine that includes return (lookup);. This type of return acts as if it had been placed within the calling subroutine, and terminates processing of both the custom subroutine and the parent built-in subroutine.

HINT: Returning a state is different from returning a value, and while all subroutines can return a state, it doesn't affect the subroutine's declared return type. It's also possible for a typed custom subroutine to return a state instead of the expected value. In that case the returned state will be used to terminate the parent lifecycle subroutine. For example, this custom subroutine is typed as a BOOL but returns a state which ends the processing of vcl_recv:

sub supports_compression BOOL {
if (req.http.Accept-Encoding ~ "gzip|br") {
return(lookup); # Ends the parent sub and proceeds to 'lookup' (assignment is cancelled)
}
return false; # Passes control back to parent sub and assigns the value false
}

Returning a state makes custom subroutines sensitive to where they are called from. If a custom subroutine contains a return (fetch); statement, it can no longer be called from any context other than vcl_miss (the only built-in subroutine to have a valid return state of fetch). The same applies to using variables scoped to specific subroutines within custom subroutines.

Concatenation

Built-in subroutines with the same name are automatically concatenated in the order the compiler encounters them. Note that a return statement in one subroutine also returns from subsequent concatenated subroutines.

sub vcl_recv {
return (lookup);
}
sub vcl_recv {
# this code won't run due to the return statement in the prior vcl_recv
# if the order of these two subs were reversed, the origin would be set before returning
set req.backend = origin;
}

Custom subroutines may not be concatenated. Multiple declarations of the same (non-built-in) name are not permitted.

Recursion

Subroutines may not call themselves, either directly or via another subroutine. Even if the loop is not infinite, the compiler will still disallow it.