Integrating Web Applications with Apache

When you deploy a web application, how do end users access it? Often web applications are set behind a gateway device through which end users can access it. One of the popular products to act as an application gateway on Linux is the Apache Web Server. Although it can function as a normal web server, it also has the ability to connect through it to other web servers.

In this article, I discuss what it takes to integrate a web application into Apache. This includes integrating the HTTP protocol functionality, customizing content to render properly and reusing pieces of configuration. Once you understand those basic bits of functionality, you'll have the tools you need to maximize your web applications' usability. So, let's get started!

Crash Course in RegEx

A mechanism that I use throughout this article that might need a brief introduction is Regular Expressions (or regex). Regex is used to define a text pattern to search for within a URL or to find and replace text within content, such as HTML or JavaScript. The text processing command sed uses regex to do searches and substitutions.

For each example below there will be three parts: input, regex pattern and output. The pattern will be applied to the input text and determine the value of the output text.

Example 1:


Input:
  Name: Frank Sinatra
  Genre: Jazz
  Name: 2Pac
  Genre: Rap
  Name: Reel Big Fish
  Genre: Ska

Regex pattern: "^Name: "

Output:
  Name: Frank Sinatra
  Name: 2Pac
  Name: Reel Big Fish

This example searches the input text for text that matches the pattern "^Name: ". This pattern says, "Look for the text 'Name: ' at the beginning of each line." Since there are two lines that begin with that text, only those two lines are returned. While "^" represents the beginning of a line, "$" represents the end of a line. So if you were to apply the pattern "a$", two lines would be returned (Frank Sinatra and Ska). Let's expand on that example and use the input from Example 1 with a new pattern.

Example 2:


Regex pattern: "^Name: [0-9]"

Output:
  Name: 2Pac

As you can see, I've taken the original regex pattern and added [0-9] to the end. This will search for a single character that can be any number from 0 to 9, which is why "2Pac" was the only line returned. You also can specify a range with alphabetic characters ([a-z] or [A-Z]).

Along with pattern selection, you also can do substitution with regex. There are two formats for regex substitutions: s|pattern|replace|modifier or s/pattern/replace/modifier. In Apache, I find it easier to use the pipe-style substitution. Example 3 uses the same input with a new pattern.

Example 3:


Regex pattern: "s|^(.*)Frank(.*)$|\1Dwezil\2|g"

Output:
  Name: Dwezil Sinatra
  Genre: Jazz
  Name: 2Pac
  Genre: Rap
  Name: Reel Big Fish
  Genre: Ska
  Name: Dwezil Zappa
  Genre: Unknown

This pattern has a lot to dissect. One of the great features of regex is the ability to match any character. The dot operator will match any one character. The asterisk operator will match 0 or more of whatever character or operator preceded it. Putting these two operators together matches 0 or more of any character. Enclosing this in parentheses allows the matched text to be represented in the replace portion of the pattern with a variable. In this case, \1 represents the first block of text within parentheses and \2 represents the second. The only characters that are explicitly being matched are "Frank". As such, the lines containing "Frank" will be replaced with everything up to "Frank" (represented by \1), "Dwezil", and everything following "Frank" (represented by \2). As you can see, the entirety of the text input was sent to the output although modified by the pattern.

Protocol Integration

When it is decided that an application would benefit from Apache integration, there is a high likelihood that it will reside on a separate server from Apache. To integrate applications being accessed via HTTP fully, any or all of these modules may be used: mod_rewrite, mod_proxy, mod_ssl and mod_headers. Each of these modules allows you to customize the way communication between the end user and web servers occurs from modifying HTTP header data to managing proxy connections to other servers.

First, let's look at mod_rewrite. There are a number of directives within the mod_rewrite module, but I cover only a handful here: RewriteEngine, RewriteCond and RewriteRule. The RewriteEngine directive simply enables URL rewriting and is invoked as follows:


RewriteEngine on

RewriteRule allows the server to respond to an HTTP request to a specific URL by, among other things, returning an HTTP redirect (code 301 or 302), which will redirect the end user to a specified URL or send a proxied request to a back-end server. Here's an example of issuing an HTTP redirect:


RewriteRule /google http://www.google.com [R=301]

In this example, when the URL of /google is accessed, the server will respond with an HTTP 301 that will redirect the user to http://www.google.com. This example will work only if the request URL is exactly equal to "/google". If the need is to redirect on any URL starting with "/google", you would define a conditional redirect using RewriteCond as follows:


RewriteCond "%{REQUEST_URI}" "/google.*$"
RewriteRule "^.*$" http://www.google.com [R=301]

The RewriteCond directive has two parts: a string value to check and a substring to search for. In this example, you are looking in the REQUEST_URI HTTP session variable for anything beginning with "/google". If that condition is met, the RewriteRule on the following line is executed. Because you are determining the value of the target URL in the RewriteCond, the value of the target URL in the RewriteRule is defined as "^.*$".

The examples given here are all user-facing events like a 301 redirect. The RewriteRule directive also can be used to proxy requests to a server. This is done behind the scenes unlike an HTTP redirect, so the request is forwarded without the users' knowledge. A proxied request may be configured like the example below:


RewriteRule "/home/(.*)$" http://back-end01.test:8080/$1 [P]

The above illustrates an example of a virtual root directory. When the user accesses anything underneath /home (note the ".*" expression), the request is sent to back-end01.test on port 8080 with the location set to the URL path beneath /home. For example, if the user tries to access /home/test/image.jpg, the request is sent to back-end01.test:8080 with a location of /test/image.jpg. A proxied RewriteRule also may be used in conjunction with RewriteCond for further customization. Note that this statement proxies only the HTTP request. Proxying of HTTP responses will require mod_proxy.

Another option for proxying HTTP connections through Apache is mod_proxy, which provides ProxyPass, ProxyPassReverse and ProxyPassMatch among many other directives that provide more robust proxying options. I focus primarily on these three directives here. As mentioned previously, RewriteRule provides proxying of HTTP requests. Let's compare the example already given for proxying with RewriteRule and an example for ProxyPass:


ProxyPass /home http://back-end01.test:8080/

This ProxyPass statement provides roughly the same level of functionality as the RewriteRule statement with a more simplistic command. When a request comes in for any URL beginning with "/home", the request header will be rewritten so that the request will be received properly by http://back-end01.test:8080/. Consider the following first lines of an HTTP request:


From user to server:    GET /home/test/image.jpg HTTP/1.1
From server to back-end:    GET /test/image.jpg HTTP/1.1

The first line of the header contains the method (GET in this case) and the URL being requested. When the server receives the request from the client, it strips off "/home", as specified in the ProxyPass directive and forwards the request to the back-end server. If you want to proxy response packets as well as request packets, the following ProxyPassReverse statement can be paired with the previous ProxyPass statement:


ProxyPassReverse /home http://back-end01.test:8080/

The syntax is exactly the same as ProxyPass, adding to the simplicity of the mod_proxy configuration. This will take any HTTP response matching an HTTP request for /home and forward the response back to the original client. If you need to add some programmatic proxying (similar to RewriteCond), you can use the ProxyPassMatch. When implementing a forward/reverse proxy configuration, ProxyPassMatch can replace ProxyPass. Here's an example:


ProxyPassMatch "^/home/([a-z0-9]*/docs)" http://docserver01.test:8080/$1
ProxyPassReverse /home http://docserver01.test:8080/

This example suggests that within the /home folder, there are many sub-folders (let's say user names) and within each of those exists a folder named "docs". The USERNAME/docs URL exists on docserver01.test:8080 in the root of the web server, as denoted by the $1 in the server URL. The ProxyPassReverse will function in the same manner as it did in the previous example.

Securing websites with SSL in Apache is accomplished with mod_ssl. Although I won't discuss configuring SSL from the ground up, a few directives relate to proxied SSL connections: SSLProxyCheckPeerExpire, SSLProxyCheckPeerName and SSLProxyCheckPeerCN. It is a common practice to use self-signed certificates on back-end servers (provided a valid cert is in place on the user-facing server), and these directives address common issues that can arise when using self-signed certs. Any of these directives can have one of two arguments provided: "on" or "off". If set to "off", SSLProxyCheckPeerExpire will skip checking the expiration date on the SSL cert used on a back-end server. To avoid checking a certificate's common name or alternate names against the server name used to access a back end, set SSLProxyCheckPeerName to "off". In older versions of Apache, you might be able to use SSLProxyCheckPeerCN (set to "off") instead of SSLProxyCheckPeerName.

Along with rewriting URLs, it may be necessary to rewrite HTTP request or response header fields. In Apache, this is done with mod_headers. There are only two directives within this module: Header and RequestHeader. These directives are used to modify response and request header fields, respectively. Many actions can be used with either of these directives, but here, let's look at the set and edit actions—for example:


Header set ReceiveTime "%t"

This example will add and replace any existing header in an HTTP response named ReceiveTime and give it the value of the UNIX timestamp when the request was received by the server (represented by "%t").

If you need to replace the value of a header that comes from a back-end server, you would use the edit action. Consider the following example:


Header edit Location "^http://back-end01.test:8080/(.*)$"
 ↪"http://public.test/$1"

This example will replace the Location attribute in an HTTP response, which will exist in a 301/302 redirect. If it finds http://back-end01.test:8080 at the beginning of the Location header, it replaces that part with "http://public.test" (the user-facing URL).

Content Integration

Once a remote application is integrated with an Apache server, from a protocol standpoint, it may be necessary to integrate content. This will generally manifest itself as URLs coded into HTML or JavaScript that are specific to a back-end server and not to a user-facing server. The basic necessity is to be able to search and replace bits of HTML or JavaScript content, so that it can render and perform correctly when accessed through an Apache proxy. The module that accomplishes this is mod_substitute and specifically the Substitute directive. Substitute allows a simple regex substitute to be performed on the payload data of an HTTP response.

Something to consider before attempting to replace text is to account for whether the back-end web server compresses data before sending it over the network. If it does, your Substitute statements might not work, as it will be searching for ASCII text within binary compressed data. To account for this, you can instruct Apache to decompress the data, manipulate the response and then re-compress it. This is done using the SetOutputFilter directive, which is part of Apache core functionality. Here's how it works:


SetOutputFilter INFLATE;SUBSTITUTE;DEFLATE

Reading the arguments from left to right, this tells Apache to INFLATE (decompress) the data from the back-end server, perform the substitute and DEFLATE (compress) the data before returning it to the end user.

The Substitute statement uses a regex substitute expression. As I mentioned previously, I found it easier to use the pipe-style substitute expression in Apache. To recap, the syntax is s|search|replace|options. Two common options that I tend to use: "i", which denotes a case-insensitive search, and "n", to allow the search and replace values to be processed as regex. Here's a common use example:


Substitute "s|(href="http)(://)back-end01.test:8080|$1s$2public.test|in"

For this example, let's assume that the user-facing site (public.test) runs HTTPS, and the back-end server (back-end01.test) runs HTTP on port 8080. This would be a solution if the back-end web server returned hyperlinks that were specific to itself as opposed to the user-facing site. In the search portion of the regex substitute, this splits out two groups of text in parentheses: (href=\"http) and (://). These are blocks of text that you want preserved in the replace section of the regex. In the replace, you are inserting an "s" after http and replacing the hostname/port with the user-facing site name. After processing, the resulting string will be href="https://public.test. This will update hyperlinks that use "href" attributes (<a> and <link>). For <img> and <script> tags, you could use this same Substitute statement and replace "href" with "src". Another consideration would be to account for double or single quotes delimiting attribute values (href=' vs. href=").

Another application of Substitute is to extend the functionality of a page without manipulating the original source code. Consider the following example:


Substitute "s|(<body.*>)|\1<div style=\"font-size:14pt;
↪font-weight:bold;background-color:#ff0000;color:
↪#ffffff;display:block;text-align:center;\">This site
 ↪will be down for 24 hours beginning at 8 pm tonight</div>|in"

If a website needs to be taken off-line for maintenance, this is an easy way to alert the user population of the outage without modifying the application itself. This example simply inserts a red bar along the top of the page (right after the <body> tag), which displays information about the outage. Depending on how your page is rendered, you might need to choose another tag to act as your starting point instead of <body>.

Streamlining Future Integrations

All of the topics presented here can be configured and maintained relatively easily if you have only a few statements. In the real world, there typically will be many sites that use a similar configuration and having to define the functionality for each site can be time-consuming and can lead to mistakes. Luckily, Apache provides a mechanism to repeat functionality throughout your configuration through the use of mod_macro. The <Macro> directive within an Apache config functions very much like a function or subroutine. Once a macro is defined, it can be referenced as many times as is necessary, leaving you with one place within your config to maintain your detailed functionality. Here's an example macro:


<Macro RedirectSecure $host $path>
        RewriteCond "%{REQUEST_URI}" "^$path"
        RewriteRule "^/(.*)$" "https://$host/$1"
</Macro>

When called, this macro will define a RewriteCond and RewriteRule that, if they access a URL starting with the value of the $path argument, will redirect the user to http://$host/$1, where $host is the hostname specified as a macro argument and $1 is the entire URL path. The following syntax would be used to call this macro:


Use RedirectSecure public.test /users

Something to consider is the location within the Apache config from which a macro is called. A RewriteRule, for example, cannot be called outside a <VirtualHost> block. As such, if the macro is called outside a <VirtualHost> block, Apache will throw an error and not start. Here's another example:


<Macro ReplaceContentURL $backendurl $publicurl>
        Substitute "s|(href=\")$backendurl|$1$publicurl|in"
        Substitute "s|(src=\")$backendurl|$1$publicurl|in"
</Macro>

This macro expands on the replacing of URLs that I covered previously. This will search for tag attributes of "href" and "src" and replace the hyperlinks of the back-end server with that of the user-facing server. Here's an example of how this might be called:


Use ReplaceContentURL http://back-end01.test:8080 https://public.test

This will search for http://back-end01.test:8080, beginning with either href=" or src=" and replace the URL with https://public.test. Macros can be used for any piece of Apache configuration. They can be used to do small tasks as shown here as well as whole site configurations. Although macros are pretty simple, they make the difference between a large amount of difficult-to-maintain configuration files and a simplified reusable configuration.

At this point, you have some basic knowledge of integrating HTTP, customizing content and reproducing configuration within Apache. Although many directives and modules weren't covered here, this will be a great starting point and can help you get started with accessing your applications through Apache.

Resources

The following are some articles I've found useful along with some example Apache configs I've written.

Apache Module Reference (2.2): http://httpd.apache.org/docs/2.2/mod

Apache Module Reference (2.4): http://httpd.apache.org/docs/2.4/mod

Git Instaweb Reverse Proxy: http://git.andydoestech.com/git/scripts/.git/tree/config/gitreverseproxy.conf

Monit Reverse Proxy: http://git.andydoestech.com/git/scripts/.git/tree/config/monit.conf

Adobe Experience Manager Apache Config: http://git.andydoestech.com/git/aem-dispatcher-config/.git/tree

Andy Carlson has worked in IT for the past 15 years doing networking and server administration along with occasional coding. He is thankful to have chosen a career that he loves, grows in and learns from. He currently resides in Cincinnati, Ohio, with his wife, three daughters and his son. His family is currently in the process of adopting two children internationally. He enjoys playing the guitar, coding, and spending time with family and friends.

Load Disqus comments