A friend pointed me towards a elegant solution that does not only handle 404 but also 500 errors. In fact, it handles every error. The key is, that every error generates an exception that propagates upwards through the stack of rack middlewares until it is handled by one of them. If you are interested in learning more, you can watch this excellent screencast. Rails has it own handlers for exceptions, but you can override them by the less documented exceptions_app
config option. Now, you can write your own middleware or you can route the error back into rails, like this:
# In your config/application.rb
config.exceptions_app = self.routes
Then you just have to match these routes in your config/routes.rb
:
get "/404" => "errors#not_found"
get "/500" => "errors#exception"
And then you just create a controller for handling this.
class ErrorsController < ActionController::Base
def not_found
if env["REQUEST_PATH"] =~ /^\/api/
render :json => {:error => "not-found"}.to_json, :status => 404
else
render :text => "404 Not found", :status => 404 # You can render your own template here
end
end
def exception
if env["REQUEST_PATH"] =~ /^\/api/
render :json => {:error => "internal-server-error"}.to_json, :status => 500
else
render :text => "500 Internal Server Error", :status => 500 # You can render your own template here
end
end
end
One last thing to add: In the development environment, rails usally does not render the 404 or 500 pages but prints a backtrace instead. If you want to see your ErrorsController
in action in development mode, then disable the backtrace stuff in your config/enviroments/development.rb
file.
config.consider_all_requests_local = false
Handling 404 Errors¶
We’re handling validation errors and invalid JSON errors. The last big thing
is to properly handle 404 errors. In showAction and updateAction,
we’re throwing a special type of exception class to trigger a 404 response.
But in reality, the 404 response isn’t JSON: it’s a big HTML page. You can
see this by browsing to a made-up programmer:
And actually, if we go to a completely made-up URL, we also see this same
HTML page:
Internally, Silex throws that same exception to cause this 404 page.
Somehow, we need to be able to return JSON for all exceptions and
while we are at it we should use the API problem detail format.
Writing the Test¶
First, what should we do?… anyone? Bueller?
You know, write a test! Copy the GET scenario, but use a
fake programmer name.
# features/api/programmer.feature # ... Scenario: Proper 404 exception on no programmer When I request "GET /api/programmers/fake" Then the response status code should be 404 And the "Content-Type" header should be "application/problem+json" And the "type" property should equal "about:blank" And the "title" property should equal "Not Found"
For the type field, I’m going to use about:blank. Why? When we don’t
have any extra information about an error beyond the status code, the spec
says we should use this. I’m also going to check that title equals Not Found.
Again, the spec says that if we use about:blank for type, then title
should contain the standard status code’s description. 404 means “Not Found”.
Using the Exception Listener on all /api URLs¶
Now let’s roll up our sleeves and get to work! We’ll go back to the exception listener
function. We want to handle any exception, as long as the URL starts with /api.
We can pass a handle to this object into my anonymous function in order to get Silex’s
Request. With it, the getPathInfo function gives us a clean version of the URL
that we can check:
// src/KnpU/CodeBattle/Application.php // ... public function configureListeners() { $app = $this; $this->error(function(\Exception $e, $statusCode) use ($app) { // only act on /api URLs if (strpos($app['request']->getPathInfo(), '/api') !== 0) { return; } // ... return $response; }); }
If you’re not using Silex, just make sure you can check the current URL to
see if it’s for your API. Alternatively, you may have some other logic to
know if the current request is for your API.
Always Create an ApiProblem¶
Next, we need an ApiProblem object so we can create our application/problem+json
response. If the exception is an instance of ApiProblemException, then
that’s easy! If not, we need to do our best to create one:
// src/KnpU/CodeBattle/Application.php // ... $this->error(function(\Exception $e, $statusCode) use ($app) { // only act on /api URLs if (strpos($app['request']->getPathInfo(), '/api') !== 0) { return; } if ($e instanceof ApiProblemException) { $apiProblem = $e->getApiProblem(); } else { $apiProblem = new ApiProblem($statusCode); } // ... });
In this second case, the only information we have is the status code. This
is where we should use about:blank as the type. But instead of doing
that here, let’s add a bit of logic into ApiProblem:
// src/KnpU/CodeBattle/Api/ApiProblem.php // ... public function __construct($statusCode, $type = null) { $this->statusCode = $statusCode; $this->type = $type; if (!$type) { // no type? The default is about:blank and the title should // be the standard status code message $this->type = 'about:blank'; $this->title = isset(Response::$statusTexts[$statusCode]) ? Response::$statusTexts[$statusCode] : 'Unknown HTTP status code :('; } else { if (!isset(self::$titles[$type])) { throw new \InvalidArgumentException('No title for type '.$type); } $this->title = self::$titles[$type]; } }
First, make $type optional. Then, if nothing is passed, set it to about:blank.
Next, Silex’s Response class has a nice map of status codes and their
short description. We can use it to get a consistent title.
Back in configureListeners, the rest is exactly like before: use ApiProblem
to create a JsonResponse and set the application/problem+json Content-Type
header on it. Now, if an exception is thrown from anywhere in the system
for a URL beginning with /api, the client will get back an API problem
response. It took a little bit of work, but this is huge!
// src/KnpU/CodeBattle/Application.php // ... $this->error(function(\Exception $e, $statusCode) use ($app) { // ... $response = new JsonResponse( $apiProblem->toArray(), $statusCode ); $response->headers->set('Content-Type', 'application/problem+json'); return $response; });
To make sure it’s working, head back to the terminal and run the tests:
The green lights prove that even the 404 page is being transformed into a
proper API problem response.
The type key should be a URL¶
We’re now returning an API problem response whenever something goes wrong in
our app. We can create these manually, like we did for validation errors.
Or we can let them happen naturally, like when a 404 page occurs. We also
have a very systematic way to create error responses, so that they stay consistent.
One last problem is that the type should be a URL, not just a string.
One simple solution would be to prefix the type with the URL to some
documentation page and use our code as the anchor. Let’s do this inside our
anonymous function, unless it’s set to about:blank:
// src/KnpU/CodeBattle/Application.php // ... $data = $apiProblem->toArray(); if ($data['type'] != 'about:blank') { $data['type'] = 'http://localhost:8000/api/docs/errors#'.$data['type']; } $response = new JsonResponse( $data, $statusCode );
Of course, creating that page is still up to you. But we’ll talk more about
documentation in the next episode.
Run the tests to see if we broke anything:
Ah, we did! The scenario that is checking for invalid JSON is expecting the
header to equal invalid_body_format. Tweak the scenario so the URL doesn’t
break things:
# features/api/programmer.feature # ... Scenario: Error response on invalid JSON # ... And the "type" property should contain "/api/docs/errors#invalid_body_format"
Run the tests again. Ok, all greeen!
This tutorial uses a deprecated micro-framework called Silex. The fundamentals of REST are still ? valid, but the code we use can’t be used in a real application.
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"silex/silex": "~1.0", // v1.3.2
"symfony/twig-bridge": "~2.1", // v2.7.3
"symfony/security": "~2.4", // v2.7.3
"doctrine/dbal": "^2.5.4", // v2.5.4
"monolog/monolog": "~1.7.0", // 1.7.0
"symfony/validator": "~2.4", // v2.7.3
"symfony/expression-language": "~2.4" // v2.7.3
},
"require-dev": {
"behat/mink": "~1.5", // v1.5.0
"behat/mink-goutte-driver": "~1.0.9", // v1.0.9
"behat/mink-selenium2-driver": "~1.1.1", // v1.1.1
"behat/behat": "~2.5", // v2.5.5
"behat/mink-extension": "~1.2.0", // v1.2.0
"phpunit/phpunit": "~5.7.0", // 5.7.27
"guzzle/guzzle": "~3.7" // v3.9.3
}
}
In this short article, we will learn how we can handle 404 error for API as well as webpage. While building API, we need JSON response with the useful message whereas in web page load, we need a 404 page. We can achieve this with a few lines of code.
First of all, we will try the route method. In this method, we will add the below code at the end of api.php
to handle the routes if non of the above routes are matched. What it does is basically if non of the routes are matched, it catches the route and returns a JSON response with a message instead of a 404 page.
Route::any('{path}', function() { return response()->json([ 'success' => false, 'message' => 'Route not found' ], 404); })->where('path', '.*');
However, this has some limitations. If there are packages where routes are defined then those routes will never hit because the api.php is rendered first and the packages’ routes are rendered. Thus, it won’t work under those conditions. Thus, below methods works perfectly fine for all conditions.
To handle the limitation of the above code, we will customize handler.php file. The limitation of the below code is that you need to have your API route prefix with “/api/”. Simply add the below code in your app/Exceptions/Handler.php
.
//import class on the top use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; public function render($request, Exception $exception) { if ($exception instanceof NotFoundHttpException) { if ($request->is('api/*')) { return response()->json(['error' => 'Not Found'], 404); } return response()->view('404', [], 404); } return parent::render($request, $exception); }
What it does is that, if there is an exception that is an instance of NotFoundHttpException
and if the request has “/api/” in its route, then the JSON response of Route not found will be returned.
Read More Articles
A useful feature that shipped in Laravel 5.5 is fallback routing. You can learn about fallback routing in Better 404 responses using Laravel +5.5 by Mohamed Said (the author of the feature) to get the full picture of why it’s useful and how to use fallback routes.
When you are creating an API, you probably want a 404 route that responds with JSON (or whatever format you are serving via content negotiation) instead of the default 404 JSON response.
Here’s what you get if you request an undefined route with a JSON content type:
curl \
-H"Content-Type:application/json" \
-H"Accept: application/json" \
-i http://apidemo.test/not/found
HTTP/1.1 404 Not Found
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
Cache-Control: no-cache, private
Date: Thu, 16 Aug 2018 06:00:42 GMT
{
"message": ""
}
You can see that we get an empty message that isn’t necessarily super helpful, but the framework returns valid JSON without any work on our part. Let’s cover a couple of scenarios and walk through how you can ensure your API responds with a fallback 404 response with a useful message when an API route doesn’t match.
Setup
Using the Laravel CLI, we’ll create a new project to walk through adding a 404 response to your API:
laravel new apidemo
cd apidemo/
# Valet users...
valet link
We’ll configure a MySQL database for this project:
mysql -u root -e'create database apidemo'
Update the .env
to the credentials that match your database environment, for me it would be:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=apidemo
DB_USERNAME=root
DB_PASSWORD=
We’re going to use the users
table to demonstrate a few things. Update the database/seeds/DatabaseSeeder.php
to the following:
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
factory('App\User', 10)->create();
}
}
Finally, you can run migrations and the seeder:
php artisan migrate:fresh --seed
API Routes
We’ll set up a few API routes, including the fallback route for our API, complete with a test. First, let’s create the following test:
php artisan make:test Api/FallbackRouteTest
Add a test case to look for a JSON response and verify a 404 error:
<?php
namespace Tests\Feature\Api;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class FallbackRouteTest extends TestCase
{
/** @test */
public function missing_api_routes_should_return_a_json_404()
{
$this->withoutExceptionHandling();
$response = $this->get('/api/missing/route');
$response->assertStatus(404);
$response->assertHeader('Content-Type', 'application/json');
$response->assertJson([
'message' => 'Not Found.'
]);
}
}
If you run the test suite at this point this test will fail because we haven’t defined a fallback route:
phpunit --filter=ApiFallbackRouteTest
...
There was 1 error:
1) Tests\Feature\ApiFallbackRouteTest::missing_api_routes_should_return_a_json_404
Symfony\Component\HttpKernel\Exception\NotFoundHttpException: GET http://localhost/api/missing/route
Let’s define a fallback route at the end of the routes/api.php
file:
Route::fallback(function(){
return response()->json(['message' => 'Not Found.'], 404);
})->name('api.fallback.404');
We’ve created a fallback route that responds with JSON and returns a message which we assert in our test. We also assert the content type to be application/json
as well.
Note the name that we’ve defined for the route api.fallback.404
. We’ll need to use this name shortly to respond to a few exception types in our exception handler.
A Valid Users Endpoint
To illustrate further how the fallback route is used, we’ll define a valid route for our User
model:
Route::get('/users/{user}', 'UsersController@show')
->name('api.users.show');
Next, we’ll create a controller and user resource:
php artisan make:controller UsersController
php artisan make:resource User
We rely on implicit route model binding, and our controller uses the User resource to respond with JSON:
<?php
namespace App\Http\Controllers;
use App\User;
use App\Http\Resources\User as UserResource;
use Illuminate\Http\Request;
class UsersController extends Controller
{
public function show(User $user)
{
return new UserResource($user);
}
}
Next, we’re going to create a test to verify the user endpoint and also verify that we get a 404 back when we request an invalid user:
php artisan make:test Api/ViewUserTes
This is where it gets interesting: let’s create a test to verify our route triggers a ModelNotFoundException
via route model binding:
<?php
namespace Tests\Feature\Api;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Testing\RefreshDatabase;
class ViewUserTest extends TestCase
{
/** @test */
public function requesting_an_invalid_user_triggers_model_not_found_exception()
{
$this->withoutExceptionHandling();
try {
$this->json('GET', '/api/users/123');
} catch (ModelNotFoundException $exception) {
$this->assertEquals('No query results for model [App\User].', $exception->getMessage());
return;
}
$this->fail('ModelNotFoundException should be triggered.');
}
}
This test is valid and should pass—it’s mostly making sure that somehow our controller is either using route model binding or is triggering a model exception somehow. We’ll come back to it in a second, but note that you may want a custom response message for the ModelNotFoundException
.
A somewhat redundant test, we will not disable exception handling and make sure we are getting back a 404 and the “no query results” error message back from the API:
/** @test */
public function requesting_an_invalid_user_returns_no_query_results_error()
{
$response = $this->json('GET', '/api/users/123');
$response->assertStatus(404);
$response->assertHeader('Content-Type', 'application/json');
$response->assertJson([
'message' => 'No query results for model [App\User].'
]);
}
At this point both tests should be passing—we didn’t necessarily use TDD to drive out the route model binding.
Next, let’s write a failing test for our route model binding which ensures an invalid user id doesn’t match the route, and we get the fallback route:
/** @test */
public function invalid_user_uri_triggers_fallback_route()
{
$response = $this->json('GET', '/api/users/invalid-user-id');
$response->assertStatus(404);
$response->assertHeader('Content-Type', 'application/json');
$response->assertJson([
'message' => 'Not Found.'
]);
}
If you run this test you’ll get (partially) the following error:
Failed asserting that an array has the subset Array &0 (
'message' => 'Not Found.'
).
--- Expected
+++ Actual
@@ @@
Array
(
- [message] => Not Found.
+ [message] => No query results for model [App\User].
We can get this test passing by constraining the {user}
route parameter:
Route::get('/users/{user}', 'UsersController@show')
->name('api.users.show')
->where('user', '[0-9]+');
If you run the test it will be passing, thus, successfully hitting the API fallback route:
phpunit --filter=invalid_user_uri_triggers_fallback_route
...
OK (1 test, 4 assertions)
It’s a good idea to add conditions to your route parameters so that the route will only match valid parameters and hit the fallback route otherwise. If using a fallback isn’t essential to you, the route model binding will still return a 404 error, but some databases might trigger an error while querying a table with an invalid value.
You would probably have something like this test as well, ensuring that your endpoint works for valid users:
use RefreshDatabase;
/** @test */
public function guests_can_view_a_valid_user()
{
$user = factory('App\User')->create([
'name' => 'LeBron James',
'email' => 'lebron@lakers.com',
]);
$response = $this->json('GET', "/api/users/{$user->id}");
$response->assertOk();
$response->assertJson([
'data' => [
'name' => 'LeBron James',
'email' => 'lebron@lakers.com',
]
]);
}
Customizing ModelNotFoundException Responses
You might be fine with the error message that the API responds with when a ModelNotFoundException
is triggered through our route model binding. If you’re interested in triggering the fallback route, you could update the exception handler with a check that resembles the following:
# app/Exceptions/Handler.php
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Route;
public function render($request, Exception $exception)
{
if ($exception instanceof ModelNotFoundException && $request->isJson()) {
return Route::respondWithRoute('api.fallback.404');
}
return parent::render($request, $exception);
}
Again, this is an optional step that gives you an idea of how you can use exception handling coupled with a fallback route. You might be perfectly fine with the JSON response provided by default for a ModelNotFoundException
. The main point is demonstrating how to use fallback routes from the exception handler.
If you update the exception handler to include the check for a model not found, you’ll need to update the broken test to match the fallback response to get the test passing again.
Learn More
I’d encourage you to read through Mohamed Said’s Better 404 responses using Laravel +5.5 post and his pull request for the feature 5.5 Add ability to set a fallback route by themsaid . Our release announcement post Laravel 5.5.5 Released With a New Route Fallback to Help Customize Your 404 Views has more information on this feature as well.
The following document provides reference information about the status codes
and error messages that are used in the Cloud Storage JSON API. For
the page specific to the Cloud Storage XML API, see
HTTP status and error codes for XML.
Error Response Format
Cloud Storage uses the standard HTTP error reporting format for the
JSON API. Successful requests return HTTP status codes in the 2xx range. Failed
requests return status codes in the 4xx and 5xx ranges. Requests that require a
redirect returns status codes in the 3xx range. Error responses usually include
a JSON document in the response body, which contains information about the
error.
The following examples show some common errors. Note that the header
information in the responses is omitted.
The following is an example of an error response you receive if you try to
list the buckets for a project but do not provide an authorization header.
401 Unauthorized { "error": { "errors": [ { "domain": "global", "reason": "required", "message": "Login Required", "locationType": "header", "location": "Authorization" } ], "code": 401, "message": "Login Required" } }
403 Forbidden
This is an example of an error response you receive if you try to list the
buckets of a non-existent project or one in which you don’t have permission
to list buckets.
403 Forbidden { "error": { "errors": [ { "domain": "global", "reason": "forbidden", "message": "Forbidden" } ], "code": 403, "message": "Forbidden" } }
404 Not Found
The following is an example of an error response you receive if you try to
retrieve an object that does not exist.
404 Not Found { "error": { "errors": [ { "domain": "global", "reason": "notFound", "message": "Not Found" } ], "code": 404, "message": "Not Found" } }
409 Conflict
The following is an example of an error response you receive if you try to
create a bucket using the name of a bucket you already own.
409 Conflict { "error": { "errors": [ { "domain": "global", "reason": "conflict", "message": "You already own this bucket. Please select another name." } ], "code": 409, "message": "You already own this bucket. Please select another name." } }
The following table describes the elements that can appear in the response body
of an error. Fields should be used together to help determine the problem.
Also, the example values given below are meant for illustration and are not an
exhaustive list of all possible values.
Element | Description |
---|---|
code |
An HTTP status code value, without the textual description.
Example values include: |
error |
A container for the error information. |
errors |
A container for the error details. |
errors.domain |
The scope of the error. Example values include: global and push . |
errors.location |
The specific item within the locationType that caused the error. For example, if you specify an invalid value for a parameter, the location will be the name of the parameter.
Example values include: |
errors.locationType |
The location or part of the request that caused the error. Use with location to pinpoint the error. For example, if you specify an invalid value for a parameter, the locationType will be parameter and the location will be the name of the parameter.
Example values include |
errors.message |
Description of the error.
Example values include |
errors.reason |
Example values include invalid , invalidParameter , and required . |
message |
Description of the error. Same as errors.message . |
HTTP Status and Error Codes
This section provides a non-exhaustive list of HTTP status and error codes that
the Cloud Storage JSON API uses. The 1xx
Informational and 2xx
Success codes are not discussed here. For more information, see Response Status
Codes in RFC 7231 §6, RFC 7232 §4,
RFC 7233 §4, RFC 7235 §3, and RFC 6585.
302—Found
Reason | Description |
---|---|
found | Resource temporarily located elsewhere according to the Location header. |
303—See Other
Reason | Description |
---|---|
mediaDownloadRedirect | When requesting a download using alt=media URL parameter, the direct URL path to use is prefixed by /download . If this is omitted, the service will issue this redirect with the appropriate media download path in the Location header. |
304—Not Modified
Reason | Description |
---|---|
notModified | The conditional request would have been successful, but the condition was false, so no body was sent. |
307—Temporary Redirect
Reason | Description |
---|---|
temporaryRedirect | Resource temporarily located elsewhere according to the Location header. Among other reasons, this can occur when cookie-based authentication is being used, e.g., when using the Storage Browser, and it receives a request to download content. |
308—Resume Incomplete
Description |
---|
Indicates an incomplete resumable upload and provides the range of bytes already received by Cloud Storage. Responses with this status do not contain a body. |
400—Bad Request
[Domain.]Reason | Description |
---|---|
badRequest | The request cannot be completed based on your current Cloud Storage settings. For example, you cannot lock a retention policy if the requested bucket doesn’t have a retention policy, and you cannot set ACLs if the requested bucket has uniform bucket-level access enabled. |
badRequestException | The retention period on a locked bucket cannot be reduced. |
cloudKmsBadKey | Bad Cloud KMS key. |
cloudKmsCannotChangeKeyName | Cloud KMS key name cannot be changed. |
cloudKmsDecryptionKeyNotFound | Resource’s Cloud KMS decryption key not found. |
cloudKmsDisabledKey | Cloud KMS key is disabled, destroyed, or scheduled to be destroyed. |
cloudKmsEncryptionKeyNotFound | Cloud KMS encryption key not found. |
cloudKmsKeyLocationNotAllowed | Cloud KMS key location not allowed. |
corsRequestWithXOrigin | CORS request contains an XD3 X-Origin header. |
customerEncryptionAlgorithmIsInvalid | Missing an encryption algorithm, or the provided algorithm is not «AE256.» |
customerEncryptionKeyFormatIsInvalid | Missing an encryption key, or it is not Base64 encoded, or it does not meet the required length of the encryption algorithm. |
customerEncryptionKeyIsIncorrect | The provided encryption key is incorrect. |
customerEncryptionKeySha256IsInvalid | Missing a SHA256 hash of the encryption key, or it is not Base64 encoded, or it does not match the encryption key. |
invalidAltValue | The value for the alt URL parameter was not recognized. |
invalidArgument | The value for one of fields in the request body was invalid. |
invalidParameter | The value for one of the URL parameters was invalid. In addition to normal URL parameter validation, any URL parameters that have a corresponding value in provided JSON request bodies must match if they are both specified. If using JSONP, you will get this error if you provide an alt parameter that is not json . |
notDownload | Uploads or normal API request was sent to a /download/* path. Use the same path, but without the /download prefix. |
notUpload | Downloads or normal API request was sent to a /upload/* path. Use the same path, but without the /upload prefix. |
parseError | Could not parse the body of the request according to the provided Content-Type. |
push.channelIdInvalid | Channel id must match the following regular expression: [A-Za-z0-9\\-_\\+/=]+ |
push.channelIdNotUnique | storage.objects.watchAll ‘s id property must be unique across channels. |
push.webhookUrlNoHostOrAddress | storage.objects.watchAll ‘s address property must contain a valid URL. |
push.webhookUrlNotHttps | storage.objects.watchAll ‘s address property must be an HTTPS URL. |
required | A required URL parameter or required request body JSON property is missing. |
resourceIsEncryptedWithCustomerEncryptionKey | The resource is encrypted with a customer-supplied encryption key, but the request did not provide one. |
resourceNotEncryptedWithCustomerEncryptionKey | The resource is not encrypted with a customer-supplied encryption key, but the request provided one. |
turnedDown | A request was made to an API version that has been turned down. Clients will need to update to a supported version. |
userProjectInvalid | The user project specified in the request is invalid, either because it is a malformed project id or because it refers to a non-existent project. |
userProjectMissing | The requested bucket has Requester Pays enabled, the requester is not an owner of the bucket, and no user project was present in the request. |
wrongUrlForUpload | storage.objects.insert must be invoked as an upload rather than a metadata. |
401—Unauthorized
[Domain.]Reason | Description |
---|---|
AuthenticationRequiredRequesterPays | Access to a Requester Pays bucket requires authentication. |
authError | This error indicates a problem with the authorization provided in the request to Cloud Storage. The following are some situations where that will occur:
|
lockedDomainExpired | When downloading content from a cookie-authenticated site, e.g., using the Storage Browser, the response will redirect to a temporary domain. This error will occur if access to said domain occurs after the domain expires. Issue the original request again, and receive a new redirect. |
required | Access to a non-public method that requires authorization was made, but none was provided in the Authorization header or through other means. |
403—Forbidden
[Domain.]Reason | Description |
---|---|
accountDisabled | The account associated with the project that owns the bucket or object has been disabled. Check the Google Cloud console to see if there is a problem with billing, and if not, contact account support. |
countryBlocked | The Cloud Storage JSON API is restricted by law from operating with certain countries. |
forbidden | According to access control policy, the current user does not have access to perform the requested action. This code applies even if the resource being acted on doesn’t exist. |
insufficientPermissions | According to access control policy, the current user does not have access to perform the requested action. This code applies even if the resource being acted on doesn’t exist. |
objectUnderActiveHold | Object replacement or deletion is not allowed due to an active hold on the object. |
retentionPolicyNotMet | Object replacement or deletion is not allowed until the object meets the retention period set by the retention policy on the bucket. |
sslRequired | Requests to this API require SSL. |
stopChannelCallerNotOwner | Calls to storage.channels.stop require that the caller own the channel. |
UserProjectAccessDenied | The requester is not authorized to use the project specified in the userProject portion of the request. The requester must have the serviceusage.services.use permission for the specified project. |
UserProjectAccountProblem | There is a problem with the project used in the request that prevents the operation from completing successfully. One issue could be billing. Check the billing page to see if you have a past due balance or if the credit card (or other payment mechanism) on your account is expired. For project creation, see the Projects page in the Google Cloud console. |
404—Not Found
Reason | Description |
---|---|
notFound | Either there is no API method associated with the URL path of the request, or the request refers to one or more resources that were not found. |
405—Method Not Allowed
Reason | Description |
---|---|
methodNotAllowed | The HTTP verb is not supported by the URL endpoint used in the request. This can happen, for example, when using the wrong verb with the /upload or /download URLs. |
408—Request Timeout
Reason | Description |
---|---|
uploadBrokenConnection | The request timed out. You should retry the request using truncated exponential backoff. |
409—Conflict
Reason | Description |
---|---|
conflict | A request to change a resource, usually a storage.*.update or storage.*.patch method, failed to commit the change due to a conflicting concurrent change to the same resource. The request can be retried, though care should be taken to consider the new state of the resource to avoid blind replacement of another agent’s changes. |
410—Gone
Description |
---|
You have attempted to use a resumable upload session or rewrite token that is no longer available. If the reported status code was not successful and you still wish to complete the upload or rewrite, you must start a new session. |
411—Length Required
Description |
---|
You must provide the Content-Length HTTP header. This error has no response body. |
412—Precondition Failed
Reason | Description |
---|---|
conditionNotMet | At least one of the pre-conditions you specified did not hold. |
orgPolicyConstraintFailed | Request violates an OrgPolicy constraint. |
413—Payload Too Large
Reason | Description |
---|---|
uploadTooLarge | This error arises if you:
|
416—Requested Range Not Satisfiable
Reason | Description |
---|---|
requestedRangeNotSatisfiable | The requested Range cannot be satisfied. |
429—Too Many Requests
[Domain.]Reason | Description |
---|---|
usageLimits.rateLimitExceeded | A Cloud Storage JSON API usage limit was exceeded. If your application tries to use more than its limit, additional requests will fail. Throttle your client’s requests, and/or retry requests using truncated exponential backoff. |
499—Client Closed Request
Description |
---|
The resumable upload was cancelled at the client’s request prior to completion. This error has no response body. |
500—Internal Server Error
Reason | Description |
---|---|
backendError | We encountered an internal error. You should retry the request using truncated exponential backoff. |
internalError | We encountered an internal error. You should retry the request using truncated exponential backoff. |
502—Bad Gateway
This error is generated when there was difficulty reaching an internal service.
It is not formatted with a JSON document. You should retry the request
using truncated exponential backoff.
503—Service Unavailable
Reason | Description |
---|---|
backendError | We encountered an internal error. You should retry the request using truncated exponential backoff. |
504—Gateway Timeout
This error is generated when there was difficulty reaching an internal service.
It is not formatted with a JSON document. You should retry the request
using truncated exponential backoff.