4

I am building a small framework for my API's since they are quite specific, but I have a problem with the Content-Type when I received data for an ErrorDocument. Currently, I have the following .htaccess:

<IfModule mod_headers.c>
Header set Content-Type "text/plain"
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE"
</IfModule>

<IfModule mod_rewrite.c>
RewriteEngine on

RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]
RewriteRule ^([a-z]+)(/[A-Za-z0-9-\._\/]*)?$ $1.php [QSA,L]

ErrorDocument 404 "API_NOT_FOUND"
</IfModule>

What I want to achieve is the error 404 with a different Content-Type. Either text/plain or application/json would be fine, but none of those works. So probably I can't set the Content-Type header in the .htaccess like I want to. I also tried the ErrorDocument as a file, but since the path to the directory is dynamic, I can't use an error document without the path hardcoded like:

ErrorDocument 404 /api/index.php?error=404

The .htaccess is inside the api directory, but the directory can be renamed. Is there any way I can achieve one of the following things?

  • Set a Content-Type inside the .htaccess so the ErrorDocument doesn't have the text/html with a charset.
  • Set the error document to index.php in the directory the .htaccess is.

If the first one works, would I still be able to override it inside the .php scripts? Some of my calls are JSON, other are XML files.

knb
  • 9,138
  • 4
  • 58
  • 85
Sietse
  • 623
  • 1
  • 8
  • 23
  • I want to throw an error when the file in the rule before that isn't found. But the Content-Type is text/html while the data isn't HTML. So if I asked for /test/something I check for the file test.php, but if test.php doesn't exists, I want to provide an error like API_NOT_FOUND in text/plain or {"error":"API_NOT_FOUND"} in application/json. – Sietse Dec 31 '15 at 09:32
  • "I can't use an error document without the path hardcoded" - In Apache 2.4.13+ you can use Apache Expressions in the `ErrorDocument` directive to be able to create dynamic paths. See this question on Pro Webmasters for an example: [ErrorDocument in dynamic SubFolder](http://webmasters.stackexchange.com/questions/92489/errordocument-in-dynamic-subfolder) – MrWhite Apr 20 '16 at 23:36
  • Thanks for the answer. It is nice to see it is possible to use a dynamic path, but I've found another way. I'll post my answer below. – Sietse Apr 21 '16 at 13:19

2 Answers2

3

You can use ForceType directive for this.

First create a file called error.json inside your DocumentRoot/folder/ with this data:

{"error":"API_NOT_FOUND"}

Then in your DocumentRoot/folder/.htaccess have it like this:

ErrorDocument 404 /folder/error.json
<Files "/folder/error.json">
   ForceType application/json
</Files>
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • Thanks for the answer and sorry for the late response (let's say the holidays caused it). When trying to provide a file, I get the following response: "Additionally, a 404 Not Found error was encountered while trying to use an ErrorDocument to handle the request.". This is because I can't put the .json file in the root nor can I modify the .htaccess in that directory. I am only allowed to change it inside a directory which the name is dynamically. – Sietse Jan 04 '16 at 16:17
  • You can create this file as `/yourfolder/error.json` and then use `ErrorDocument 404 /yourfolder/error.json` – anubhava Jan 04 '16 at 16:19
  • I know this is possible, yet I need to put the folder hardcoded inside the .htaccess while the directory can change without my knowledge. The /directory/index.php?error=404 did the trick already, but wasn't the solution for the same reason. – Sietse Jan 04 '16 at 16:29
  • 1
    You can put `error.json` in any known folder of your choice, it doesn't have to be in every dynamic folder. – anubhava Jan 04 '16 at 16:35
0

Thanks for the answers and sorry to provide the final answer this late. I have found a solution which I think works like it should.

<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
</IfModule>

<IfModule mod_rewrite.c>
RewriteEngine on

RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]

RewriteRule ^([A-Za-z0-9_-]+)(/[A-Za-z0-9-\._\/]*)?$ $1.php [QSA,L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^([a-z]+) index.php?error=404 [L]
</IfModule>

The error is redirected to the index.php which outputs the right stuff after doing the logging itself, so it is a win-win situation I believe. For the simple explanation, the following lines will be executed in the index.php:

http_response_code(404);
die(json_encode(['error' => ['code' => 'API_SCRIPT_NOT_FOUND', 'number' => 404]]);

Edit: I'll explain multiple things I do. The index.php normally generates a documentation, but when the index.php isn't called clean, I'll output the notfound error. It looks like this:

<?php
  class Documentation {}
  $API = new Documentation();

  require_once('common/initialize.php');

  Output::notfound('API_SCRIPT_NOT_FOUND');

The output class is a small class which handles the output with the correct Content-Type. It automatically set 'application/json' when no other Content-Type is set. A small example (there are more functions, but this is the one it runs):

class Output {
    protected static $instance = null;
    public static function instance() {
        return self::$instance ?: self::$instance = new static;
    }

    private $finished = false;

    private function finish($output, $status = null) {
        if($this->finished) return; $this->finished = true;

        http_response_code($status ?: 200); $content = null;

        $headers = headers_list();
        foreach($headers as $header) {
            if(substr($header, 0, 13) == 'Content-Type:') {
                $content = substr($header, 14); break;
            }
        }

        if(!$content && !headers_sent()) {
            header(sprintf('Content-Type: %s', $content = 'application/json'));
            die(json_encode((http_response_code() >= 400) ? ['error' => $output] : $output));
        }

        die(!empty($output['code']) ? $output['code'] : $output);
    }

    public static function notfound($output) { self::instance()->finish(['code' => $output, 'number' => 404], 404); }
}
amphetamachine
  • 27,620
  • 12
  • 60
  • 72
Sietse
  • 623
  • 1
  • 8
  • 23
  • Presumably you must also be setting the `Content-Type` header in `index.php` (or somewhere), otherwise it looks like you'll still be getting a `text/html` Content-Type? – MrWhite Apr 22 '16 at 09:21
  • You are right! I'll check if I can post more of the files, because the output is handled by a function. But setting the Content-Type is one of the things. – Sietse Apr 25 '16 at 06:41