<p>I solved my issue, but unfortunately at this point in time I don't fully understand <em>why</em> this resolves the problem.</p>

<p>My controller, its <code>behaviors</code> method, and my frontend request were all correct. <strong>The problem was with two Axios defaults I had set</strong> in my external application that was sending the request to my Craft install:</p>

<pre class="lang-js prettyprint-override"><code>// Axios.defaults.headers.common['Accept'] = 'application/json';
// Axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
</code></pre>

<p>Once these were commented-out or removed my external Vue application was able to successfully send its request to my Craft controller and receive an array of entries.</p>

<p>As for the <code>behaviors</code> method in my Controller I only needed one <code>corsFilter</code>:</p>

<pre class="lang-php prettyprint-override"><code>public function behaviors ()
{
    return [
        'corsFilter' =&gt; [
            'class' =&gt; Cors::class,
            'cors' =&gt; [
                'Origin' =&gt; ['http://localhost:8080'] // dev url of external app
            ]
        ]
    ];
}
</code></pre>

CC BY-SA 4.0
2019-04-30T14:05:04.863Z
2019-04-30T15:08:53.363Z
2019-04-30T15:08:53.363Z
Steven Thate
<p>Craft 3.1.18</p>

<p>I have a simple controller <code>VeteransController</code> made using the updated Business Logic module. The purpose of this controller is to create a very simple endpoint for an external Vue application to send requests to and receive an array of <code>veterans</code> entries.</p>

<p>When I test this controller using a "local" version of the same Vue component it functions perfectly. The response includes an array of entries just as expected.</p>

<p><strong>Error from console</strong></p>

<p>But when I send the request from my external Vue app I get a 404 error: </p>

<pre><code>Access to XMLHttpRequest at 'https://myproject.test/actions/business-logic/veterans/index' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
</code></pre>

<p>This seems to suggest that the action isn't sending the correct status code with the response or doesn't know how to correctly respond to the preflight OPTIONS. But the logs from <code>storage/logs</code> <em>seem</em> to tell a different story.</p>

<p><strong>Error from storage/logs/web-404s.log:</strong></p>

<pre><code>[yii\web\Application::handleRequest] Route requested: 'templates/render'
[yii\base\Controller::runAction] Route to run: templates/render
[yii\base\InlineAction::runWithParams] Running action: craft\controllers\TemplatesController::actionRender()
[yii\web\HttpException:404] yii\web\NotFoundHttpException: Template not found: actions/business-logic/veterans/index in /Users/steven/Code/destination-small-town/vendor/craftcms/cms/src/controllers/TemplatesController.php:70
</code></pre>

<p>This is my first time getting this deep into extending Craft like this, so I could be missing small steps or reading things incorrectly. But those errors make it sound like Craft is trying to read the URL from my Vue's axios post call (<code>https://mycraftproject.test/actions/business-logic/veterans/index</code>) and render a template that doesn't exist rather than navigate to the correct controller action. </p>

<p>But when I place a breakpoint in PhpStorm on my <code>VeteransController</code> class definition and go through the steps I can tell that the request is indeed making its way to the controller. But when it goes along down the steps to the <code>runAction()</code> method in <code>yii\base\Module</code> it returns a <code>null</code> value.</p>

<p><strong>So I can establish that my request is reaching the controller. But it is not reaching my index action. I'm at a loss as to how I should proceed or troubleshoot further.</strong></p>

<p>Here is my controller in case I am missing something simple here:</p>

<pre class="lang-php prettyprint-override"><code>class VeteransController extends Controller
{

    public $allowAnonymous = true;
    public $enableCsrfValidation = false;

    /**
     * Add CORS Filter from Yii2
     */
    public function behaviors()
    {
        return [
            'corsFilter' =&gt; [
                'class' =&gt; \yii\filters\Cors::className(),
                'cors' =&gt; [
                    'Origin' =&gt; ['*'],
                    'Access-Control-Request-Method' =&gt; ['POST', 'OPTIONS'],
                    'Access-Control-Request-Headers' =&gt; ['*'],
                ]
            ]
        ];
    }

    /**
     * @throws BadRequestHttpException
     */
    public function actionIndex ()
    {
        $this-&gt;requirePostRequest();
        $this-&gt;requireAcceptsJson();
        $request = Craft::$app-&gt;getRequest();

        $countyId = $request-&gt;getBodyParam('countyId');
        $veterans = Entry::find()
            -&gt;section('veterans')
            -&gt;relatedTo($countyId)
            -&gt;limit(10)
            -&gt;all();


        return $this-&gt;asJson([
            'success' =&gt; true,
            'veterans' =&gt; $veterans
        ]);
    }

}
</code></pre>

<p><strong>Request Headers:</strong></p>

<p>The request headers are set by my Axios instance in my external Vue app.</p>

<pre class="lang-js prettyprint-override"><code>Axios.defaults.headers.common['Accept'] = 'application/json';
Axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
Axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
</code></pre>

<p>While not set as an Axios default the request does include <code>Access-Control-Request-Method</code> with a value of <code>POST</code>.</p>

<p>In Chrome's dev tools, under the Network -> Headers tabs and below the first section which is called "General" it shows the following:</p>

<pre><code>Request URL: https://destinationsmalltown.test/actions/business-logic/veterans/index
Request Method: OPTIONS
Status Code: 404 Not Found
Remote Address: [::1]:443
Referrer Policy: no-referrer-when-downgrade
</code></pre>

<p>Thank you!</p>

CC BY-SA 4.0
2019-04-17T19:54:37.400Z
Can you show your request headers? Do they contain `Access-Control-Request-Method`
Robin Schambach
CC BY-SA 4.0
2019-04-17T20:04:55.447Z
I updated the question with request header info and also "general" header info found in dev tools. The request is post, but it's returning a status of 404 during the OPTIONS method, which is some kind of preflight before the real post? This CORS stuff is new to me so I'm trying to wrap my head around it!
Steven Thate
<plugin-development><controller><modules><request>
How do I correctly send an external request to a module controller? runAction() is returning null
# How do I correctly send an external request to a module controller? runAction() is returning null

(Asked by: username_0 on 2019-04-17)

Craft 3.1.18
I have a simple controller VeteransController made using the updated Business Logic module. The purpose of this controller is to create a very simple endpoint for an external Vue application to send requests to and receive an array of veterans entries.
When I test this controller using a "local" version of the same Vue component it functions perfectly. The response includes an array of entries just as expected.
Error from console
But when I send the request from my external Vue app I get a 404 error: 
Access to XMLHttpRequest at 'https://myproject.test/actions/business-logic/veterans/index' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

This seems to suggest that the action isn't sending the correct status code with the response or doesn't know how to correctly respond to the preflight OPTIONS. But the logs from storage/logs seem to tell a different story.
Error from storage/logs/web-404s.log:
[yii\web\Application::handleRequest] Route requested: 'templates/render'
[yii\base\Controller::runAction] Route to run: templates/render
[yii\base\InlineAction::runWithParams] Running action: craft\controllers\TemplatesController::actionRender()
[yii\web\HttpException:404] yii\web\NotFoundHttpException: Template not found: actions/business-logic/veterans/index in /Users/steven/Code/destination-small-town/vendor/craftcms/cms/src/controllers/TemplatesController.php:70

This is my first time getting this deep into extending Craft like this, so I could be missing small steps or reading things incorrectly. But those errors make it sound like Craft is trying to read the URL from my Vue's axios post call (https://mycraftproject.test/actions/business-logic/veterans/index) and render a template that doesn't exist rather than navigate to the correct controller action. 
But when I place a breakpoint in PhpStorm on my VeteransController class definition and go through the steps I can tell that the request is indeed making its way to the controller. But when it goes along down the steps to the runAction() method in yii\base\Module it returns a null value.
So I can establish that my request is reaching the controller. But it is not reaching my index action. I'm at a loss as to how I should proceed or troubleshoot further.
Here is my controller in case I am missing something simple here:
class VeteransController extends Controller
{

    public $allowAnonymous = true;
    public $enableCsrfValidation = false;

    /**
     * Add CORS Filter from Yii2
     */
    public function behaviors()
    {
        return [
            'corsFilter' => [
                'class' => \yii\filters\Cors::className(),
                'cors' => [
                    'Origin' => ['*'],
                    'Access-Control-Request-Method' => ['POST', 'OPTIONS'],
                    'Access-Control-Request-Headers' => ['*'],
                ]
            ]
        ];
    }

    /**
     * @throws BadRequestHttpException
     */
    public function actionIndex ()
    {
        $this->requirePostRequest();
        $this->requireAcceptsJson();
        $request = Craft::$app->getRequest();

        $countyId = $request->getBodyParam('countyId');
        $veterans = Entry::find()
            ->section('veterans')
            ->relatedTo($countyId)
            ->limit(10)
            ->all();


        return $this->asJson([
            'success' => true,
            'veterans' => $veterans
        ]);
    }

}

Request Headers:
The request headers are set by my Axios instance in my external Vue app.
Axios.defaults.headers.common['Accept'] = 'application/json';
Axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
Axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

While not set as an Axios default the request does include Access-Control-Request-Method with a value of POST.
In Chrome's dev tools, under the Network -> Headers tabs and below the first section which is called "General" it shows the following:
Request URL: https://destinationsmalltown.test/actions/business-logic/veterans/index
Request Method: OPTIONS
Status Code: 404 Not Found
Remote Address: [::1]:443
Referrer Policy: no-referrer-when-downgrade

Thank you!

**Tags:** plugin-development, controller, modules, request

> **Comments:**
> **username_1**: Can you show your request headers? Do they contain `Access-Control-Request-Method`  
> **username_0**: I updated the question with request header info and also "general" header info found in dev tools. The request is post, but it's returning a status of 404 during the OPTIONS method, which is some kind of preflight before the real post? This CORS stuff is new to me so I'm trying to wrap my head around it!  

---

## 1 answers

### Answer by username_0 on 2019-04-30. Score: 1 | Accepted answer

I solved my issue, but unfortunately at this point in time I don't fully understand why this resolves the problem.
My controller, its behaviors method, and my frontend request were all correct. The problem was with two Axios defaults I had set in my external application that was sending the request to my Craft install:
// Axios.defaults.headers.common['Accept'] = 'application/json';
// Axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

Once these were commented-out or removed my external Vue application was able to successfully send its request to my Craft controller and receive an array of entries.
As for the behaviors method in my Controller I only needed one corsFilter:
public function behaviors ()
{
    return [
        'corsFilter' => [
            'class' => Cors::class,
            'cors' => [
                'Origin' => ['http://localhost:8080'] // dev url of external app
            ]
        ]
    ];
}


---

