Forum OpenACS Q&A: Best way to do HTTP 3xx Redirections?

Request notifications

As my web site has gone through various incarnations, I've broken many links. I'd like the flexibility of, instead of maintaining old functionality, instead maintaining a list of old links and their new equivalents. That is, I want something like:

Old Link     New Link    Redirect Type
/foo?12      /bar?15     302
  • Symlinks in the file system don't work very well because they screw up context - if the old link went to /foo/bar.html and the new one is /baz/bar.html, relative links in the page may be broken.
  • It looks like this has to happen in the request processor. Instead of hacking that, I'm hoping to modify Static Pages. Static Pages apparently had something similar before which was removed for simplicity. So I'm not sure if that's the best place to put it back.
  • Would other people find this useful? I'm envisioning a fairly simple table and admin interface.

Posted by Andrew Piskorski on
Joel, what makes you think that the Static Pages package used to have old to new redirect capabilities? I've never seen anything like that, not in OpenACS 4.x or ACS 4.2 anyway. I doesn't seem like it'd make a lot of sense to have that sort of functionality in Static Pages, either.

Using a registered proc is the traditional AOLserver way to do this. (Philip talked briefly about it in Chapter 10 of his book.) The way to go is probably either adding some kind of hook to the request processsor, or running something either immediately before or after the request processor. (E.g., when you would otherwise serve a 404 Not Found call your redirection stuff instead.) You might want to take a look at the old Redirecte code (it's less than 300 lines) and see about tying it into the request processor somehow.

Posted by Joel Aufrecht on

After RTFMing (, I found a simple solution that does what I need (and most of what I want.)

So. Suppose you have a file /foo/bar.html and you move it to /baz/bar.html and you want old links to work. Create a new file /foo/bar.vuh with one line:

ad_returnredirect /baz/bar

This will return an HTTP 302, the browser will open the new file, and all of the links will work fine.

Posted by Tilmann Singer on
There was a bug with .vuh files called other than index.vuh (e.g. bar.vuh), which should be fixed in more recent versions, so beware if you run into that bug #23.
Posted by Michael Bluett on
Thanks for an answer to how best to do redirections - I ought to try to improve my own RTFMing, I've been waiting around for an answer to this question to appear for a while.
Posted by Joel Aufrecht on

One limitation of the hack I posted earlier is that it only works for failed requests that have no extensions. So, here's a hack to the request processor. Whenever a file can't be found, it tries the same url with a .vuh extension. I believe this is orthogonal to bug #23. I'm not sure if I should test and then submit this as a patch - I don't see a down side, but it solves a very narrow problem, is only a partial solution by itself (you still have to go make .vuh files), and isn't the tidiest solution (which would be a core table of redirects and associated UI).

rp_serve_abstract_file in packages/acs-tcl/tcl/request-processor-procs.tcl

if { [file isfile $path] } {
    # It's actually a file.
    ad_conn -set file $path
  } else {
    # The path provided doesn't correspond directly to a file - we
    # need to glob.   (It could correspond directly to a directory.)
        # 2003-01-21 Joel Aufrecht hack:
	# In order to support certain redirects:
	# if it's a request for a file and we don't have a file with
	# that extension but we do have a .vuh, serve the .vuh
	set url [ad_conn url]
	set urlvuh [string range $url 0 [string last . $url]]
	append urlvuh vuh
	set pathvuh [string range $path 0 [string last . $path]]
	append pathvuh vuh
	if { [file isfile $pathvuh] } {
	    ad_raise redirect $urlvuh

	# end of Joel's hack

    if { ![file isdirectory [file dirname $path]] } {
      ad_raise notfound

Posted by Michael Bluett on
Further to this, you may wish to use rp_internal_redirect (procedure documentation) to hide redirects. I plan to use this to invisibly redirect the user to the file "start.htm" when entering a specific directory.
Posted by Michael Bluett on
My version of Joel's code redirects the user to the missing URL without an extension if a .vuh file exists (e.g. /emails from /emails.htm). This may activate the .vuh file (e.g. "/emails.vuh") or another file if a more appropriate one exists (e.g. an emails.tcl/emails.adp pair).
It should also deal with directory names containing periods.
    set url [ad_conn url]
    # Find the end of the URL, that which is after the last forward slash
    set url_tail [string range $url [expr [string last / $url] + 1] end]
    # ns_log "Notice" "url_tail:$url_tail"
    if {[string first . $url_tail] > 0} {
	# Lets try chopping off the extension and see if there is a suitable
	# file
	set path_and_extension [string range $path 0 [expr [string last . $path] - 1] ]
	append path_and_extension $extension_pattern
	# ns_log "Notice" "path_and_extension:$path_and_extension"
	if { [file isfile $path_and_extension] } {
	    set no_extension [string range $url 0 [expr [string last . $url] - 1] ]
	    # ns_log "Notice" "no_extension:$no_extension"
	    ad_raise redirect $no_extension
-The $extension_pattern is provided when the procedure is called, initially it is .* (which doesn't find the .vuh file) and then .vuh. One could hardcode other items in here to be searched for that would also be activated, such as .html.
Posted by Michael Bluett on
I have more information to add to this :- Google likes 301 redirects (permanent redirects), so I have looked up how to do 301 redirects (ad_returnredirect simply returns a 302 redirect). The aolserver manual suggests this:
Using ns_respond, it's easy to do an HTTP redirect: 
set headers [ns_set new myheaders]
ns_set put $headers location
ns_respond -status 302 -type text/plain \
    -string "redirection" -headers $headers
By changing the 302 to 301 this should work fine. This is primarily to replace existing Apache "RedirectPermanent/Redirect permanent" directives.
Posted by Andrew Piskorski on
Hm, this 301 redirect sounds like a good feature to have. ns_returnredirect (which is what ad_returnredirect calls) always does a 302 though. Sounds like a good idea to either add a switch to let it optionally do a 301 instead, or add a new command, ns_returnmoved or ns_returnredirectpermanent or something like that. Should be pretty easy to change the AOLserver Ns_ConnReturnRedirect C function to make it happen.

So, I just filed a feature request for that on SourceForge

Of course, without an extension to the AOLserver API like that, you should be able to always just do it with a Tcl proc like you said.

Posted by Michael Bluett on
I am just about to put my first OpenACS sites live (two sites on the same IP using AOLServer 3.3 and Jerry Asher's vhr patch).

I noticed that if you create a file called "index.vuh" in the root, then this file then becomes the default "file not found" file due to some action of the request processor. This may not be a desired behaviour of the processor.

Anyway, a bit of 301 redirecting code:

set headers [ns_set new myheaders]
ns_set put $headers location ""
ns_respond -status 301 -type text/plain -string "redirect" -headers $headers