Change DocumentRoot in Apache depending on URL path - apache

I'm redoing my API. Fortunately, its versioned. Yay! But what's the best way to handle the new code.
Basically; I have api.example.com/v1/method etc. My site runs on apache, and api.example.com points to /var/www/html/api.example.com
For the new API, I will be using a completely different framework and codebase etc, so I don't want to mix up the files.
Ideally, what I want is api.example.com/v1/ to point to /var/www/html/api-v1.example.com and api.example.com/v2/ to point to /var/www/html/api-v2.example.com.
[Edited, I originally said subdirectories which I think is confusing, but really I think I want them as completely separate sites]
Is this possible using Apache?
Additional question: if instead of using /v1/ /v2/ etc, I versioned my API by using an Accept header like:
Accept: application/vnd.api-example-com-v2+json
how would I map that to a different directory?

It's fine if you have your document root setup as
/var/www/html/api.example.com
Then have the directories /v1/ and /v2/ inside the document root. As for the Accept header. You can match against that using mod_rewrite:
RewriteEngine On
RewriteCond %{HTTP_ACCEPT} application/vnd.api-example-com-(.*)\+json
RewriteCond %1::$1 !^(.*)::\1
RewriteRule ^(.*)$ /%1/$1 [L]
If that's what you want, then you need to create some aliases in your vhost/server config:
AliasMatch ^/v([0-9]+)/(.*)$ /var/www/html/api-v$1.example.com/$2

Related

Apache 301 redirect with get parameters

I am trying to do a 301 redirect with lightspeed webserver htaccess with no luck.
I need to do a url to url redirect without any related parameters.
for example:
from: http://www.example.com/?cat=123
to: http://www.example.com/some_url
I have tried:
RewriteRule http://www.example.com/?cat=123 http://www.example.com/some_url/ [R=301,L,NC]
Any help will be appreciated.
Thanks for adding your code to your question. Once more we see how important that is:
your issue is that a RewriteRule does not operate on URLs, but on paths. So you need something like that instead:
RewriteEngine on
RewriteRule ^/?$ /some_url/ [R=301,L,NC,QSD]
From your question it is not clear if you want to ignore any GET parameters or if you only want to redirect if certain parameters are set. So here is a variant that will only get applied if some parameter is actually set in the request:
RewriteEngine on
RewriteCond %{QUERY_STRING} (?:^|&)cat=123(?:&|$)
RewriteRule ^/?$ /some_url/ [R=301,L,NC,QSD]
Another thing that does not really get clear is if you want all URLs below http://www.example.com/ (so below the path /) to be rewritten, or only that exact URL. If you want to keep any potential further path component of a request and still rewrite (for example http://www.example.com/foo => http://www.example.com/some_url/foo), then you need to add a capture in your regular expression and reuse the captured path components:
RewriteEngine on
RewriteRule ^/?(.*)$ /some_url/$1 [R=301,L,NC,QSD]
For either of this to work you need to have the interpretation of .htaccess style files enabled by means of the AllowOverride command. See the official documentation of the rewriting module for details. And you have to take care that that -htaccess style file is actually readable by the http server process and that it is located right inside the http hosts DOCUMENT_ROOT folder in the local file system.
And a general hint: you should always prefer to place such rules inside the http servers host configuration instead of using .htaccess style files. Those files are notoriously error prone, hard to debug and they really slow down the server. They are only provided as a last option for situations where you do not have control over the host configuration (read: really cheap hosting service providers) or if you have an application that relies on writing its own rewrite rules (which is an obvious security nightmare).

.htaccess rewrite root to folder, yet block direct urls to same folder

I've been researching and trying for a week to accomplish this, but I haven't been able to find my solution. I'm sure it's possible, but I'm just not an expert in the depths of voodoo magic.
The setup:
An installation of MantisBT located in
mysite.com/mantisbt/currentver/mantis-1.3.19
When I perform an upgrade, I want to archive all old versions and old
database dumps to /mantisbt/oldversions/ to keep things tidy.
I also have other utilities and pages in various subdirectories, for
instance "mysite.com/utils"
The goal:
I want users to type in mysite.com/ (root) and have the URL rewritten
(transparently) to /mantisbt/currentver/mantis-1.3.19/ so that they
don't see my directory structure and it looks like the mantisbt app is
located in the root directory.
I also want protection from anyone trying to directly access a
subdirectory beginning with "/mantis". For instance, if
someone directly types mysite.com/mantisbt/currentver/mantis-1.3.19/
into their browser, I want them redirected back to the root directory
so they access the site from root just like everyone else.
I also need to allow my other subdirectories like mysite.com/utils to
be accessible if I type in the full path to them.
The biggest problem I've encountered is that Apache loops through your .htaccess file again every time the URL changes. So I get stuck in these rewrite loops. I've tried looking at every possible tool that Apache offers, but I'm seriously outgunned here. I could provide examples of what I've tried, they're obviously not correct, but ask me and I can post them.
You'd be far better off altering the DocumentRoot in your httpd.conf, and mapping in the utils directory, via a Location directive. It will be far cleaner, and Apache won't have to do some trickery with every request.
Anyway something along the following lines should work.
# Stop direct access
RewriteCond %{HTTP_REFERER} !mysite.com [NC]
RewriteRule /?mantisbt/currentver/mantis-1.3.19 / [NC,L,R=301]
# Internally Rewrite everything apart from /utils to /mantisbt/mantis-1.3.19/
RewriteCond %{HTTP_REFERER} !mysite.com [NC]
RewriteCond %{REQUEST_URI} !/utils [NC]
RewriteRule .* /mantisbt/currentver/mantis-1.3.19%{REQUEST_URI} [L]

Apache rewrite rules for Slim (PHP) RESTful API

I'm having some trouble configuring my RESTful API (using Slim PHP and Eloquent ORM) to rewrite URL's correctly.
My project directory structure goes like this:
wwwroot/
myproject/
api/ (maintained in its own Git repo)
app/
data/
lib/
models/
routes/
app.php (where Slim is instantiated and used)
config.php (holds database settings, etc.)
public/
.htaccess (#1)
index.php (includes ../app/app.php)
vendor/
.htaccess (#2, this be empty yo)
ngapp/ (future Angular.js app)
webapp/ (future standard web app)
.htaccess (#1) contains the standard rules:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
This makes the API reachable via http://localhost/myproject/api/public/<insertAnySlimRouteHere>. However, I want to reach the API via an URL such as this: http://localhost/myproject/api/<insertAnySlimRouteHere>. How do I do this?
(Feel free to comment on my overall project structure too. I'm trying to learn how to develop a RESTful API in a language I seldom use nowadays since I'm employed as a .NET/JS developer, using two frameworks I've never dabbled with before and making it (the API) suitable for several clients, both on web and on mobile devices. Learning is fun, whenever there is time.)
Update
I followed smcjones' suggestion and added the following to my httpd-vhosts.conf:
<VirtualHost *:80>
Alias "/lampjavel/api" "C:/Users/Viktor/Dev/htdocs/lampjavel-api/public"
<Directory "C:/Users/Viktor/Dev/htdocs/lampjavel-api">
Options Indexes FollowSymLinks MultiViews
AllowOverride all
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
I also deleted my .htaccess #2 and my .htaccess #1 (in public/) looks like this:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]
However, the first part of the Slim route gets thrown away. When I access .../api in browser I get the / route, which is correct. But when I request .../api/images I still get the / route. If I delve deeper to .../api/images/images I get my desired route, /images.
In my app.php:
$app->get('/', function () use ($app) {
echo '/';
});
$app->get('/images', function () use ($app) {
echo '/images';
});
Any idea why the first part of the route gets ignored?
What you are looking to accomplish in its stated form would be unwieldy and insecure.
Unwieldy, because you are trying to call child folders from the parent. This could lead to several unforeseen issues. For example:
You need to disallow the folders which are in your project structure from being called using your mod_rewrite. Otherwise, your server will try to serve you a Slim route instead of, say, app. While you could add RewriteCond %{REQUEST_FILENAME} !-d, that would miss the point, because then your app route wouldn't work on Slim.
For this reason you will also never be able to have a Slim route named app. While you most likely have no intention of having a Slim route named app, it is better to give your application the flexibility.
On a similar note, if you ever decided you needed another folder (let's say, docs, or test for unit testing), you would either need to bury it under another folder, like app or public (where, at least to me, they don't seem like they fit), or you will need to change your mod_rewrite. You will also need to look out for naming conflicts all over again. In this sense, unless you are 100% sure you are not going to modify your structure, you're adding more pain for yourself than would be necessary.
Insecure, because your web path is currently set to show a big portion of your directory structure. While this seems to me like a development server, where it may not matter, I think it's good practice to get your paths in order on your development server first.
Currently, if you navigate to http://localhost/myproject/api/app/data, does it throw a 404? Would someone be able to guess your structure? Are there any pages that you would not want someone to find outside of your root? While PHP will not present any of these files, someone would easily be able to overload your system by calling files. Also, if a file throws an error when called alone, you may be giving up more information about your path.
Again, I realize that you are probably just testing this out, but if it is going into a live environment at some point, these are issues you will need to deal with. I have given myself a lot less grief after I started trying to make my development server emulate my live server as much as possible.
My Proposed Solution
.htaccess is really not the ideal place to conduct these sorts of operations. Instead, you should be looking at Apache's configuration files. As #guillermoandrae stated, what it sounds like you're really looking to do is have a different DocumentRoot.
If you can access Apache's configuration you can create a VirtualHost which points directly to the public directory.
Example:
<VirtualHost *:80>
Alias "/api" /wwwroot/myproject/api/public
<Directory "/api">
Options Indexes FollowSymLinks MultiViews
AllowOverride all
Order allow,deny
Allow from all
<IfModule mod_rewrite.c>
RewriteEngine on
#Rewrite Rules from .htaccess here
</IfModule>
</Directory>
</VirtualHost>
You can add more Directory tags here and create a secure application with aliases to folders you need to reach.
A (long) note about your project structure
I already mentioned that your project structure may have to change at some point. Overall I would mention that your project structure looks good. PHP is an overly forgiving language, which can be bad, but with your background means you'll be doing just fine. However, one thing I would suggest is that you split off your api folder into its own project. The reason I recommend this is because it is currently pretty tightly woven with your one application. You mentioned you want it to work with multiple applications. Well, the beauty of an API is that it works so long as you can access the URI. So long as you are creating a RESTful API (or even a JSon-based API), any language can parse your API regardless of where it is.
Currently, if you created a new project, say mysecondproject, you would have two identical URL's:
http://localhost/myproject/api/public/foo and http://localhost/mysecondproject/api/public/foo would both go to the same place. Unless you did not update the project in one place, in which case you would find inconsistencies.
Ultimately I would suspect that unless you are looking to have mirrors, you probably want your API to exist in one location on the internet. This gives you better control over, well, everything.
EDIT
Based on your questions below, your directory is being rerouted due to your mod_rewrite code. Without knowing what you're using right now for rewrites, I will suggest you add this single line and see how it works (after ...%{REQUEST_FILENAME} !-f)
RewriteCond %{REQUEST_FILENAME} !-d
EDIT #2
Your htaccess code is currently pushing everything that is not a directory or a file off of your main directory to Slim. So chances are good the error is in your Slim routes and not your Apache access. Do you have a route that looks something like this?
$app->get('/images/:route', function ($route) {
//execute code
};
If you do not, then try to figure out what route is being implemented. You can probably use some of Slim's built in logging capabilities if it is hard to locate.
It sounds like the DocumentRoot for your app is actually wwwroot/myproject/api and you want it to be wwwroot/myproject/api/public. You could just set the DocumentRoot directive, or just do this:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ /public/index.php [QSA,L]
Use this in your .htaccess #2 (in api/) :
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ public/index.php [L]

Domain handling with a controller

Im running an MVC based application on my mainsite, I have 2 other domains (for the sake of an example, www.a.com & www.b.com)
I'd like to be able to handle all a.com's requests with mainsite.com/a/ and similarly b.com with mainsite.com/b/
However I do not want the url to be redirected/changed in the browser.
I've been trying with mod_rewrite, however it seems to be clashing with my existing .htaccess rules set for mainsite.com
this is my existing .htaccess
Could anyone please suggest the best way to do this?
In the existing .htaccess, I don't see any rules redirecting the domains a.com or b.com. To do that is pretty straightforward, though.
A condition for selecting the proper host www.a.com or a.com
RewriteCond %{HTTP_HOST} ^(?:www\.)?a\.com$
prevent an endless loop
RewriteCond %{REQUEST_URI} !^/a/
and do the actual rewrite
RewriteRule ^ /a%{REQUEST_URI} [L]
As long as you don't use the R flag, the URL shouldn't change in the browser.
The rule for host b.com is analogous.
Update:
Since you already have a very large .htaccess file, the performance impact shouldn't matter too much. If you want to know for sure, there's no substitute for measuring.
If you want to reduce the performance hit nevertheless, you have two options
Move the directives in the .htaccess file to your main config or virtual config file, see When (not) to use .htaccess files for an explanation.
Do some custom rewriting with PHP in your front controller. This depends on the framework or routing mechanism you use, of course.

rewritemap for SEO and pretty URLs

I am attempting to redirect & rewrite some dynamic PHP URL's to pretty and SEO friendly URLs. I have manged to do this successfully through .htaccess with the following code:
RewriteCond %{QUERY_STRING} ^somevar=green&nodescription=([a-zA-Z0-9_-]*)$
RewriteRule (.*) /green\/%1\/? [L,R=301]
RewriteRule ^green/([^/]*)/$ /script.php?somevar=green&nodescription=$1&rewrite=on [L]
This creates a somewhat pretty URL as follows:
http://www.mysite.com/green/aA43-/
As I say, this works absolutley fine. Apart from one thing. The parameter nodescription contains a non-descriptive random set of letters, numbers and other characters.
I would like to rewrite the nodescription parameter to a more descriptive one. I understand that I can do this with a rewritemap through Apache. However, I have no experience at doing soemthing like this, and I'm not entirely sure where to start.
Normally I would simply alter script.php so that it contains more descriptive parameters, but this time I have no control over the script; I am pulling it from another site using cURL.
Can anybody give me an example of how to pull this off?
Thanks!
Matt
Well, to answer my own question, to pull this off you need access httpd.conf file on your apache server. My shared hosting company didn't allow access to this file (I doubt any would allow you access).
So I bit the bullet and purchased a VPS. I will post the steps I took here in order to set the rewritemap up in the hope that it will help a lost soul :) Ok, here goes...
My VPS has WHM installed, so in WHM I went to:
Server Configuration >> Apache Configuration >> Include Editor
Pre Virtual Host Include >> All Versions
This feature takes any text you put in and includes it in your httpd.conf file without worrying that it will be overwritten at a later stage. If you don't have WHM on your server then you can add the text directly to your httpd.conf file; make sure it is outside and before any virtual hosts.
OK, so I included the following map declaration and rewrite rule:
#Map to redirect (swaps key and value)
RewriteMap rwmap txt:/home/*/public_html/rdmap.txt
<Directory /home/*/public_html/test>
Options All -Indexes
RewriteEngine on
RewriteRule ^url/([^/]*)/$ /script.php?foo=${rwmap:$1|$1}&rewrite=on [L]
</Directory>
The actual map is a simple text file containing key/value pairs - you need to place this file in the directory declared in RewriteMap rwmap txt:/home/*/public_html/rdmap.txt.
And there you go. Apache now rewrites my URLs for me and I now have some nice and pretty SEO optimized links thanks to my rewrite map! Hoorah!
RewriteEngine on
RewriteRule ^green/([^/]*)/(.*)$ /script.php?somevar=green&nodescription=$1&rewrite=on [L]
This rewrite will allow you to pass "arbitrary text" that has nothing to do with the rewrite. For example:
http://www.mysite.com/green/aA43-/some-seo-boosting-title
Will still reroute correctly to script.php; the latter part will simply be ignored by the rewrite.