updates (check description)

- Add more caching drivers
- Add legacy cache method fallback
- Add more artisan commands via dep
- Update deps
This commit is contained in:
Irfan 2019-06-19 01:34:47 +05:00
parent 3da2afc399
commit 48dce4ad76
133 changed files with 903 additions and 247 deletions

View File

@ -11,9 +11,10 @@ DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
CACHE_DRIVER=redis
CACHE_DRIVER=file
QUEUE_DRIVER=redis
CACHE_METHOD=legacy
CACHE_DEFAULT_EXPIRE=86400
CACHE_META_EXPIRE=300
CACHE_USER_EXPIRE=300

0
MIGRATION.MD Normal file → Executable file
View File

0
README.MD Normal file → Executable file
View File

0
apiary.apib Normal file → Executable file
View File

7
app/Exceptions/GithubReport.php Normal file → Executable file
View File

@ -80,9 +80,14 @@ class GithubReport
$report->requestUri = $request->getRequestUri();
$report->requestMethod = $request->getMethod();
$report->jikanVersion = Versions::getVersion('jikan-me/jikan');
$report->redisRunning = trim(app('redis')->ping()) === 'PONG' ? "Connected" : "Disconnected";
$report->phpVersion = PHP_VERSION;
try {
$report->redisRunning = trim(app('redis')->ping()) === 'PONG' ? "Connected" : "Disconnected";
} catch (ConnectionException $e) {
$report->redisRunning = false;
}
$report->instanceType = 'UNKNOWN';
if (env('APP_ENV') !== 'testing') {
$report->instanceType = $_SERVER['SERVER_NAME'] === 'api.jikan.moe' ? 'OFFICIAL' : 'HOSTED';

View File

@ -16,6 +16,7 @@ use Laravel\Lumen\Exceptions\Handler as ExceptionHandler;
use Predis\Connection\ConnectionException;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Support\Facades\Cache;
/**
* Class Handler
@ -33,6 +34,7 @@ class Handler extends ExceptionHandler
HttpException::class,
ModelNotFoundException::class,
ValidationException::class,
BadResponseException::class
];
/**
@ -69,7 +71,7 @@ class Handler extends ExceptionHandler
->json([
'status' => 500,
'type' => 'ConnectionException',
'message' => 'Failed to communicate with Redis',
'message' => 'Failed to communicate with Redis.',
'error' => env('APP_DEBUG') ? $e->getMessage() : null,
'report_url' => env('GITHUB_REPORTING', true) ? (string) $githubReport : null
], 500);
@ -160,7 +162,7 @@ class Handler extends ExceptionHandler
{
$fingerprint = "request:404:".sha1(env('APP_URL') . $request->getRequestUri());
if (app('redis')->exists($fingerprint)) {
if (Cache::has($fingerprint)) {
return;
}
@ -177,7 +179,6 @@ class Handler extends ExceptionHandler
}
app('redis')->set($fingerprint, $e->getMessage());
app('redis')->expire($fingerprint, $cacheTtl);
Cache::put($fingerprint, $e->getMessage(), $cacheTtl);
}
}

0
app/Http/Controllers/V2/MetaController.php Normal file → Executable file
View File

0
app/Http/Controllers/V2/ScheduleController.php Normal file → Executable file
View File

0
app/Http/Controllers/V2/SeasonController.php Normal file → Executable file
View File

0
app/Http/Controllers/V2/TopController.php Normal file → Executable file
View File

0
app/Http/Controllers/V3/ClubController.php Normal file → Executable file
View File

0
app/Http/Controllers/V3/GenreController.php Normal file → Executable file
View File

0
app/Http/Controllers/V3/MagazineController.php Normal file → Executable file
View File

0
app/Http/Controllers/V3/MetaController.php Normal file → Executable file
View File

0
app/Http/Controllers/V3/ProducerController.php Normal file → Executable file
View File

0
app/Http/Controllers/V3/ScheduleController.php Normal file → Executable file
View File

0
app/Http/Controllers/V3/SeasonController.php Normal file → Executable file
View File

0
app/Http/Controllers/V3/TopController.php Normal file → Executable file
View File

0
app/Http/Controllers/V3/UserController.php Normal file → Executable file
View File

0
app/Http/Controllers/V4/ClubController.php Normal file → Executable file
View File

0
app/Http/Controllers/V4/GenreController.php Normal file → Executable file
View File

0
app/Http/Controllers/V4/MagazineController.php Normal file → Executable file
View File

0
app/Http/Controllers/V4/MetaController.php Normal file → Executable file
View File

0
app/Http/Controllers/V4/ProducerController.php Normal file → Executable file
View File

0
app/Http/Controllers/V4/ScheduleController.php Normal file → Executable file
View File

0
app/Http/Controllers/V4/SeasonController.php Normal file → Executable file
View File

0
app/Http/Controllers/V4/TopController.php Normal file → Executable file
View File

0
app/Http/Controllers/V4/UserController.php Normal file → Executable file
View File

6
app/Http/HttpHelper.php Normal file → Executable file
View File

@ -85,4 +85,10 @@ class HttpHelper
return explode('@', $route)[0];
}
public static function getRequestUriHash(Request $request) : string
{
return sha1(env('APP_URL') . $request->getRequestUri());
}
}

0
app/Http/Middleware/Blacklist.php Normal file → Executable file
View File

94
app/Http/Middleware/JikanResponseHandler.php Normal file → Executable file
View File

@ -20,13 +20,14 @@ use App\Http\HttpHelper;
use App\Jobs\UpdateCacheJob;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class JikanResponseHandler
{
private $requestUri;
private $requestUriHash;
private $requestType;
private $requestCacheExpiry;
private $requestCacheExpiry = 0;
private $requestCached = false;
private $requestCacheTtl;
@ -64,35 +65,32 @@ class JikanResponseHandler
return $next($request);
}
$this->requestUri = $request->getRequestUri();
$this->requestUriHash = sha1(env('APP_URL') . $this->requestUri);
$this->requestUriHash = HttpHelper::getRequestUriHash($request);
$this->requestType = HttpHelper::requestType($request);
$this->requestCacheTtl = HttpHelper::requestCacheExpiry($this->requestType);
$this->requestCacheExpiry = time() + $this->requestCacheTtl;
$this->fingerprint = "request:{$this->requestType}:{$this->requestUriHash}";
$this->cacheExpiryFingerprint = "ttl:{$this->fingerprint}";
$this->requestCached = (bool) app('redis')->exists($this->fingerprint);
$this->requestCached = Cache::has($this->fingerprint);
$this->route = explode('\\', $request->route()[1]['uses']);
$this->route = end($this->route);
// Check if request is in the 404 cache pool
if (app('redis')->exists("request:404:" . $this->requestUriHash)) {
// Check if request is in the 404 cache pool @todo: separate as middleware
if (Cache::has("request:404:{$this->requestUriHash}")) {
return response()
->json([
'status' => 404,
'type' => 'BadResponseException',
'message' => 'Resource does not exist',
'error' => app('redis')->get("request:404:" . $this->requestUriHash)
'error' => Cache::get("request:404:{$this->requestUriHash}")
], 404);
}
// Queueable?
if (\in_array($this->route, self::NON_QUEUEABLE)) {
if (\in_array($this->route, self::NON_QUEUEABLE) || env('CACHE_METHOD', 'legacy') === 'legacy') {
$this->queueable = false;
}
@ -104,26 +102,33 @@ class JikanResponseHandler
return $response;
}
app('redis')->set($this->fingerprint, $response->original);
app('redis')->set($this->cacheExpiryFingerprint, $this->requestCacheExpiry);
if (!$this->queueable) {
app('redis')->del($this->cacheExpiryFingerprint);
app('redis')->expire($this->fingerprint, $this->requestCacheTtl);
}
Cache::forever($this->fingerprint, $response->original);
Cache::forever($this->cacheExpiryFingerprint, time() + $this->requestCacheTtl);
}
// If cache is expired, queue for an update
$this->requestCacheExpiry = (int) app('redis')->get($this->cacheExpiryFingerprint);
// If cache is expired, handle it depending on whether it's queueable
$this->requestCacheExpiry = (int) Cache::get($this->cacheExpiryFingerprint);
if ($this->requestCacheExpiry <= time() && $this->queueable) {
if ($this->requestCached && $this->requestCacheExpiry <= time() && !$this->queueable) {
$response = $next($request);
if (HttpHelper::hasError($response)) {
return $response;
}
Cache::forever($this->fingerprint, $response->original);
Cache::forever($this->cacheExpiryFingerprint, time() + $this->requestCacheTtl);
$this->requestCacheExpiry = (int) Cache::get($this->cacheExpiryFingerprint);
}
if ($this->queueable && $this->requestCacheExpiry <= time()) {
$queueFingerprint = "queue_update:{$this->fingerprint}";
$queueHighPriority = \in_array($this->route, self::HIGH_PRIORITY_QUEUE);
// Don't duplicate the queue for same request
if (!app('redis')->exists($queueFingerprint)) {
app('redis')->set($queueFingerprint, 1);
dispatch(
(new UpdateCacheJob($request))
->delay(env('QUEUE_DELAY_PER_JOB', 5))
@ -133,21 +138,22 @@ class JikanResponseHandler
}
// ETag
if (
$request->hasHeader('If-None-Match')
&& app('redis')->exists($this->fingerprint)
&& md5(app('redis')->get($this->fingerprint)) === $request->header('If-None-Match')
) {
return response('', 304);
}
// // ETag @todo: separate as middleware
// if (
// $request->hasHeader('If-None-Match')
// && $this->cache->exists($this->fingerprint)
// && md5($this->cache->get($this->fingerprint)) === $request->header('If-None-Match')
// ) {
// return response('', 304);
// }
// Return cache
// Return response
$meta = $this->generateMeta($request);
$cache = app('redis')->get($this->fingerprint);
$cacheMutable = json_decode(app('redis')->get($this->fingerprint), true);
$cache = Cache::get($this->fingerprint);
$cacheMutable = json_decode($cache, true);
$cacheMutable = $this->cacheMutation($cacheMutable);
return response()
->json(
@ -159,10 +165,9 @@ class JikanResponseHandler
->withHeaders([
'X-Request-Hash' => $this->fingerprint,
'X-Request-Cached' => $this->requestCached,
'X-Request-Cache-Ttl' => app('redis')->get($this->cacheExpiryFingerprint) - time()
'X-Request-Cache-Ttl' => (int) $this->requestCacheExpiry - time()
])
->setExpires((new \DateTime())->setTimestamp($this->requestCacheExpiry))
;
->setExpires((new \DateTime())->setTimestamp($this->requestCacheExpiry));
}
private function generateMeta(Request $request) : array
@ -172,7 +177,7 @@ class JikanResponseHandler
$meta = [
'request_hash' => $this->fingerprint,
'request_cached' => $this->requestCached,
'request_cache_expiry' => !$this->queueable ? app('redis')->ttl($this->fingerprint) : app('redis')->get($this->cacheExpiryFingerprint) - time()
'request_cache_expiry' => (int) $this->requestCacheExpiry - time()
];
switch ($version) {
@ -191,7 +196,20 @@ class JikanResponseHandler
break;
}
return $meta;
}
private function cacheMutation(array $data) : array
{
if (!($this->requestType === 'anime' || $this->requestType === 'manga')) {
return $data;
}
// Fix JSON response for empty related object
if (isset($data['related']) && \count($data['related']) === 0) {
$data['related'] = new \stdClass();
}
return $data;
}
}

16
app/Http/Middleware/JikanResponseLegacy.php Normal file → Executable file
View File

@ -34,9 +34,6 @@ class JikanResponseLegacy
if (\in_array('meta', $request->segments())) {
return $next($request);
}
if ($request->hasHeader('auth') === env('APP_ADMIN_KEY')) {
return $next($request);
}
$this->requestUri = $request->getRequestUri();
$this->requestType = HttpHelper::requestType($request);
@ -71,6 +68,7 @@ class JikanResponseLegacy
$cache = app('redis')->get($this->fingerprint);
$cacheMutable = json_decode(app('redis')->get($this->fingerprint), true);
$cacheMutable = $this->cacheMutation($cacheMutable);
return response()
->json(
@ -115,4 +113,16 @@ class JikanResponseLegacy
return $meta;
}
private function cacheMutation(array $data) : string
{
if (!($this->requestType === 'anime' || $this->requestType === 'manga')) {
return $data;
}
// Fix JSON response for empty related object
if (isset($data['related']) && \count($data['related']) === 0) {
$data['related'] = new \stdClass();
}
}
}

0
app/Http/Middleware/Meta.php Normal file → Executable file
View File

View File

@ -0,0 +1 @@
<?php

0
app/Http/Middleware/SlaveAuthentication.php Normal file → Executable file
View File

0
app/Http/Middleware/Throttle.php Normal file → Executable file
View File

9
app/Jobs/UpdateCacheJob.php Normal file → Executable file
View File

@ -6,6 +6,8 @@ use App\Http\HttpHelper;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
/**
* Class UpdateCacheJob
@ -44,7 +46,7 @@ class UpdateCacheJob extends Job
$this->fingerprint = "request:{$this->requestType}:{$this->requestUriHash}";
$this->cacheExpiryFingerprint = "ttl:{$this->fingerprint}";
$this->requestCached = (bool) app('redis')->exists($this->fingerprint);
$this->requestCached = (bool) Cache::has($this->fingerprint);
}
@ -53,6 +55,7 @@ class UpdateCacheJob extends Job
*/
public function handle() : void
{
$queueFingerprint = "queue_update:{$this->fingerprint}";
$client = new Client();
@ -73,8 +76,8 @@ class UpdateCacheJob extends Job
$cache = json_encode($cache);
app('redis')->set($this->fingerprint, $cache);
app('redis')->set($this->cacheExpiryFingerprint, $this->requestCacheExpiry);
Cache::forever($this->fingerprint, $cache);
Cache::forever($this->cacheExpiryFingerprint, time() + $this->requestCacheTtl);
app('redis')->del($queueFingerprint);
}

0
app/Providers/SearchQueryBuilder.php Normal file → Executable file
View File

0
app/Providers/SerializationContextFactory.php Normal file → Executable file
View File

0
app/Providers/SerializerFactory.php Normal file → Executable file
View File

0
app/Providers/UserListQueryBuilder.php Normal file → Executable file
View File

View File

@ -89,8 +89,10 @@ $app->routeMiddleware([
$app->configure('database');
$app->configure('queue');
$app->configure('cache');
$app->register(Illuminate\Redis\RedisServiceProvider::class);
$app->register(Flipbox\LumenGenerator\LumenGeneratorServiceProvider::class);
$guzzleClient = new \GuzzleHttp\Client();
$app->instance('GuzzleClient', $guzzleClient);

View File

@ -18,7 +18,8 @@
"fabpot/goutte": "3.2.3",
"jikan-me/jikan": "^2.7",
"ext-json": "*",
"ocramius/package-versions": "^1.4"
"ocramius/package-versions": "^1.4",
"flipbox/lumen-generator": "^5.6"
},
"require-dev": {
"fzaninotto/faker": "~1.4",

917
composer.lock generated Normal file → Executable file

File diff suppressed because it is too large Load Diff

4
conf/supervisor/jikan-worker.conf Normal file → Executable file
View File

@ -4,7 +4,9 @@ command=php /var/www/jikan-rest/artisan queue:work --queue=high,low
autostart=true
autorestart=true
;user=forge
; Number of active jikan workers, you can change this
numprocs=5
redirect_stderr=true
;redirect_stderr=true
; Feel free to comment out `stdout_logfile`, it's just for debugging purposes
stdout_logfile=/var/www/jikan-rest/storage/logs/worker.log
stderr_logfile=/var/www/jikan-rest/storage/logs/worker.error.log

79
config/cache.php Executable file
View File

@ -0,0 +1,79 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache connection that gets used while
| using this caching library. This connection is used when another is
| not explicitly specified when executing a given caching function.
|
*/
'default' => env('CACHE_DRIVER', 'file'),
/*
|--------------------------------------------------------------------------
| Cache Stores
|--------------------------------------------------------------------------
|
| Here you may define all of the cache "stores" for your application as
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
*/
'stores' => [
'apc' => [
'driver' => 'apc',
],
'array' => [
'driver' => 'array',
],
'database' => [
'driver' => 'database',
'table' => env('CACHE_DATABASE_TABLE', 'cache'),
'connection' => env('CACHE_DATABASE_CONNECTION', null),
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache'),
],
'memcached' => [
'driver' => 'memcached',
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'), 'port' => env('MEMCACHED_PORT', 11211), 'weight' => 100,
],
],
],
'redis' => [
'driver' => 'redis',
'connection' => env('CACHE_REDIS_CONNECTION', 'default'),
],
],
/*
|--------------------------------------------------------------------------
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing a RAM based store such as APC or Memcached, there might
| be other applications utilizing the same cache. So, we'll specify a
| value to get prefixed to all our keys so we can avoid collisions.
|
*/
'prefix' => env('CACHE_PREFIX', 'jikan'),
];

0
config/database.php Normal file → Executable file
View File

0
config/queue.php Normal file → Executable file
View File

0
routes/web.v2.php Normal file → Executable file
View File

0
routes/web.v3.php Normal file → Executable file
View File

0
routes/web.v4.php Normal file → Executable file
View File

0
storage/app/blacklist.json Normal file → Executable file
View File

0
storage/app/metadata.v2/Jikan.Model.Anime.Anime.yml Normal file → Executable file
View File

View File

View File

View File

View File

0
storage/app/metadata.v2/Jikan.Model.Anime.Episodes.yml Normal file → Executable file
View File

View File

View File

View File

View File

View File

View File

View File

View File

0
storage/app/metadata.v2/Jikan.Model.Manga.Manga.yml Normal file → Executable file
View File

View File

View File

0
storage/app/metadata.v2/Jikan.Model.Person.Person.yml Normal file → Executable file
View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

0
storage/app/metadata.v2/Jikan.Model.Top.TopAnime.yml Normal file → Executable file
View File

View File

0
storage/app/metadata.v2/Jikan.Model.Top.TopManga.yml Normal file → Executable file
View File

0
storage/app/metadata.v2/Jikan.Model.Top.TopPerson.yml Normal file → Executable file
View File

0
storage/app/metadata.v3/Jikan.Model.Anime.Anime.yml Normal file → Executable file
View File

View File

View File

0
storage/app/metadata.v3/Jikan.Model.Anime.Episodes.yml Normal file → Executable file
View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

0
storage/app/metadata.v3/Jikan.Model.Common.Picture.yml Normal file → Executable file
View File

Some files were not shown because too many files have changed in this diff Show More