diff --git a/docs/en/02_Developer_Guides/02_Controllers/02_Routing.md b/docs/en/02_Developer_Guides/02_Controllers/02_Routing.md
index 832ef4b4f..b22c3a6b4 100644
--- a/docs/en/02_Developer_Guides/02_Controllers/02_Routing.md
+++ b/docs/en/02_Developer_Guides/02_Controllers/02_Routing.md
@@ -216,6 +216,30 @@ Director:
'feed': 'FeedController'
```
+## Root URL Handlers
+
+In some cases, the Director rule covers the entire URL you intend to match, and you simply want the controller to respond to a 'root' request. This request will automatically direct to an `index()` method if it exists on the controller, but you can also set a custom method to use in `$url_handlers` with the `'/'` key:
+
+```php
+use SilverStripe\Control\Controller;
+
+class BreadAPIController extends Controller
+{
+ private static $allowed_actions = [
+ 'getBreads',
+ 'createBread',
+ ];
+
+ private static $url_handlers = [
+ 'GET /' => 'getBreads',
+ 'POST /' => 'createBread',
+ ];
+```
+
+
+In SilverStripe Framework versions prior to 4.6, an empty key (`''`) must be used in place of the `'/'` key. When specifying an HTTP method, the empty string must be separated from the method (e.g. `'GET '`). The empty key and slash key are also equivalent in Director rules.
+
+
## Related Lessons
* [Creating filtered views](https://www.silverstripe.org/learn/lessons/v4/creating-filtered-views-1)
* [Controller actions / DataObjects as pages](https://www.silverstripe.org/learn/lessons/v4/controller-actions-dataobjects-as-pages-1)
diff --git a/src/Control/HTTPRequest.php b/src/Control/HTTPRequest.php
index 56680464b..46743c045 100644
--- a/src/Control/HTTPRequest.php
+++ b/src/Control/HTTPRequest.php
@@ -523,8 +523,8 @@ class HTTPRequest implements ArrayAccess
$pattern = $matches[2];
}
- // Special case for the root URL controller
- if (!$pattern) {
+ // Special case for the root URL controller (designated as an empty string, or a slash)
+ if (!$pattern || $pattern === '/') {
return ($this->dirParts == array()) ? array('Matched' => true) : false;
}
diff --git a/tests/php/Control/ControllerTest.php b/tests/php/Control/ControllerTest.php
index a38648bc5..5e5ea8ea5 100644
--- a/tests/php/Control/ControllerTest.php
+++ b/tests/php/Control/ControllerTest.php
@@ -14,6 +14,7 @@ use SilverStripe\Control\Tests\ControllerTest\IndexSecuredController;
use SilverStripe\Control\Tests\ControllerTest\SubController;
use SilverStripe\Control\Tests\ControllerTest\TestController;
use SilverStripe\Control\Tests\ControllerTest\UnsecuredController;
+use SilverStripe\Control\Tests\RequestHandlingTest\HTTPMethodTestController;
use SilverStripe\Dev\FunctionalTest;
use SilverStripe\Security\Member;
use SilverStripe\View\SSViewer;
@@ -34,6 +35,7 @@ class ControllerTest extends FunctionalTest
ContainerController::class,
HasAction::class,
HasAction_Unsecured::class,
+ HTTPMethodTestController::class,
IndexSecuredController::class,
SubController::class,
TestController::class,
@@ -493,4 +495,15 @@ class ControllerTest extends FunctionalTest
$response = $this->get('ContainerController/subcontroller/substring/subvieweraction');
$this->assertEquals('Hope this works', $response->getBody());
}
+
+ public function testSpecificHTTPMethods()
+ {
+ // 'GET /'
+ $response = $this->get('HTTPMethodTestController');
+ $this->assertEquals('Routed to getRoot', $response->getBody());
+
+ // 'POST ' (legacy method of specifying root route)
+ $response = $this->post('HTTPMethodTestController', ['dummy' => 'example']);
+ $this->assertEquals('Routed to postLegacyRoot', $response->getBody());
+ }
}
diff --git a/tests/php/Control/RequestHandlingTest/HTTPMethodTestController.php b/tests/php/Control/RequestHandlingTest/HTTPMethodTestController.php
new file mode 100644
index 000000000..beb211e21
--- /dev/null
+++ b/tests/php/Control/RequestHandlingTest/HTTPMethodTestController.php
@@ -0,0 +1,32 @@
+ 'getRoot',
+ 'POST ' => 'postLegacyRoot',
+ ];
+
+ private static $allowed_actions = [
+ 'getRoot',
+ 'postLegacyRoot',
+ ];
+
+ public function getRoot(HTTPRequest $request)
+ {
+ return "Routed to getRoot";
+ }
+
+ public function postLegacyRoot(HTTPRequest $request)
+ {
+ return "Routed to postLegacyRoot";
+ }
+}
diff --git a/tests/php/Control/RequestHandlingTest/TestFormHandler.php b/tests/php/Control/RequestHandlingTest/TestFormHandler.php
index eae42fc8f..9ee8c41e7 100644
--- a/tests/php/Control/RequestHandlingTest/TestFormHandler.php
+++ b/tests/php/Control/RequestHandlingTest/TestFormHandler.php
@@ -13,8 +13,8 @@ class TestFormHandler extends FormRequestHandler
{
private static $url_handlers = array(
'fields/$FieldName' => 'handleField',
- "POST " => "handleSubmission",
- "GET " => "handleGet",
+ "POST /" => "handleSubmission",
+ "GET /" => "handleGet",
);
// These are a different case from those in url_handlers to confirm that it's all case-insensitive